feat(clients): wire RNNoise mic noise reduction into Windows, macOS, and iOS
Expose the existing send-side vc_set_input_noise_reduction C ABI (MIC-only, mono, LOCAL — denoises captured mic PCM before input gain and VAD/PTT gate) as a persisted global toggle in each client's audio settings, applied live and re-applied on Join Voice. Mirrors the existing mic-gain wiring pattern. - Shared Swift (VoiceCatCore): add setInputNoiseReduction(_:) wrapper - Windows: P/Invoke + SetInputNoiseReduction wrapper, MicNoiseReduction in VoiceSettings, new checkbox in AudioSettingsForm (layout shifted +28px), apply on Join Voice; also fix stale 'planned - currently passthrough' label on the receive-side per-user NR checkbox (RNNoise now backs it) - macOS: inputNoiseReduction state + UserDefaults in MainWindowController, NR checkbox + nrChanged action in SettingsWindowController - iOS: inputNoiseReduction in VoiceState + setter + restore in SessionState, NR Toggle in SettingsView Voice section Aux/screen are out of scope by design (core's NR guards kind == MIC). Apple builds require a rebuilt VoiceCatCore.xcframework with VOICECAT_HAS_NS.
This commit is contained in:
@@ -417,6 +417,15 @@ public final class VoiceCatClient {
|
|||||||
VoiceCatResult(vc_set_input_gain(handle, gain < 0 ? 0 : gain))
|
VoiceCatResult(vc_set_input_gain(handle, gain < 0 ? 0 : gain))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send-side microphone noise suppression (RNNoise). Denoises captured MIC PCM before the
|
||||||
|
/// input gain and VAD/PTT gate, so everyone hears the cleaned signal (one pass for all
|
||||||
|
/// listeners). MIC stream only, mono only; always LOCAL — no protocol traffic. Independent
|
||||||
|
/// of the per-listener receive-side NR in `setRemoteStream` (docs/voice.md §10).
|
||||||
|
@discardableResult
|
||||||
|
public func setInputNoiseReduction(_ enable: Bool) -> VoiceCatResult {
|
||||||
|
VoiceCatResult(vc_set_input_noise_reduction(handle, enable ? 1 : 0))
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - AVAudioSession interruption hooks (iOS)
|
// MARK: - AVAudioSession interruption hooks (iOS)
|
||||||
|
|
||||||
/// Pause miniaudio device I/O. Call when AVAudioSession interruption begins.
|
/// Pause miniaudio device I/O. Call when AVAudioSession interruption begins.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ struct VoiceState {
|
|||||||
var inputMode: VoiceCatInputMode = .voiceActivation
|
var inputMode: VoiceCatInputMode = .voiceActivation
|
||||||
var vadThreshold: Float = 0.025
|
var vadThreshold: Float = 0.025
|
||||||
var inputGain: Float = 1.0
|
var inputGain: Float = 1.0
|
||||||
|
var inputNoiseReduction: Bool = false
|
||||||
var level: Float = 0.0
|
var level: Float = 0.0
|
||||||
var currentDeviceId: String?
|
var currentDeviceId: String?
|
||||||
var localStreamId: UInt32 = 0
|
var localStreamId: UInt32 = 0
|
||||||
@@ -353,12 +354,19 @@ final class SessionState {
|
|||||||
UserDefaults.standard.set(gain, forKey: DefaultsKey.inputGain)
|
UserDefaults.standard.set(gain, forKey: DefaultsKey.inputGain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setInputNoiseReduction(_ on: Bool) {
|
||||||
|
client.setInputNoiseReduction(on)
|
||||||
|
voiceState.inputNoiseReduction = on
|
||||||
|
UserDefaults.standard.set(on, forKey: DefaultsKey.inputNoiseReduction)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Persisted input settings
|
// MARK: - Persisted input settings
|
||||||
|
|
||||||
private enum DefaultsKey {
|
private enum DefaultsKey {
|
||||||
static let inputMode = "voice.inputMode"
|
static let inputMode = "voice.inputMode"
|
||||||
static let vadThreshold = "voice.vadThreshold"
|
static let vadThreshold = "voice.vadThreshold"
|
||||||
static let inputGain = "voice.inputGain"
|
static let inputGain = "voice.inputGain"
|
||||||
|
static let inputNoiseReduction = "voice.inputNoiseReduction"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restore the saved input mode / VAD threshold / mic gain and push them into the core so a
|
/// Restore the saved input mode / VAD threshold / mic gain and push them into the core so a
|
||||||
@@ -375,9 +383,13 @@ final class SessionState {
|
|||||||
if d.object(forKey: DefaultsKey.inputGain) != nil {
|
if d.object(forKey: DefaultsKey.inputGain) != nil {
|
||||||
voiceState.inputGain = d.float(forKey: DefaultsKey.inputGain)
|
voiceState.inputGain = d.float(forKey: DefaultsKey.inputGain)
|
||||||
}
|
}
|
||||||
|
if d.object(forKey: DefaultsKey.inputNoiseReduction) != nil {
|
||||||
|
voiceState.inputNoiseReduction = d.bool(forKey: DefaultsKey.inputNoiseReduction)
|
||||||
|
}
|
||||||
client.setInputMode(voiceState.inputMode)
|
client.setInputMode(voiceState.inputMode)
|
||||||
client.setVadThreshold(voiceState.vadThreshold)
|
client.setVadThreshold(voiceState.vadThreshold)
|
||||||
client.setInputGain(voiceState.inputGain)
|
client.setInputGain(voiceState.inputGain)
|
||||||
|
client.setInputNoiseReduction(voiceState.inputNoiseReduction)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var pttEngaged = false
|
private var pttEngaged = false
|
||||||
|
|||||||
@@ -251,6 +251,13 @@ struct SettingsView: View {
|
|||||||
.accessibilityLabel("Microphone volume")
|
.accessibilityLabel("Microphone volume")
|
||||||
.accessibilityValue("\(Int((session.voiceState.inputGain * 100).rounded())) percent")
|
.accessibilityValue("\(Int((session.voiceState.inputGain * 100).rounded())) percent")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toggle("Noise Reduction (RNNoise)", isOn: Binding(
|
||||||
|
get: { session.voiceState.inputNoiseReduction },
|
||||||
|
set: { session.setInputNoiseReduction($0) }
|
||||||
|
))
|
||||||
|
.accessibilityLabel("Microphone noise reduction")
|
||||||
|
.accessibilityHint("Denoises your microphone signal for everyone listening.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
static let inputMode = "voice.inputMode"
|
static let inputMode = "voice.inputMode"
|
||||||
static let vadThreshold = "voice.vadThreshold"
|
static let vadThreshold = "voice.vadThreshold"
|
||||||
static let inputGain = "voice.inputGain"
|
static let inputGain = "voice.inputGain"
|
||||||
|
static let inputNoiseReduction = "voice.inputNoiseReduction"
|
||||||
static let pttKeyCode = "voice.pttKeyCode"
|
static let pttKeyCode = "voice.pttKeyCode"
|
||||||
static let auxEnabled = "voice.auxEnabled"
|
static let auxEnabled = "voice.auxEnabled"
|
||||||
static let auxDeviceUID = "voice.auxDeviceUID"
|
static let auxDeviceUID = "voice.auxDeviceUID"
|
||||||
@@ -76,6 +77,9 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
internal var inputGain: Float = 1.0 {
|
internal var inputGain: Float = 1.0 {
|
||||||
didSet { UserDefaults.standard.set(inputGain, forKey: AudioDefaults.inputGain) }
|
didSet { UserDefaults.standard.set(inputGain, forKey: AudioDefaults.inputGain) }
|
||||||
}
|
}
|
||||||
|
internal var inputNoiseReduction: Bool = false {
|
||||||
|
didSet { UserDefaults.standard.set(inputNoiseReduction, forKey: AudioDefaults.inputNoiseReduction) }
|
||||||
|
}
|
||||||
internal var selectedInputDeviceId: String?
|
internal var selectedInputDeviceId: String?
|
||||||
|
|
||||||
// Aux outgoing stream: a second hardware input device the client captures itself and feeds to
|
// Aux outgoing stream: a second hardware input device the client captures itself and feeds to
|
||||||
@@ -156,6 +160,9 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
if d.object(forKey: AudioDefaults.inputGain) != nil {
|
if d.object(forKey: AudioDefaults.inputGain) != nil {
|
||||||
inputGain = d.float(forKey: AudioDefaults.inputGain)
|
inputGain = d.float(forKey: AudioDefaults.inputGain)
|
||||||
}
|
}
|
||||||
|
if d.object(forKey: AudioDefaults.inputNoiseReduction) != nil {
|
||||||
|
inputNoiseReduction = d.bool(forKey: AudioDefaults.inputNoiseReduction)
|
||||||
|
}
|
||||||
if d.object(forKey: AudioDefaults.pttKeyCode) != nil {
|
if d.object(forKey: AudioDefaults.pttKeyCode) != nil {
|
||||||
pttKeyCode = UInt16(d.integer(forKey: AudioDefaults.pttKeyCode))
|
pttKeyCode = UInt16(d.integer(forKey: AudioDefaults.pttKeyCode))
|
||||||
}
|
}
|
||||||
@@ -779,6 +786,7 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
client.setVadThreshold(vadThresholdValue)
|
client.setVadThreshold(vadThresholdValue)
|
||||||
}
|
}
|
||||||
client.setInputGain(inputGain)
|
client.setInputGain(inputGain)
|
||||||
|
client.setInputNoiseReduction(inputNoiseReduction)
|
||||||
setVoiceJoinedState(true)
|
setVoiceJoinedState(true)
|
||||||
addActivity("Joined voice — microphone active")
|
addActivity("Joined voice — microphone active")
|
||||||
EventFeedback.shared.play(.voiceOn)
|
EventFeedback.shared.play(.voiceOn)
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
}()
|
}()
|
||||||
private let inputGainValueLabel = NSTextField(labelWithString: "100%")
|
private let inputGainValueLabel = NSTextField(labelWithString: "100%")
|
||||||
|
|
||||||
|
// Send-side mic noise reduction (RNNoise). MIC-only. Persisted via
|
||||||
|
// MainWindowController.inputNoiseReduction.
|
||||||
|
private let nrCheckbox = NSButton(checkboxWithTitle: "Noise reduction (RNNoise)",
|
||||||
|
target: nil, action: nil)
|
||||||
|
|
||||||
// Aux input stream: a second outgoing stream from another hardware input device (e.g. line-in
|
// Aux input stream: a second outgoing stream from another hardware input device (e.g. line-in
|
||||||
// / aux), captured client-side. Device + volume only — aux is always-on. Persisted via
|
// / aux), captured client-side. Device + volume only — aux is always-on. Persisted via
|
||||||
// MainWindowController.auxEnabled / auxDeviceUID / auxGain.
|
// MainWindowController.auxEnabled / auxDeviceUID / auxGain.
|
||||||
@@ -168,6 +173,11 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
inputGainSlider.setAccessibilityHelp("Boost a quiet microphone. 100% is unity.")
|
inputGainSlider.setAccessibilityHelp("Boost a quiet microphone. 100% is unity.")
|
||||||
inputGainValueLabel.setAccessibilityLabel("Microphone volume value")
|
inputGainValueLabel.setAccessibilityLabel("Microphone volume value")
|
||||||
|
|
||||||
|
nrCheckbox.target = self
|
||||||
|
nrCheckbox.action = #selector(nrChanged)
|
||||||
|
nrCheckbox.setAccessibilityLabel("Microphone noise reduction")
|
||||||
|
nrCheckbox.setAccessibilityHelp("RNNoise denoising of your microphone. Cleans your signal for everyone.")
|
||||||
|
|
||||||
let inputModeRow = NSStackView(views: [inputModeLabel, inputModeControl])
|
let inputModeRow = NSStackView(views: [inputModeLabel, inputModeControl])
|
||||||
inputModeRow.orientation = .horizontal
|
inputModeRow.orientation = .horizontal
|
||||||
inputModeRow.spacing = 8
|
inputModeRow.spacing = 8
|
||||||
@@ -249,7 +259,7 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
volumeRow.orientation = .horizontal
|
volumeRow.orientation = .horizontal
|
||||||
volumeRow.spacing = 8
|
volumeRow.spacing = 8
|
||||||
|
|
||||||
let stack = NSStackView(views: [inputModeRow, vadRow, inputGainRow, pttRow, deviceRow,
|
let stack = NSStackView(views: [inputModeRow, vadRow, inputGainRow, nrCheckbox, pttRow, deviceRow,
|
||||||
levelRow, auxHeader, auxCheckbox, auxDeviceRow, auxGainRow,
|
levelRow, auxHeader, auxCheckbox, auxDeviceRow, auxGainRow,
|
||||||
notificationsHeader, soundsCheckbox, volumeRow,
|
notificationsHeader, soundsCheckbox, volumeRow,
|
||||||
speechCheckbox, selfTalkCheckbox, pttSoundCheckbox])
|
speechCheckbox, selfTalkCheckbox, pttSoundCheckbox])
|
||||||
@@ -319,6 +329,7 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
|
|
||||||
inputGainSlider.doubleValue = Double(mc.inputGain * 100)
|
inputGainSlider.doubleValue = Double(mc.inputGain * 100)
|
||||||
updateInputGainLabel()
|
updateInputGainLabel()
|
||||||
|
nrCheckbox.state = mc.inputNoiseReduction ? .on : .off
|
||||||
|
|
||||||
auxCheckbox.state = mc.auxEnabled ? .on : .off
|
auxCheckbox.state = mc.auxEnabled ? .on : .off
|
||||||
auxGainSlider.doubleValue = Double(mc.auxGain * 100)
|
auxGainSlider.doubleValue = Double(mc.auxGain * 100)
|
||||||
@@ -371,6 +382,14 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func nrChanged() {
|
||||||
|
let on = nrCheckbox.state == .on
|
||||||
|
mainController?.inputNoiseReduction = on
|
||||||
|
if let mc = mainController, mc.micStreamId != 0 {
|
||||||
|
client.setInputNoiseReduction(on)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateInputGainLabel() {
|
private func updateInputGainLabel() {
|
||||||
let pct = Int(inputGainSlider.doubleValue.rounded())
|
let pct = Int(inputGainSlider.doubleValue.rounded())
|
||||||
inputGainValueLabel.stringValue = "\(pct)%"
|
inputGainValueLabel.stringValue = "\(pct)%"
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public sealed class AudioSettingsForm : Form
|
|||||||
private readonly VcInputMode _origMode;
|
private readonly VcInputMode _origMode;
|
||||||
private readonly int _origVadSlider;
|
private readonly int _origVadSlider;
|
||||||
private readonly int _origMicGain;
|
private readonly int _origMicGain;
|
||||||
|
private readonly bool _origMicNoiseReduction;
|
||||||
private readonly Keys _origPttKey;
|
private readonly Keys _origPttKey;
|
||||||
private readonly bool _origAuxEnabled;
|
private readonly bool _origAuxEnabled;
|
||||||
private readonly string? _origAuxDeviceId;
|
private readonly string? _origAuxDeviceId;
|
||||||
@@ -42,6 +43,7 @@ public sealed class AudioSettingsForm : Form
|
|||||||
private readonly Label _lblSensitivity;
|
private readonly Label _lblSensitivity;
|
||||||
private readonly TrackBar _trkVad;
|
private readonly TrackBar _trkVad;
|
||||||
private readonly TrackBar _trkGain;
|
private readonly TrackBar _trkGain;
|
||||||
|
private readonly CheckBox _chkNoiseReduction;
|
||||||
private readonly CheckBox _chkAux;
|
private readonly CheckBox _chkAux;
|
||||||
private readonly Label _lblAuxDevice;
|
private readonly Label _lblAuxDevice;
|
||||||
private readonly ComboBox _cboAuxDevice;
|
private readonly ComboBox _cboAuxDevice;
|
||||||
@@ -67,6 +69,7 @@ public sealed class AudioSettingsForm : Form
|
|||||||
_origMode = (VcInputMode)settings.InputMode;
|
_origMode = (VcInputMode)settings.InputMode;
|
||||||
_origVadSlider = settings.VadThresholdSlider;
|
_origVadSlider = settings.VadThresholdSlider;
|
||||||
_origMicGain = settings.MicGain;
|
_origMicGain = settings.MicGain;
|
||||||
|
_origMicNoiseReduction = settings.MicNoiseReduction;
|
||||||
_origPttKey = _pttKey;
|
_origPttKey = _pttKey;
|
||||||
_origAuxEnabled = settings.AuxEnabled;
|
_origAuxEnabled = settings.AuxEnabled;
|
||||||
_origAuxDeviceId = settings.AuxDeviceId;
|
_origAuxDeviceId = settings.AuxDeviceId;
|
||||||
@@ -78,7 +81,7 @@ public sealed class AudioSettingsForm : Form
|
|||||||
MinimizeBox = false;
|
MinimizeBox = false;
|
||||||
StartPosition = FormStartPosition.CenterParent;
|
StartPosition = FormStartPosition.CenterParent;
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(420, 555);
|
ClientSize = new Size(420, 583);
|
||||||
|
|
||||||
// ── Device row ────────────────────────────────────────────────────────
|
// ── Device row ────────────────────────────────────────────────────────
|
||||||
var lblDevice = new Label
|
var lblDevice = new Label
|
||||||
@@ -199,6 +202,21 @@ public sealed class AudioSettingsForm : Form
|
|||||||
"Boost a quiet microphone. 100 is unity gain; range 0–300 percent.";
|
"Boost a quiet microphone. 100 is unity gain; range 0–300 percent.";
|
||||||
_trkGain.Scroll += TrkGain_Scroll;
|
_trkGain.Scroll += TrkGain_Scroll;
|
||||||
|
|
||||||
|
// ── Mic noise reduction ───────────────────────────────────────────────
|
||||||
|
// Send-side RNNoise denoise of the mic stream. MIC-only (the core's NR runs before
|
||||||
|
// the input gain and VAD/PTT gate; aux/screen are excluded). One pass for all listeners.
|
||||||
|
_chkNoiseReduction = new CheckBox
|
||||||
|
{
|
||||||
|
Text = "Noise &reduction (RNNoise)",
|
||||||
|
Location = new Point(12, 340),
|
||||||
|
AutoSize = true,
|
||||||
|
Checked = settings.MicNoiseReduction,
|
||||||
|
AccessibleName = "Microphone noise reduction",
|
||||||
|
AccessibleDescription =
|
||||||
|
"RNNoise denoising of your microphone. Cleans your signal for everyone listening.",
|
||||||
|
};
|
||||||
|
_chkNoiseReduction.CheckedChanged += ChkNoiseReduction_CheckedChanged;
|
||||||
|
|
||||||
// ── Aux input stream ──────────────────────────────────────────────────
|
// ── Aux input stream ──────────────────────────────────────────────────
|
||||||
// A second outgoing stream from another hardware input device (e.g. line-in / aux),
|
// A second outgoing stream from another hardware input device (e.g. line-in / aux),
|
||||||
// captured client-side and fed to the core. Device + volume only — aux is always-on
|
// captured client-side and fed to the core. Device + volume only — aux is always-on
|
||||||
@@ -206,7 +224,7 @@ public sealed class AudioSettingsForm : Form
|
|||||||
_chkAux = new CheckBox
|
_chkAux = new CheckBox
|
||||||
{
|
{
|
||||||
Text = "&Aux stream (second input device)",
|
Text = "&Aux stream (second input device)",
|
||||||
Location = new Point(12, 348),
|
Location = new Point(12, 376),
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
Checked = settings.AuxEnabled,
|
Checked = settings.AuxEnabled,
|
||||||
AccessibleName = "Enable aux input stream",
|
AccessibleName = "Enable aux input stream",
|
||||||
@@ -218,12 +236,12 @@ public sealed class AudioSettingsForm : Form
|
|||||||
_lblAuxDevice = new Label
|
_lblAuxDevice = new Label
|
||||||
{
|
{
|
||||||
Text = "Aux d&evice:",
|
Text = "Aux d&evice:",
|
||||||
Location = new Point(12, 378),
|
Location = new Point(12, 406),
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
};
|
};
|
||||||
_cboAuxDevice = new ComboBox
|
_cboAuxDevice = new ComboBox
|
||||||
{
|
{
|
||||||
Location = new Point(12, 398),
|
Location = new Point(12, 426),
|
||||||
Width = 300,
|
Width = 300,
|
||||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||||
DisplayMember = "Name",
|
DisplayMember = "Name",
|
||||||
@@ -236,7 +254,7 @@ public sealed class AudioSettingsForm : Form
|
|||||||
_btnAuxRefresh = new Button
|
_btnAuxRefresh = new Button
|
||||||
{
|
{
|
||||||
Text = "Re&fresh",
|
Text = "Re&fresh",
|
||||||
Location = new Point(320, 396),
|
Location = new Point(320, 424),
|
||||||
Size = new Size(80, 26),
|
Size = new Size(80, 26),
|
||||||
};
|
};
|
||||||
_btnAuxRefresh.Click += (_, _) => LoadAuxDevices();
|
_btnAuxRefresh.Click += (_, _) => LoadAuxDevices();
|
||||||
@@ -244,12 +262,12 @@ public sealed class AudioSettingsForm : Form
|
|||||||
_lblAuxGain = new Label
|
_lblAuxGain = new Label
|
||||||
{
|
{
|
||||||
Text = "Aux vo&lume:",
|
Text = "Aux vo&lume:",
|
||||||
Location = new Point(12, 434),
|
Location = new Point(12, 462),
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
};
|
};
|
||||||
_trkAuxGain = new TrackBar
|
_trkAuxGain = new TrackBar
|
||||||
{
|
{
|
||||||
Location = new Point(12, 454),
|
Location = new Point(12, 482),
|
||||||
Size = new Size(200, 45),
|
Size = new Size(200, 45),
|
||||||
Minimum = 0,
|
Minimum = 0,
|
||||||
Maximum = 300,
|
Maximum = 300,
|
||||||
@@ -268,14 +286,14 @@ public sealed class AudioSettingsForm : Form
|
|||||||
{
|
{
|
||||||
Text = "&OK",
|
Text = "&OK",
|
||||||
DialogResult = DialogResult.OK,
|
DialogResult = DialogResult.OK,
|
||||||
Location = new Point(228, 516),
|
Location = new Point(228, 544),
|
||||||
Size = new Size(80, 27),
|
Size = new Size(80, 27),
|
||||||
};
|
};
|
||||||
var btnCancel = new Button
|
var btnCancel = new Button
|
||||||
{
|
{
|
||||||
Text = "&Cancel",
|
Text = "&Cancel",
|
||||||
DialogResult = DialogResult.Cancel,
|
DialogResult = DialogResult.Cancel,
|
||||||
Location = new Point(316, 516),
|
Location = new Point(316, 544),
|
||||||
Size = new Size(80, 27),
|
Size = new Size(80, 27),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -292,7 +310,7 @@ public sealed class AudioSettingsForm : Form
|
|||||||
lblDevice, _cboDevice, _btnRefresh,
|
lblDevice, _cboDevice, _btnRefresh,
|
||||||
lblMode, _radioVad, _lblSensitivity, _trkVad,
|
lblMode, _radioVad, _lblSensitivity, _trkVad,
|
||||||
_radioPtt, _lblPttKey, _btnChangePtt, _radioAlwaysOn,
|
_radioPtt, _lblPttKey, _btnChangePtt, _radioAlwaysOn,
|
||||||
lblGain, _trkGain,
|
lblGain, _trkGain, _chkNoiseReduction,
|
||||||
_chkAux, _lblAuxDevice, _cboAuxDevice, _btnAuxRefresh, _lblAuxGain, _trkAuxGain,
|
_chkAux, _lblAuxDevice, _cboAuxDevice, _btnAuxRefresh, _lblAuxGain, _trkAuxGain,
|
||||||
btnOk, btnCancel,
|
btnOk, btnCancel,
|
||||||
]);
|
]);
|
||||||
@@ -430,6 +448,13 @@ public sealed class AudioSettingsForm : Form
|
|||||||
_client.SetInputGain(_trkGain.Value / 100f);
|
_client.SetInputGain(_trkGain.Value / 100f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ChkNoiseReduction_CheckedChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_settings.MicNoiseReduction = _chkNoiseReduction.Checked;
|
||||||
|
if (_micStreamId != 0)
|
||||||
|
_client.SetInputNoiseReduction(_chkNoiseReduction.Checked);
|
||||||
|
}
|
||||||
|
|
||||||
private void BtnChangePtt_Click(object? sender, EventArgs e)
|
private void BtnChangePtt_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using var dlg = new PttKeyCaptureDialog(_pttKey);
|
using var dlg = new PttKeyCaptureDialog(_pttKey);
|
||||||
@@ -450,6 +475,7 @@ public sealed class AudioSettingsForm : Form
|
|||||||
_settings.InputMode = (int)_origMode;
|
_settings.InputMode = (int)_origMode;
|
||||||
_settings.VadThresholdSlider = _origVadSlider;
|
_settings.VadThresholdSlider = _origVadSlider;
|
||||||
_settings.MicGain = _origMicGain;
|
_settings.MicGain = _origMicGain;
|
||||||
|
_settings.MicNoiseReduction = _origMicNoiseReduction;
|
||||||
_settings.PttKey = (int)_origPttKey;
|
_settings.PttKey = (int)_origPttKey;
|
||||||
|
|
||||||
if (_micStreamId != 0)
|
if (_micStreamId != 0)
|
||||||
@@ -459,6 +485,7 @@ public sealed class AudioSettingsForm : Form
|
|||||||
if (_origMode == VcInputMode.VoiceActivation)
|
if (_origMode == VcInputMode.VoiceActivation)
|
||||||
_client.SetVadThreshold(0.1f * (1f - (_origVadSlider - 1f) / 99f));
|
_client.SetVadThreshold(0.1f * (1f - (_origVadSlider - 1f) / 99f));
|
||||||
_client.SetInputGain(_origMicGain / 100f);
|
_client.SetInputGain(_origMicGain / 100f);
|
||||||
|
_client.SetInputNoiseReduction(_origMicNoiseReduction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aux: restore originals to settings and re-apply live (order: device + gain first so a
|
// Aux: restore originals to settings and re-apply live (order: device + gain first so a
|
||||||
|
|||||||
@@ -650,6 +650,7 @@ public partial class MainForm : Form
|
|||||||
if (mode == VcInputMode.VoiceActivation)
|
if (mode == VcInputMode.VoiceActivation)
|
||||||
_client.SetVadThreshold(VadThresholdFromSettings());
|
_client.SetVadThreshold(VadThresholdFromSettings());
|
||||||
_client.SetInputGain(_voiceSettings.MicGain / 100f);
|
_client.SetInputGain(_voiceSettings.MicGain / 100f);
|
||||||
|
_client.SetInputNoiseReduction(_voiceSettings.MicNoiseReduction);
|
||||||
SetVoiceJoinedState(true);
|
SetVoiceJoinedState(true);
|
||||||
AddActivity("Joined voice — microphone active");
|
AddActivity("Joined voice — microphone active");
|
||||||
_feedback.PlaySound(SoundEvent.VoiceOn);
|
_feedback.PlaySound(SoundEvent.VoiceOn);
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ public sealed class PerUserTuningDialog : Form
|
|||||||
|
|
||||||
var chkNr = new CheckBox
|
var chkNr = new CheckBox
|
||||||
{
|
{
|
||||||
Text = "&Noise reduction (planned — currently passthrough)",
|
Text = "&Noise reduction",
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
Location = new Point(96, y + 48),
|
Location = new Point(96, y + 48),
|
||||||
Checked = nr0,
|
Checked = nr0,
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ public sealed class VoiceSettings
|
|||||||
/// <summary>Microphone input gain slider position, 0–300 percent (100 = unity).</summary>
|
/// <summary>Microphone input gain slider position, 0–300 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;
|
||||||
|
/// denoises captured mic PCM before input gain and VAD/PTT gate so everyone hears the
|
||||||
|
/// cleaned signal. Independent of the per-listener receive-side NR.</summary>
|
||||||
|
public bool MicNoiseReduction { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>Push-to-talk key, stored as the integer value of System.Windows.Forms.Keys.</summary>
|
/// <summary>Push-to-talk key, stored as the integer value of System.Windows.Forms.Keys.</summary>
|
||||||
public int PttKey { get; set; } = (int)Keys.F8;
|
public int PttKey { get; set; } = (int)Keys.F8;
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ internal static partial class NativeMethods
|
|||||||
[LibraryImport(LibName)]
|
[LibraryImport(LibName)]
|
||||||
internal static partial VcResult vc_set_input_gain(nint c, float gain);
|
internal static partial VcResult vc_set_input_gain(nint c, float gain);
|
||||||
|
|
||||||
|
[LibraryImport(LibName)]
|
||||||
|
internal static partial VcResult vc_set_input_noise_reduction(nint c, int enable);
|
||||||
|
|
||||||
[LibraryImport(LibName)]
|
[LibraryImport(LibName)]
|
||||||
internal static partial VcResult vc_set_remote_stream(nint c, uint userId, uint streamId,
|
internal static partial VcResult vc_set_remote_stream(nint c, uint userId, uint streamId,
|
||||||
float gain, int muted, int noiseReduction);
|
float gain, int muted, int noiseReduction);
|
||||||
|
|||||||
@@ -232,6 +232,13 @@ public sealed class VoiceCatClient : IDisposable
|
|||||||
public VcResult SetInputGain(float gain) =>
|
public VcResult SetInputGain(float gain) =>
|
||||||
NativeMethods.vc_set_input_gain(_handle.DangerousGetHandle(), gain < 0f ? 0f : gain);
|
NativeMethods.vc_set_input_gain(_handle.DangerousGetHandle(), gain < 0f ? 0f : gain);
|
||||||
|
|
||||||
|
/// <summary>Send-side microphone noise suppression (RNNoise). Denoises captured MIC PCM before
|
||||||
|
/// the input gain and VAD/PTT gate, so everyone hears the cleaned signal. MIC stream only,
|
||||||
|
/// mono only; always LOCAL — no protocol traffic. Independent of per-listener receive-side
|
||||||
|
/// NR in <see cref="SetRemoteStream"/>.</summary>
|
||||||
|
public VcResult SetInputNoiseReduction(bool enable) =>
|
||||||
|
NativeMethods.vc_set_input_noise_reduction(_handle.DangerousGetHandle(), enable ? 1 : 0);
|
||||||
|
|
||||||
public VcResult SetRemoteStream(uint userId, uint streamId, float gain, bool muted, bool noiseReduction) =>
|
public VcResult SetRemoteStream(uint userId, uint streamId, float gain, bool muted, bool noiseReduction) =>
|
||||||
NativeMethods.vc_set_remote_stream(_handle.DangerousGetHandle(), userId, streamId, gain,
|
NativeMethods.vc_set_remote_stream(_handle.DangerousGetHandle(), userId, streamId, gain,
|
||||||
muted ? 1 : 0, noiseReduction ? 1 : 0);
|
muted ? 1 : 0, noiseReduction ? 1 : 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user