docs: explain why mono streams aren't link-enforced

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.
This commit is contained in:
atagen 2026-05-21 19:50:11 +10:00
parent ec49206660
commit 4a80a16d79

View file

@ -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;
}