From 4a80a16d7914b587a5912ecd29d3ac33314a6c1b Mon Sep 17 00:00:00 2001 From: atagen Date: Thu, 21 May 2026 19:50:11 +1000 Subject: [PATCH] docs: explain why mono streams aren't link-enforced MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `pair_count < 2` early-return in `apply_pending_routes` looked arbitrary from the outside (and Codex+self-review both flagged it as a possible bug). It's actually a deliberate choice: WP's source-side upmix adapter handles mono → stereo cleanly today, and broadcasting one source port to N target ports via link-factory fanout requires the limiter's stereo-link semantics and the BS.1770 multichannel weights to make sense for N=1 — neither generalises trivially. The proper fix lives in the v1 multichannel pipeline. Replaces the old "PipeWire's adapter is responsible for any downmix" comment with the actual reasoning + the contract caveat (`route.set` on a mono app won't move it; the metadata write is a hint, not enforcement) so a future contributor doesn't accidentally "fix" it without weighing the trade-offs. No code change beyond the comment + the debug-log message. --- crates/headroom-core/src/pw/registry.rs | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/crates/headroom-core/src/pw/registry.rs b/crates/headroom-core/src/pw/registry.rs index c4f5904..13dee0f 100644 --- a/crates/headroom-core/src/pw/registry.rs +++ b/crates/headroom-core/src/pw/registry.rs @@ -1114,22 +1114,31 @@ impl RoutingState { }; // Pair by ordinal up to whichever side has fewer // channels. For stereo→stereo this is the original - // `take(2)`. For wider streams (surround) routed - // Bypass to a wide sink we pair all N channels — fixes - // the F3 bug where the old `take(2)` silently dropped - // the centre, LFE, and surround channels of a 5.1 - // stream. If the wider side has more channels than the - // narrower side, the extra ports are left unlinked - // here; the destruction pass below also leaves them - // alone (they don't land on the target sink at all). - // PipeWire's adapter is responsible for any downmix. + // `take(2)`. For wider streams (surround) routed Bypass + // to a wide sink we pair all N channels — fixes the F3 + // bug where the old `take(2)` silently dropped the + // centre, LFE, and surround channels of a 5.1 stream. + // + // **Mono streams are intentionally not enforced.** A + // single-port source needs broadcast (1→N fanout) to + // play on both channels of a stereo sink, and the + // limiter's stereo-link semantics + the BS.1770 + // multichannel weights don't generalise cleanly to + // `N=1`. WP's source-side upmix adapter handles mono + // → stereo correctly today, so we let it. The cost is + // a small contract leak (`route.set` on a mono app + // won't actually move it; the metadata write is a + // hint, not enforcement) — acceptable for v0, the + // proper fix is part of the v1 multichannel pipeline. + // See PLAN §11 "Filter rate matching" and the + // multichannel-deferral memory. let pair_count = src_outs.len().min(target_ins.len()); if pair_count < 2 { tracing::debug!( node_id, src_outs = src_outs.len(), target_ins = target_ins.len(), - "pending route: not enough ports yet" + "pending route: not enough ports for stereo+ pairing (mono left to WP)" ); continue; }