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:
co-authored by
Claude Opus 4.8
parent
d7f6d9fd9d
commit
1c1bf5a5cb
@@ -68,6 +68,12 @@ public sealed class AppConfig
|
||||
/// already running quietly". Default false.</summary>
|
||||
public bool StartMinimised { get; set; }
|
||||
|
||||
/// <summary>If true, the main window shows a "Pan and EQ" tab (positioned before "Audio profile")
|
||||
/// for setting per-peer panning and EQ. Off by default; toggled by "Show pan and EQ tab" on the
|
||||
/// Preferences General tab. Machine-wide (a UI-visibility preference, like which tabs exist) — the
|
||||
/// pan/EQ VALUES are saved per profile, in <see cref="Profile.PeerShaping"/>.</summary>
|
||||
public bool ShowPanEqTab { get; set; }
|
||||
|
||||
/// <summary>If true (the default), RemSound plays the startup cue once, right after this
|
||||
/// copy wins the single-instance takeover and before the profile loads. Machine-wide (not
|
||||
/// per-<see cref="Profile"/>) because it fires before any profile — and its per-profile
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
namespace RemSound.Core;
|
||||
|
||||
/// <summary>Which EQ the user is shaping a peer with. The two modes are INDEPENDENT: switching
|
||||
/// between them keeps each mode's own band gains — nothing is carried over. The active mode is the
|
||||
/// one that's applied to the sound.</summary>
|
||||
public enum PeerEqMode
|
||||
{
|
||||
Simple3Band = 0,
|
||||
Advanced10Band = 1,
|
||||
}
|
||||
|
||||
/// <summary>Per-peer pan + EQ, saved per profile (see <see cref="Profile.PeerShaping"/>). Keyed in
|
||||
/// the profile by the same peer-entry string as <see cref="Profile.SelectedConnectedPeers"/>.</summary>
|
||||
public sealed class PeerShaping
|
||||
{
|
||||
/// <summary>-1 (full left) .. 0 (centre) .. +1 (full right).</summary>
|
||||
public float Pan { get; set; }
|
||||
|
||||
/// <summary>Which EQ mode is currently active for this peer.</summary>
|
||||
public PeerEqMode EqMode { get; set; } = PeerEqMode.Simple3Band;
|
||||
|
||||
/// <summary>Gains in dB (-12..+12) for the 3 simple bands, in the order of <see cref="PeerEqBands.Simple"/>
|
||||
/// (bass, mids, treble). Kept independently of the advanced bands.</summary>
|
||||
public float[] SimpleBandsDb { get; set; } = new float[3];
|
||||
|
||||
/// <summary>Gains in dB (-12..+12) for the 10 advanced graphic-EQ bands, in the order of
|
||||
/// <see cref="PeerEqBands.Advanced"/>. Kept independently of the simple bands.</summary>
|
||||
public float[] AdvancedBandsDb { get; set; } = new float[10];
|
||||
}
|
||||
|
||||
/// <summary>The fixed EQ band layouts. The user sets each band's GAIN; the centre frequencies are
|
||||
/// fixed so the controls stay simple and predictable. Shared by the DSP (which builds the filters)
|
||||
/// and the UI (which labels the sliders), so the two can never drift apart.</summary>
|
||||
public static class PeerEqBands
|
||||
{
|
||||
/// <summary>RemSound's internal mix sample rate. All received audio is resampled to this stereo
|
||||
/// float mix before per-peer shaping, so filter coefficients are computed against it.</summary>
|
||||
public const int MixSampleRate = 48000;
|
||||
|
||||
/// <summary>Maximum boost/cut for any band, in dB. Sliders run -12..+12.</summary>
|
||||
public const float MaxGainDb = 12f;
|
||||
|
||||
/// <summary>3-band "tone control": bass (low shelf), mids (peaking), treble (high shelf).</summary>
|
||||
public static readonly (string Label, double Freq)[] Simple =
|
||||
[
|
||||
("Bass", 100.0),
|
||||
("Mids", 1000.0),
|
||||
("Treble", 8000.0),
|
||||
];
|
||||
|
||||
/// <summary>10-band graphic EQ on ISO octave centres (all peaking).</summary>
|
||||
public static readonly (string Label, double Freq)[] Advanced =
|
||||
[
|
||||
("31 Hz", 31.0),
|
||||
("63 Hz", 63.0),
|
||||
("125 Hz", 125.0),
|
||||
("250 Hz", 250.0),
|
||||
("500 Hz", 500.0),
|
||||
("1 kHz", 1000.0),
|
||||
("2 kHz", 2000.0),
|
||||
("4 kHz", 4000.0),
|
||||
("8 kHz", 8000.0),
|
||||
("16 kHz", 16000.0),
|
||||
];
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user