Per-peer pan + EQ ("Pan and EQ" tab) — off by default, held for next release

New feature (Ed's jam-mixing request): pan and EQ each peer's signal independently.

Engine (zero added latency — per-sample, applied to each peer's isolated block just
before the mix): PeerDspChain (balance pan + RBJ biquad EQ) built on the UI thread and
swapped onto SessionPlayout via a volatile reference; PlayoutEngine remembers it per
address so a reconnecting peer keeps its shaping; AudioReceiver.SetPeerDsp facade.

Model: per-profile PeerShaping dict (keyed by peer address) + EnablePan/EnableEqForPeers
master switches; machine-wide AppConfig.ShowPanEqTab. Fixed band layouts in PeerEqBands
(3-band tone control: bass/mids/treble shelves+bell; 10-band ISO graphic EQ), +/-12 dB.

UI: a "Pan and EQ" tab (before Audio profile, shown only when ShowPanEqTab is on) with
the two enables, a connected-peer picker, a pan slider (balance, keeps stereo), a
"reset EQ" button (clears both modes' bands, leaves pan), a 3/10-band mode picker and its
band sliders — all TrackBars (arrow + page-up/down), updating in real time and saved per
profile. Sliders set a friendly AccessibleName on change (pan centre/left/right %, band
dB). "Show the Pan and EQ tab" checkbox added to Preferences > General.

Everything is off by default (tab hidden, both enables off), so this is dormant for all
users until switched on. Builds clean. Pending Ed's hands-on testing before release.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-04 13:54:30 +01:00
co-authored by Claude Opus 4.8
parent d7f6d9fd9d
commit 1c1bf5a5cb
9 changed files with 558 additions and 1 deletions
+13
View File
@@ -183,6 +183,19 @@ public sealed class Profile
/// (the connection simply waits or drops rather than moving). Off by default. (#17)</summary>
public bool LockPeerAddresses { get; set; }
// === Per-peer pan and EQ (jam mixing) ===
/// <summary>Master switch for this profile: apply each connected peer's saved PAN. Off by default.
/// The pan values themselves live in <see cref="PeerShaping"/>.</summary>
public bool EnablePanForPeers { get; set; }
/// <summary>Master switch for this profile: apply each connected peer's saved EQ. Off by default.</summary>
public bool EnableEqForPeers { get; set; }
/// <summary>Per-peer pan + EQ, keyed by the SAME peer-entry string used in
/// <see cref="SelectedConnectedPeers"/> (user text / discovery label / address:port). A peer with
/// no entry gets centre pan and flat EQ. Empty on a fresh profile.</summary>
public Dictionary<string, PeerShaping> PeerShaping { get; set; } = new();
// === Hotkeys ===
public HotkeyRecord? ReceiveMuteHotkey { get; set; }
public HotkeyRecord? SendMuteHotkey { get; set; }