feat(clients): unify volume boost cap to 4x across all clients

Mic input gain (was 3x) and per-user receive gain (was 2x) had
asymmetric boost ceilings. Raise both, plus the desktop aux input
gain (was 3x), to a uniform 4x (400%) on macOS, iOS, and Windows.

The master Output volume slider is unchanged (still 1x). No core
changes needed: the C ABI only clamps negatives, so the ceilings
live entirely in the client UI sliders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 12:18:10 +02:00
parent 7ef560ba8a
commit 8bb2ba933c
7 changed files with 17 additions and 17 deletions

View File

@@ -20,7 +20,7 @@ struct PerUserTuningView: View {
Section("Volume") { Section("Volume") {
HStack { HStack {
Text("Gain") Text("Gain")
Slider(value: $gain, in: 0...2, step: 0.05) { _ in Slider(value: $gain, in: 0...4, step: 0.05) { _ in
applyToAllStreams() applyToAllStreams()
} }
.accessibilityLabel("Volume gain for \(user.nickname)") .accessibilityLabel("Volume gain for \(user.nickname)")

View File

@@ -246,7 +246,7 @@ struct SettingsView: View {
get: { Double(session.voiceState.inputGain) }, get: { Double(session.voiceState.inputGain) },
set: { session.setInputGain(Float($0)) } set: { session.setInputGain(Float($0)) }
), ),
in: 0...3, step: 0.05 in: 0...4, step: 0.05
) )
.accessibilityLabel("Microphone volume") .accessibilityLabel("Microphone volume")
.accessibilityValue("\(Int((session.voiceState.inputGain * 100).rounded())) percent") .accessibilityValue("\(Int((session.voiceState.inputGain * 100).rounded())) percent")

View File

@@ -49,7 +49,7 @@ final class PerUserTuningSheet: NSViewController {
let muted = state?.muted ?? false let muted = state?.muted ?? false
let nr = state?.noiseReduction ?? false let nr = state?.noiseReduction ?? false
let gainSlider = NSSlider(value: Double(gain * 100), minValue: 0, maxValue: 200, target: self, action: #selector(sliderChanged)) let gainSlider = NSSlider(value: Double(gain * 100), minValue: 0, maxValue: 400, target: self, action: #selector(sliderChanged))
gainSlider.tag = Int(stream.id) gainSlider.tag = Int(stream.id)
gainSlider.numberOfTickMarks = 0 gainSlider.numberOfTickMarks = 0
gainSlider.setAccessibilityLabel("Volume for \(stream.label): \(Int(gain * 100)) percent") gainSlider.setAccessibilityLabel("Volume for \(stream.label): \(Int(gain * 100)) percent")

View File

@@ -50,9 +50,9 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
// Cached VAD slider position so we can restore it when the window reopens. // Cached VAD slider position so we can restore it when the window reopens.
private var vadSliderValue: Double = 50 private var vadSliderValue: Double = 50
// Mic input gain: 0300 % (100 = unity). Persisted via MainWindowController.inputGain. // Mic input gain: 0400 % (100 = unity). Persisted via MainWindowController.inputGain.
private let inputGainSlider: NSSlider = { private let inputGainSlider: NSSlider = {
let s = NSSlider(value: 100, minValue: 0, maxValue: 300, target: nil, action: nil) let s = NSSlider(value: 100, minValue: 0, maxValue: 400, target: nil, action: nil)
s.numberOfTickMarks = 0 s.numberOfTickMarks = 0
return s return s
}() }()
@@ -77,7 +77,7 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
private let auxDevicePicker = NSPopUpButton() private let auxDevicePicker = NSPopUpButton()
private let auxRefreshButton = NSButton() private let auxRefreshButton = NSButton()
private let auxGainSlider: NSSlider = { private let auxGainSlider: NSSlider = {
let s = NSSlider(value: 100, minValue: 0, maxValue: 300, target: nil, action: nil) let s = NSSlider(value: 100, minValue: 0, maxValue: 400, target: nil, action: nil)
s.numberOfTickMarks = 0 s.numberOfTickMarks = 0
return s return s
}() }()

View File

@@ -194,15 +194,15 @@ public sealed class AudioSettingsForm : Form
Location = new Point(12, 288), Location = new Point(12, 288),
Size = new Size(200, 45), Size = new Size(200, 45),
Minimum = 0, Minimum = 0,
Maximum = 300, Maximum = 400,
TickFrequency = 25, TickFrequency = 25,
SmallChange = 5, SmallChange = 5,
LargeChange = 25, LargeChange = 25,
Value = Math.Clamp(settings.MicGain, 0, 300), Value = Math.Clamp(settings.MicGain, 0, 400),
}; };
_trkGain.AccessibleName = "Microphone volume"; _trkGain.AccessibleName = "Microphone volume";
_trkGain.AccessibleDescription = _trkGain.AccessibleDescription =
"Boost a quiet microphone. 100 is unity gain; range 0300 percent."; "Boost a quiet microphone. 100 is unity gain; range 0400 percent.";
_trkGain.Scroll += TrkGain_Scroll; _trkGain.Scroll += TrkGain_Scroll;
// ── Mic noise reduction ─────────────────────────────────────────────── // ── Mic noise reduction ───────────────────────────────────────────────
@@ -290,15 +290,15 @@ public sealed class AudioSettingsForm : Form
Location = new Point(12, 510), Location = new Point(12, 510),
Size = new Size(200, 45), Size = new Size(200, 45),
Minimum = 0, Minimum = 0,
Maximum = 300, Maximum = 400,
TickFrequency = 25, TickFrequency = 25,
SmallChange = 5, SmallChange = 5,
LargeChange = 25, LargeChange = 25,
Value = Math.Clamp(settings.AuxGain, 0, 300), Value = Math.Clamp(settings.AuxGain, 0, 400),
}; };
_trkAuxGain.AccessibleName = "Aux volume"; _trkAuxGain.AccessibleName = "Aux volume";
_trkAuxGain.AccessibleDescription = _trkAuxGain.AccessibleDescription =
"Volume of the aux input stream. 100 is unity gain; range 0300 percent."; "Volume of the aux input stream. 100 is unity gain; range 0400 percent.";
_trkAuxGain.Scroll += TrkAuxGain_Scroll; _trkAuxGain.Scroll += TrkAuxGain_Scroll;
// ── OK / Cancel ─────────────────────────────────────────────────────── // ── OK / Cancel ───────────────────────────────────────────────────────

