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); }