feat(dock): the MIC bay — dB fader, measured ladder, mute, picker

Unit 5. The dock's first working bay:
- 24-segment ladder fed by `MicMeterFraction` — the MEASURED display level (already scaled by the
  volume and zeroed when muted), never the gain setting.
- Fader in dB via `MicVolumeDb` (−60 … +6, 0 = unity) with a mono-weight readout
  (`MicVolumeDbLabel`), so "−6.0 dB" is what the creator reads instead of "0.50".
- Mute button whose dot uses the existing MicStatusBrush; clicking the device name opens the mic
  picker, so the label stays the control (the KISS rule: one place to change the mic).

The preview's own mic row stays for now — it is removed in the final unit, after the dock bays are
complete, so nothing is lost if a bay misbehaves.

Guard: MicBayTests — the bay binds the dB face and the measured level (not the gain), the fader
range IS the scale's range, the VM surface exists, and mute still zeroes the ladder.
This commit is contained in:
2026-10-30 12:46:57 -07:00
parent 7d8021ca09
commit 3524ede480
4 changed files with 111 additions and 5 deletions
+26
View File
@@ -53,6 +53,9 @@ public partial class MainViewModel
{
OnPropertyChanged(nameof(MeterFillWidth));
OnPropertyChanged(nameof(MeterBrush));
OnPropertyChanged(nameof(MicMeterFraction));
OnPropertyChanged(nameof(MicVolumeDb));
OnPropertyChanged(nameof(MicVolumeDbLabel));
}
}
}
@@ -141,6 +144,29 @@ public partial class MainViewModel
}
}
/// <summary>The fader's face, in dB (−60 … +6, 0 = unity). The model keeps linear gain; this is
/// the same value expressed the way a person reads a mixer — and the only thing the dock binds.
/// </summary>
public double MicVolumeDb
{
get => Helpers.GainDb.ToDb(MicVolume);
set
{
var gain = Helpers.GainDb.FromDb(value);
if (Math.Abs(gain - MicVolume) < 1e-6) return;
MicVolume = gain;
OnPropertyChanged(nameof(MicVolumeDb));
OnPropertyChanged(nameof(MicVolumeDbLabel));
}
}
/// <summary>"-6.0 dB" beside the fader; "−∞ dB" at the floor.</summary>
public string MicVolumeDbLabel => Helpers.GainDb.Label(MicVolumeDb) + " dB";
/// <summary>The ladder's input: a 0…1 display fraction of what was MEASURED (already scaled by
/// the volume and muted state), never the gain setting.</summary>
public double MicMeterFraction => MeterLevel;
/// <summary>Read-only: true whenever the volume is 0. Only MicVolume may
/// change it, so the slider and the speaker can never disagree.</summary>
public bool MicMuted => _micMuted;