diff --git a/clients/windows/VoiceCat.App/Forms/AudioSettingsForm.cs b/clients/windows/VoiceCat.App/Forms/AudioSettingsForm.cs new file mode 100644 index 0000000..35ca910 --- /dev/null +++ b/clients/windows/VoiceCat.App/Forms/AudioSettingsForm.cs @@ -0,0 +1,325 @@ +using System.ComponentModel; +using VoiceCat.App.Models; +using VoiceCat.Interop; + +namespace VoiceCat.App.Forms; + +/// +/// Edits audio input settings: device selection, transmission mode (VAD/PTT/always-on), +/// VAD sensitivity, mic gain, and PTT key. Changes are applied live to the client for +/// immediate feedback; Cancel reverts them. +/// +public sealed class AudioSettingsForm : Form +{ + private readonly VoiceCatClient _client; + private readonly VoiceSettings _settings; + private readonly uint _micStreamId; + + // Snapshot of original values so Cancel can restore them + private readonly string? _origDeviceId; + private readonly VcInputMode _origMode; + private readonly int _origVadSlider; + private readonly int _origMicGain; + private readonly Keys _origPttKey; + + private readonly ComboBox _cboDevice; + private readonly Button _btnRefresh; + private readonly RadioButton _radioVad; + private readonly RadioButton _radioPtt; + private readonly RadioButton _radioAlwaysOn; + private readonly Label _lblPttKey; + private readonly Button _btnChangePtt; + private readonly Label _lblSensitivity; + private readonly TrackBar _trkVad; + private readonly TrackBar _trkGain; + + private Keys _pttKey; + + public AudioSettingsForm(VoiceCatClient client, VoiceSettings settings, uint micStreamId) + { + _client = client; + _settings = settings; + _micStreamId = micStreamId; + _pttKey = (Keys)settings.PttKey; + + _origDeviceId = settings.InputDeviceId; + _origMode = (VcInputMode)settings.InputMode; + _origVadSlider = settings.VadThresholdSlider; + _origMicGain = settings.MicGain; + _origPttKey = _pttKey; + + Text = "Audio settings"; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + StartPosition = FormStartPosition.CenterParent; + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(420, 370); + + // ── Device row ──────────────────────────────────────────────────────── + var lblDevice = new Label + { + Text = "Input &device:", + Location = new Point(12, 16), + AutoSize = true, + }; + + _cboDevice = new ComboBox + { + Location = new Point(12, 36), + Width = 300, + DropDownStyle = ComboBoxStyle.DropDownList, + DisplayMember = "Name", + ValueMember = "Id", + AccessibleName = "Input device", + AccessibleDescription = "Select which microphone or audio input device to use.", + }; + _cboDevice.SelectedIndexChanged += CboDevice_SelectedIndexChanged; + + _btnRefresh = new Button + { + Text = "&Refresh", + Location = new Point(320, 34), + Size = new Size(80, 26), + }; + _btnRefresh.Click += (_, _) => LoadDevices(); + + // ── Transmission mode ───────────────────────────────────────────────── + var lblMode = new Label + { + Text = "Transmission mode:", + Location = new Point(12, 76), + AutoSize = true, + }; + + _radioVad = new RadioButton + { + Text = "&Voice activation", + Location = new Point(20, 96), + AutoSize = true, + }; + _radioVad.CheckedChanged += RadioMode_CheckedChanged; + + _lblSensitivity = new Label + { + Text = "Sensitivity:", + Location = new Point(36, 122), + AutoSize = true, + }; + _trkVad = new TrackBar + { + Location = new Point(36, 140), + Size = new Size(200, 45), + Minimum = 1, + Maximum = 100, + TickFrequency = 10, + SmallChange = 1, + LargeChange = 10, + Value = Math.Clamp(settings.VadThresholdSlider, 1, 100), + }; + _trkVad.AccessibleName = "VAD sensitivity"; + _trkVad.AccessibleDescription = + "Voice detection sensitivity. Higher = more sensitive. Range 1–100."; + _trkVad.Scroll += TrkVad_Scroll; + + _radioPtt = new RadioButton + { + Text = "&Push to talk", + Location = new Point(20, 192), + AutoSize = true, + }; + _radioPtt.CheckedChanged += RadioMode_CheckedChanged; + + _lblPttKey = new Label + { + Location = new Point(140, 194), + AutoSize = true, + }; + + _btnChangePtt = new Button + { + Text = "Change key...", + Location = new Point(200, 189), + Size = new Size(105, 26), + }; + _btnChangePtt.Click += BtnChangePtt_Click; + + _radioAlwaysOn = new RadioButton + { + Text = "A&lways on", + Location = new Point(20, 224), + AutoSize = true, + }; + _radioAlwaysOn.CheckedChanged += RadioMode_CheckedChanged; + + // ── Mic gain ────────────────────────────────────────────────────────── + var lblGain = new Label + { + Text = "Microphone &volume:", + Location = new Point(12, 268), + AutoSize = true, + }; + _trkGain = new TrackBar + { + Location = new Point(12, 288), + Size = new Size(200, 45), + Minimum = 0, + Maximum = 300, + TickFrequency = 25, + SmallChange = 5, + LargeChange = 25, + Value = Math.Clamp(settings.MicGain, 0, 300), + }; + _trkGain.AccessibleName = "Microphone volume"; + _trkGain.AccessibleDescription = + "Boost a quiet microphone. 100 is unity gain; range 0–300 percent."; + _trkGain.Scroll += TrkGain_Scroll; + + // ── OK / Cancel ─────────────────────────────────────────────────────── + var btnOk = new Button + { + Text = "&OK", + DialogResult = DialogResult.OK, + Location = new Point(228, 334), + Size = new Size(80, 27), + }; + var btnCancel = new Button + { + Text = "&Cancel", + DialogResult = DialogResult.Cancel, + Location = new Point(316, 334), + Size = new Size(80, 27), + }; + + AcceptButton = btnOk; + CancelButton = btnCancel; + + btnOk.Click += (_, _) => + { + _settings.Save(); + }; + FormClosing += AudioSettingsForm_FormClosing; + + Controls.AddRange([ + lblDevice, _cboDevice, _btnRefresh, + lblMode, _radioVad, _lblSensitivity, _trkVad, + _radioPtt, _lblPttKey, _btnChangePtt, _radioAlwaysOn, + lblGain, _trkGain, + btnOk, btnCancel, + ]); + + // Apply saved mode (fires RadioMode_CheckedChanged which sets visibility) + switch ((VcInputMode)settings.InputMode) + { + case VcInputMode.PushToTalk: _radioPtt.Checked = true; break; + case VcInputMode.AlwaysOn: _radioAlwaysOn.Checked = true; break; + default: _radioVad.Checked = true; break; + } + + LoadDevices(); + } + + private void LoadDevices() + { + string? currentId = (_cboDevice.SelectedItem as DeviceInfo)?.Id ?? _settings.InputDeviceId; + + var devices = _client.ListDevices(VcDeviceKind.Input); + _cboDevice.SelectedIndexChanged -= CboDevice_SelectedIndexChanged; + _cboDevice.DataSource = new BindingList(devices); + _cboDevice.SelectedIndexChanged += CboDevice_SelectedIndexChanged; + + // Try to restore previous selection + int idx = -1; + if (currentId is not null) + idx = devices.FindIndex(d => d.Id == currentId); + if (idx < 0) + idx = devices.FindIndex(d => d.IsDefault); + _cboDevice.SelectedIndex = idx >= 0 ? idx : (devices.Count > 0 ? 0 : -1); + } + + private void CboDevice_SelectedIndexChanged(object? sender, EventArgs e) + { + if (_cboDevice.SelectedItem is not DeviceInfo dev) return; + _settings.InputDeviceId = dev.IsDefault ? null : dev.Id; + if (_micStreamId != 0) + _client.SetInputDevice(_micStreamId, dev.IsDefault ? null : dev.Id); + } + + private void RadioMode_CheckedChanged(object? sender, EventArgs e) + { + var mode = CurrentMode(); + bool isVad = mode == VcInputMode.VoiceActivation; + bool isPtt = mode == VcInputMode.PushToTalk; + + _lblSensitivity.Visible = isVad; + _trkVad.Visible = isVad; + _lblPttKey.Visible = isPtt; + _btnChangePtt.Visible = isPtt; + UpdatePttKeyLabel(); + + _settings.InputMode = (int)mode; + if (_micStreamId != 0) + { + _client.SetInputMode(mode); + if (isVad) _client.SetVadThreshold(VadThresholdFromSlider()); + if (isPtt) _client.SetPushToTalk(false); + } + } + + private void TrkVad_Scroll(object? sender, EventArgs e) + { + _settings.VadThresholdSlider = _trkVad.Value; + if (_micStreamId != 0 && CurrentMode() == VcInputMode.VoiceActivation) + _client.SetVadThreshold(VadThresholdFromSlider()); + } + + private void TrkGain_Scroll(object? sender, EventArgs e) + { + _settings.MicGain = _trkGain.Value; + if (_micStreamId != 0) + _client.SetInputGain(_trkGain.Value / 100f); + } + + private void BtnChangePtt_Click(object? sender, EventArgs e) + { + using var dlg = new PttKeyCaptureDialog(_pttKey); + if (dlg.ShowDialog(this) == DialogResult.OK) + { + _pttKey = dlg.CapturedKey; + _settings.PttKey = (int)_pttKey; + UpdatePttKeyLabel(); + } + } + + private void AudioSettingsForm_FormClosing(object? sender, FormClosingEventArgs e) + { + if (DialogResult == DialogResult.OK) return; + + // Cancel: restore originals to settings and live client + _settings.InputDeviceId = _origDeviceId; + _settings.InputMode = (int)_origMode; + _settings.VadThresholdSlider = _origVadSlider; + _settings.MicGain = _origMicGain; + _settings.PttKey = (int)_origPttKey; + + if (_micStreamId != 0) + { + _client.SetInputDevice(_micStreamId, _origDeviceId); + _client.SetInputMode(_origMode); + if (_origMode == VcInputMode.VoiceActivation) + _client.SetVadThreshold(0.1f * (1f - (_origVadSlider - 1f) / 99f)); + _client.SetInputGain(_origMicGain / 100f); + } + } + + private void UpdatePttKeyLabel() => + _lblPttKey.Text = $"({_pttKey})"; + + private VcInputMode CurrentMode() => + _radioPtt.Checked ? VcInputMode.PushToTalk : + _radioAlwaysOn.Checked ? VcInputMode.AlwaysOn : + VcInputMode.VoiceActivation; + + private float VadThresholdFromSlider() => + 0.1f * (1f - (_trkVad.Value - 1f) / 99f); +} diff --git a/clients/windows/VoiceCat.App/Forms/MainForm.Designer.cs b/clients/windows/VoiceCat.App/Forms/MainForm.Designer.cs index 1edb4dd..6773a2a 100644 --- a/clients/windows/VoiceCat.App/Forms/MainForm.Designer.cs +++ b/clients/windows/VoiceCat.App/Forms/MainForm.Designer.cs @@ -36,23 +36,10 @@ partial class MainForm // Voice control panel (docked Bottom) private Panel pnlVoice = null!; private FlowLayoutPanel flpVoiceTop = null!; - private FlowLayoutPanel flpVoiceBottom = null!; private CheckBox chkMute = null!; private CheckBox chkDeafen = null!; - private RadioButton radioVad = null!; - private RadioButton radioPtt = null!; - private RadioButton radioAlwaysOn = null!; - private Label lblPttKey = null!; - private Button btnChangePtt = null!; - private Label lblInputDevice = null!; - private ComboBox cboInputDevice = null!; - private Button btnRefreshDevices = null!; private Label lblLevel = null!; private ProgressBar pbLevel = null!; - private Label lblVadThreshold = null!; - private TrackBar trkVadThreshold = null!; - private Label lblMicGain = null!; - private TrackBar trkMicGain = null!; protected override void Dispose(bool disposing) { @@ -81,23 +68,10 @@ partial class MainForm trkOutputVolume = new TrackBar(); pnlVoice = new Panel(); flpVoiceTop = new FlowLayoutPanel(); - flpVoiceBottom = new FlowLayoutPanel(); chkMute = new CheckBox(); chkDeafen = new CheckBox(); - radioVad = new RadioButton(); - radioPtt = new RadioButton(); - radioAlwaysOn = new RadioButton(); - lblPttKey = new Label(); - btnChangePtt = new Button(); - lblInputDevice = new Label(); - cboInputDevice = new ComboBox(); - btnRefreshDevices = new Button(); lblLevel = new Label(); pbLevel = new ProgressBar(); - lblVadThreshold = new Label(); - trkVadThreshold = new TrackBar(); - lblMicGain = new Label(); - trkMicGain = new TrackBar(); menuStrip = new MenuStrip(); toolStrip = new ToolStrip(); tsbJoinVoice = new ToolStripButton(); @@ -245,71 +219,15 @@ partial class MainForm chkDeafen.Margin = new Padding(0, 4, 12, 0); chkDeafen.TabIndex = 2; - var lblMode = new Label { Text = "Mode:", AutoSize = true, Margin = new Padding(0, 5, 4, 0) }; - - radioVad.Text = "&Voice activation"; - radioVad.AutoSize = true; - radioVad.Checked = true; - radioVad.Enabled = false; - radioVad.Margin = new Padding(0, 4, 6, 0); - radioVad.TabIndex = 3; - - radioPtt.Text = "&Push to talk"; - radioPtt.AutoSize = true; - radioPtt.Enabled = false; - radioPtt.Margin = new Padding(0, 4, 4, 0); - radioPtt.TabIndex = 4; - - radioAlwaysOn.Text = "A&lways on"; - radioAlwaysOn.AutoSize = true; - radioAlwaysOn.Enabled = false; - radioAlwaysOn.Margin = new Padding(0, 4, 12, 0); - radioAlwaysOn.TabIndex = 5; - - lblPttKey.Text = "(F8)"; - lblPttKey.AutoSize = true; - lblPttKey.Margin = new Padding(2, 5, 4, 0); - lblPttKey.Visible = false; - - btnChangePtt.Text = "Change key..."; - btnChangePtt.AutoSize = true; - btnChangePtt.Margin = new Padding(0, 2, 0, 0); - btnChangePtt.Visible = false; - btnChangePtt.TabIndex = 6; - - flpVoiceTop.Dock = DockStyle.Top; - flpVoiceTop.Height = 34; + flpVoiceTop.Dock = DockStyle.Fill; flpVoiceTop.AutoSize = false; flpVoiceTop.Padding = new Padding(4, 2, 4, 0); flpVoiceTop.Controls.Add(chkMute); flpVoiceTop.Controls.Add(chkDeafen); - flpVoiceTop.Controls.Add(lblMode); - flpVoiceTop.Controls.Add(radioVad); - flpVoiceTop.Controls.Add(radioPtt); - flpVoiceTop.Controls.Add(radioAlwaysOn); - flpVoiceTop.Controls.Add(lblPttKey); - flpVoiceTop.Controls.Add(btnChangePtt); - - // Bottom row: device picker + level meter - lblInputDevice.Text = "Input:"; - lblInputDevice.AutoSize = true; - lblInputDevice.Margin = new Padding(0, 5, 4, 0); - - cboInputDevice.AccessibleName = "Input device"; - cboInputDevice.AccessibleDescription = "Select which microphone or audio device to use."; - cboInputDevice.DropDownStyle = ComboBoxStyle.DropDownList; - cboInputDevice.Width = 200; - cboInputDevice.Margin = new Padding(0, 2, 4, 0); - cboInputDevice.TabIndex = 7; - - btnRefreshDevices.Text = "Re&fresh"; - btnRefreshDevices.AutoSize = true; - btnRefreshDevices.Margin = new Padding(0, 2, 12, 0); - btnRefreshDevices.TabIndex = 8; lblLevel.Text = "Level:"; lblLevel.AutoSize = true; - lblLevel.Margin = new Padding(0, 5, 4, 0); + lblLevel.Margin = new Padding(12, 5, 4, 0); pbLevel.AccessibleName = "Microphone level"; pbLevel.AccessibleDescription = "Current input level from the microphone."; @@ -320,61 +238,14 @@ partial class MainForm pbLevel.Style = ProgressBarStyle.Continuous; pbLevel.TabStop = false; - lblVadThreshold.Text = "Sensitivity:"; - lblVadThreshold.AutoSize = true; - lblVadThreshold.Margin = new Padding(12, 5, 4, 0); - lblVadThreshold.Visible = true; - - trkVadThreshold.AccessibleName = "VAD sensitivity"; - trkVadThreshold.AccessibleDescription = - "Voice detection sensitivity. Higher = more sensitive (triggers on quieter sounds). " + - "Range 1–100; default 25."; - trkVadThreshold.Minimum = 1; - trkVadThreshold.Maximum = 100; - trkVadThreshold.Value = 76; - trkVadThreshold.TickFrequency = 10; - trkVadThreshold.SmallChange = 1; - trkVadThreshold.LargeChange = 10; - trkVadThreshold.Width = 120; - trkVadThreshold.Margin = new Padding(0, 2, 0, 0); - trkVadThreshold.TabIndex = 9; - trkVadThreshold.Visible = true; - - lblMicGain.Text = "Mic volume:"; - lblMicGain.AutoSize = true; - lblMicGain.Margin = new Padding(12, 5, 4, 0); - - trkMicGain.AccessibleName = "Microphone volume"; - trkMicGain.AccessibleDescription = - "Boost a quiet microphone. 100 is unity; range 0–300 percent."; - trkMicGain.Minimum = 0; - trkMicGain.Maximum = 300; - trkMicGain.Value = 100; - trkMicGain.TickFrequency = 25; - trkMicGain.SmallChange = 5; - trkMicGain.LargeChange = 25; - trkMicGain.Width = 120; - trkMicGain.Margin = new Padding(0, 2, 0, 0); - trkMicGain.TabIndex = 10; - - flpVoiceBottom.Dock = DockStyle.Fill; - flpVoiceBottom.Padding = new Padding(4, 0, 4, 2); - flpVoiceBottom.Controls.Add(lblInputDevice); - flpVoiceBottom.Controls.Add(cboInputDevice); - flpVoiceBottom.Controls.Add(btnRefreshDevices); - flpVoiceBottom.Controls.Add(lblLevel); - flpVoiceBottom.Controls.Add(pbLevel); - flpVoiceBottom.Controls.Add(lblVadThreshold); - flpVoiceBottom.Controls.Add(trkVadThreshold); - flpVoiceBottom.Controls.Add(lblMicGain); - flpVoiceBottom.Controls.Add(trkMicGain); + flpVoiceTop.Controls.Add(lblLevel); + flpVoiceTop.Controls.Add(pbLevel); pnlVoice.Dock = DockStyle.Bottom; - pnlVoice.Height = 68; + pnlVoice.Height = 38; pnlVoice.BorderStyle = BorderStyle.FixedSingle; pnlVoice.Padding = new Padding(0); - pnlVoice.Controls.Add(flpVoiceBottom); // Fill — added first - pnlVoice.Controls.Add(flpVoiceTop); // Top — added last + pnlVoice.Controls.Add(flpVoiceTop); // ── Toolbar ─────────────────────────────────────────────────────────── tsbJoinVoice.Text = "Join Voice"; diff --git a/clients/windows/VoiceCat.App/Forms/MainForm.cs b/clients/windows/VoiceCat.App/Forms/MainForm.cs index 00d2883..e0e5c70 100644 --- a/clients/windows/VoiceCat.App/Forms/MainForm.cs +++ b/clients/windows/VoiceCat.App/Forms/MainForm.cs @@ -83,14 +83,6 @@ public partial class MainForm : Form // Voice controls chkMute.CheckedChanged += (_, _) => ApplySelfMute(); chkDeafen.CheckedChanged += (_, _) => ApplySelfMute(); - radioVad.CheckedChanged += RadioVad_CheckedChanged; - radioPtt.CheckedChanged += RadioPtt_CheckedChanged; - radioAlwaysOn.CheckedChanged += RadioAlwaysOn_CheckedChanged; - trkVadThreshold.Scroll += TrkVadThreshold_Scroll; - trkMicGain.Scroll += TrkMicGain_Scroll; - btnChangePtt.Click += BtnChangePtt_Click; - btnRefreshDevices.Click += (_, _) => LoadInputDevices(); - cboInputDevice.SelectedIndexChanged += CboInputDevice_SelectedIndexChanged; // PTT and global hotkeys (focus-scoped — work only while this form has focus) KeyDown += MainForm_KeyDown; @@ -105,29 +97,9 @@ public partial class MainForm : Form BootstrapFromServer(); } - /// - /// Restore the saved transmission mode / VAD sensitivity / mic gain / PTT key into the UI so a - /// relaunch keeps the user's input settings instead of resetting to the designer defaults. The - /// values are pushed into the core when the mic stream starts (). - /// Setting the radio fires CheckedChanged, which only adjusts visibility while the mic is idle. - /// - private void ApplyPersistedVoiceSettings() - { + private void ApplyPersistedVoiceSettings() => _pttKey = (Keys)_voiceSettings.PttKey; - trkVadThreshold.Value = Math.Clamp(_voiceSettings.VadThresholdSlider, - trkVadThreshold.Minimum, trkVadThreshold.Maximum); - trkMicGain.Value = Math.Clamp(_voiceSettings.MicGain, - trkMicGain.Minimum, trkMicGain.Maximum); - - switch ((VcInputMode)_voiceSettings.InputMode) - { - case VcInputMode.PushToTalk: radioPtt.Checked = true; break; - case VcInputMode.AlwaysOn: radioAlwaysOn.Checked = true; break; - default: radioVad.Checked = true; break; - } - } - // ── Startup ────────────────────────────────────────────────────────────── private void BootstrapFromServer() @@ -157,7 +129,6 @@ public partial class MainForm : Form base.OnLoad(e); splitMain.SplitterDistance = Math.Min(220, splitMain.Width - 304); splitLeft.SplitterDistance = Math.Min(260, splitLeft.Height - 84); - LoadInputDevices(); } // ── Menu / context menu builders ───────────────────────────────────────── @@ -195,6 +166,14 @@ public partial class MainForm : Form // Settings menu — always visible var settingsMenu = new ToolStripMenuItem("&Settings"); + var miAudio = new ToolStripMenuItem("&Audio..."); + miAudio.Click += (_, _) => + { + using var dlg = new AudioSettingsForm(_client, _voiceSettings, _micStreamId); + dlg.ShowDialog(this); + _pttKey = (Keys)_voiceSettings.PttKey; + }; + settingsMenu.DropDownItems.Add(miAudio); var miNotifications = new ToolStripMenuItem("&Notifications..."); miNotifications.Click += (_, _) => { @@ -642,38 +621,6 @@ public partial class MainForm : Form lblStatus.Text = $"Connected as {_nickname}{suffix} — {chanName} ({count} user{(count == 1 ? "" : "s")})"; } - // ── Device management ───────────────────────────────────────────────────── - - private void LoadInputDevices() - { - var devices = _client.ListDevices(VcDeviceKind.Input); - DeviceInfo? prevDevice = cboInputDevice.SelectedItem as DeviceInfo; - - cboInputDevice.Items.Clear(); - foreach (var d in devices) cboInputDevice.Items.Add(d); - - if (prevDevice is not null) - { - for (int i = 0; i < cboInputDevice.Items.Count; i++) - { - if (cboInputDevice.Items[i] is DeviceInfo d && d.Id == prevDevice.Id) - { - cboInputDevice.SelectedIndex = i; - return; - } - } - } - for (int i = 0; i < cboInputDevice.Items.Count; i++) - { - if (cboInputDevice.Items[i] is DeviceInfo d && d.IsDefault) - { - cboInputDevice.SelectedIndex = i; - return; - } - } - if (cboInputDevice.Items.Count > 0) cboInputDevice.SelectedIndex = 0; - } - // ── Voice controls ──────────────────────────────────────────────────────── private void BtnMicToggle_Click(object? sender, EventArgs e) @@ -684,11 +631,13 @@ public partial class MainForm : Form if (result == VcResult.Ok) { _micStreamId = streamId; - if (cboInputDevice.SelectedItem is DeviceInfo { IsDefault: false } dev) - _client.SetInputDevice(streamId, dev.Id); - _client.SetInputMode(CurrentInputMode()); - if (radioVad.Checked) _client.SetVadThreshold(VadThresholdFromSlider()); - _client.SetInputGain(trkMicGain.Value / 100f); + var mode = (VcInputMode)_voiceSettings.InputMode; + if (_voiceSettings.InputDeviceId is string devId) + _client.SetInputDevice(streamId, devId); + _client.SetInputMode(mode); + if (mode == VcInputMode.VoiceActivation) + _client.SetVadThreshold(VadThresholdFromSettings()); + _client.SetInputGain(_voiceSettings.MicGain / 100f); SetVoiceJoinedState(true); AddActivity("Joined voice — microphone active"); _feedback.PlaySound(SoundEvent.VoiceOn); @@ -716,9 +665,6 @@ public partial class MainForm : Form _miJoinVoice.Text = joined ? "Leave &Voice" : "&Join Voice"; chkMute.Enabled = joined; chkDeafen.Enabled = joined; - radioVad.Enabled = joined; - radioPtt.Enabled = joined; - radioAlwaysOn.Enabled = joined; } private void ApplySelfMute() => @@ -800,104 +746,17 @@ public partial class MainForm : Form private void TrkOutputVolume_Scroll(object? sender, EventArgs e) => _client.SetOutputVolume(trkOutputVolume.Value / 100f); - private void RadioVad_CheckedChanged(object? sender, EventArgs e) - { - if (!radioVad.Checked) return; - lblPttKey.Visible = false; - btnChangePtt.Visible = false; - lblVadThreshold.Visible = true; - trkVadThreshold.Visible = true; - SaveInputMode(VcInputMode.VoiceActivation); - if (_micStreamId != 0) - { - _client.SetInputMode(VcInputMode.VoiceActivation); - _client.SetVadThreshold(VadThresholdFromSlider()); - } - } - - private void RadioPtt_CheckedChanged(object? sender, EventArgs e) - { - if (!radioPtt.Checked) return; - lblPttKey.Text = $"({_pttKey})"; - lblPttKey.Visible = true; - btnChangePtt.Visible = true; - lblVadThreshold.Visible = false; - trkVadThreshold.Visible = false; - SaveInputMode(VcInputMode.PushToTalk); - if (_micStreamId != 0) - { - _client.SetInputMode(VcInputMode.PushToTalk); - _client.SetPushToTalk(false); - } - } - - private void RadioAlwaysOn_CheckedChanged(object? sender, EventArgs e) - { - if (!radioAlwaysOn.Checked) return; - lblPttKey.Visible = false; - btnChangePtt.Visible = false; - lblVadThreshold.Visible = false; - trkVadThreshold.Visible = false; - SaveInputMode(VcInputMode.AlwaysOn); - if (_micStreamId != 0) _client.SetInputMode(VcInputMode.AlwaysOn); - } - - private void TrkVadThreshold_Scroll(object? sender, EventArgs e) - { - _voiceSettings.VadThresholdSlider = trkVadThreshold.Value; - _voiceSettings.Save(); - if (_micStreamId != 0 && radioVad.Checked) - _client.SetVadThreshold(VadThresholdFromSlider()); - } - - private void TrkMicGain_Scroll(object? sender, EventArgs e) - { - _voiceSettings.MicGain = trkMicGain.Value; - _voiceSettings.Save(); - if (_micStreamId != 0) _client.SetInputGain(trkMicGain.Value / 100f); - } - - private void SaveInputMode(VcInputMode mode) - { - _voiceSettings.InputMode = (int)mode; - _voiceSettings.Save(); - } - - private float VadThresholdFromSlider() => - 0.1f * (1f - (trkVadThreshold.Value - 1f) / 99f); - - private VcInputMode CurrentInputMode() => - radioPtt.Checked ? VcInputMode.PushToTalk : - radioAlwaysOn.Checked ? VcInputMode.AlwaysOn : - VcInputMode.VoiceActivation; - - private void BtnChangePtt_Click(object? sender, EventArgs e) - { - using var dlg = new PttKeyCaptureDialog(_pttKey); - if (dlg.ShowDialog(this) == DialogResult.OK) - { - _pttKey = dlg.CapturedKey; - lblPttKey.Text = $"({_pttKey})"; - _voiceSettings.PttKey = (int)_pttKey; - _voiceSettings.Save(); - } - } - - private void CboInputDevice_SelectedIndexChanged(object? sender, EventArgs e) - { - if (_micStreamId == 0) return; - string? deviceId = (cboInputDevice.SelectedItem as DeviceInfo)?.Id; - _client.SetInputDevice(_micStreamId, deviceId); - } + private float VadThresholdFromSettings() => + 0.1f * (1f - (_voiceSettings.VadThresholdSlider - 1f) / 99f); // ── PTT key handling (focus-scoped) ─────────────────────────────────────── private void MainForm_KeyDown(object? sender, KeyEventArgs e) { - if (!radioPtt.Checked || e.KeyCode != _pttKey || _micStreamId == 0) return; + if ((VcInputMode)_voiceSettings.InputMode != VcInputMode.PushToTalk) return; + if (e.KeyCode != _pttKey || _micStreamId == 0) return; if (ActiveControl is TextBox or RichTextBox) return; _client.SetPushToTalk(true); - lblPttKey.Text = $"({_pttKey} ▶)"; if (!_pttEngaged) // first key-down only, not auto-repeat { _pttEngaged = true; @@ -935,10 +794,10 @@ public partial class MainForm : Form private void MainForm_KeyUp(object? sender, KeyEventArgs e) { - if (!radioPtt.Checked || e.KeyCode != _pttKey || _micStreamId == 0) return; + if ((VcInputMode)_voiceSettings.InputMode != VcInputMode.PushToTalk) return; + if (e.KeyCode != _pttKey || _micStreamId == 0) return; _client.SetPushToTalk(false); _pttEngaged = false; - lblPttKey.Text = $"({_pttKey})"; e.Handled = true; } diff --git a/clients/windows/VoiceCat.App/Models/VoiceSettings.cs b/clients/windows/VoiceCat.App/Models/VoiceSettings.cs index 6ff639d..e95a0e8 100644 --- a/clients/windows/VoiceCat.App/Models/VoiceSettings.cs +++ b/clients/windows/VoiceCat.App/Models/VoiceSettings.cs @@ -24,6 +24,9 @@ public sealed class VoiceSettings /// Push-to-talk key, stored as the integer value of System.Windows.Forms.Keys. public int PttKey { get; set; } = (int)Keys.F8; + /// Saved device ID from the last session; null means use the system default. + public string? InputDeviceId { get; set; } = null; + private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; private static string AppDataDir => Path.Combine(