diff --git a/PLAN.md b/PLAN.md index 4cc64a1..4fedb21 100644 --- a/PLAN.md +++ b/PLAN.md @@ -233,6 +233,23 @@ downsampling) guarantee the contract numerically — the envelope can misbehave and the contract still holds. Never bypassed, never disabled. +**Contract scope (caveat).** The ≤ −0.1 dBTP guarantee holds at the +*filter's output*, not at the speaker. The bus filter is hardcoded +F32 stereo @ 48 kHz (`headroom-dsp::limiter`'s 4× oversampler is +sized for 48 k); when the real sink negotiates a different rate +(44.1 kHz, 96 kHz, 192 kHz), PipeWire inserts a downstream +resampler between `filter.playback` and the sink. Polynomial / +windowed-sinc resamplers can elevate inter-sample peaks slightly +through their own reconstruction, so the limiter's true-peak +guarantee leaks across that resampling stage. In practice the +elevation is small (a few tenths of a dB worst case for a clean +band-limited resampler), and the contract still holds at the bus +output where headroom is in control. **For the contract to hold +end-to-end the filter would need to match the real sink's rate +and rebuild its DSP coefficients on rate-change** — that's the +v1 work tracked as PLAN §11 "filter rate matching" (deferred from +8d, gated on a multi-rate hardware test bench). + **Soft tier — the comfort cap.** Targets a *dynamic* ceiling computed as `program_lufs + max_psr_db`. Smooth attack/release envelope so the gain reduction sounds like volume riding, not a slap. Pulls transients @@ -903,6 +920,14 @@ lost. Pick up by name when the trigger that gates them fires. for simplicity. Revisit if real users ask for it; the store-level change is a flag on the setter methods. **Dormant** — no user has asked through Phase 8. +- **Filter rate matching to the real sink.** *(F5 follow-up.)* §3.1 + documents the contract leak when the real sink runs at a + non-48 kHz native rate. Closing it requires dynamic + `FILTER_SAMPLE_RATE`, kernel rebuild on real-sink change + (compressor + limiter coefficients are rate-dependent), and + Layer A's `LAYER_A_BLOCK_DT_S` constant becoming dynamic too. + Gated on a multi-rate hardware test bench — no point shipping + the refactor without something to validate it against. **v1 scope.** - ~~**Filter playback BUSY spikes (periodic, ~10 s cadence).**~~ **Closed in 8e (`d52cd6d`).** The instrumentation added by 8e did not reproduce the ~8×-baseline outlier pattern in a ~3 min