View File

@@ -117,11 +117,11 @@ public sealed class PerUserTuningDialog : Form
var trkGain = new TrackBar var trkGain = new TrackBar
{ {
AccessibleName = $"Gain for {s.Label}", AccessibleName = $"Gain for {s.Label}",
AccessibleDescription = "Volume level for this stream. 100 is normal (1.0×), 200 is double.", AccessibleDescription = "Volume level for this stream. 100 is normal (1.0×); range 0400 percent.",
Location = new Point(12, y + 20), Location = new Point(12, y + 20),
Size = new Size(260, 45), Size = new Size(260, 45),
Minimum = 0, Minimum = 0,
Maximum = 200, Maximum = 400,
Value = ClampToTrack(gain0), Value = ClampToTrack(gain0),
TickFrequency = 25, TickFrequency = 25,
SmallChange = 5, SmallChange = 5,
@@ -186,7 +186,7 @@ public sealed class PerUserTuningDialog : Form
private static int ClampToTrack(float gain) private static int ClampToTrack(float gain)
{ {
int v = (int)Math.Round(gain * 100f); int v = (int)Math.Round(gain * 100f);
return Math.Max(0, Math.Min(200, v)); return Math.Max(0, Math.Min(400, v));
} }
private sealed class StreamRow private sealed class StreamRow

View File

@@ -18,7 +18,7 @@ public sealed class VoiceSettings
/// <summary>VAD sensitivity slider position, 1100 (default mirrors the designer's 76).</summary> /// <summary>VAD sensitivity slider position, 1100 (default mirrors the designer's 76).</summary>
public int VadThresholdSlider { get; set; } = 76; public int VadThresholdSlider { get; set; } = 76;
/// <summary>Microphone input gain slider position, 0300 percent (100 = unity).</summary> /// <summary>Microphone input gain slider position, 0400 percent (100 = unity).</summary>
public int MicGain { get; set; } = 100; public int MicGain { get; set; } = 100;
/// <summary>Send-side mic noise reduction (RNNoise) toggle. MIC stream only, mono only; /// <summary>Send-side mic noise reduction (RNNoise) toggle. MIC stream only, mono only;
@@ -48,7 +48,7 @@ public sealed class VoiceSettings
/// NOT a core/miniaudio id — the aux device is opened client-side, so the two id spaces differ.</summary> /// NOT a core/miniaudio id — the aux device is opened client-side, so the two id spaces differ.</summary>
public string? AuxDeviceId { get; set; } = null; public string? AuxDeviceId { get; set; } = null;
/// <summary>Aux input volume slider position, 0300 percent (100 = unity). Applied client-side /// <summary>Aux input volume slider position, 0400 percent (100 = unity). Applied client-side
/// to the captured PCM before feeding (the core's input gain is mic-only and global).</summary> /// to the captured PCM before feeding (the core's input gain is mic-only and global).</summary>
public int AuxGain { get; set; } = 100; public int AuxGain { get; set; } = 100;