2026-09-21 00:11:01 +02:00
|
|
|
|
using VoiceCat.Windows;
|
2026-06-17 00:35:16 +02:00
|
|
|
|
|
|
|
|
|
|
namespace VoiceCat.App.Forms;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-06-18 02:06:44 +02:00
|
|
|
|
/// Per-remote-user, per-stream gain/mute/noise-reduction settings (docs/voice.md §1, §10).
|
|
|
|
|
|
/// One row is rendered for each stream the remote user is currently publishing. Each row
|
|
|
|
|
|
/// controls only that stream — so a listener can turn down one user's desktop audio while
|
|
|
|
|
|
/// keeping their mic, and independently noise-reduce a third user. All of these are
|
|
|
|
|
|
/// listener-local: no protocol traffic, no effect on other listeners.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Changes apply in real time as the user adjusts controls (no OK/Cancel round-trip); the
|
|
|
|
|
|
/// Close button just dismisses. State lives in the core and is not persisted across sessions.
|
|
|
|
|
|
/// The dialog is a modal snapshot of the streams active when it was opened — close and
|
|
|
|
|
|
/// reopen to see streams started/stopped in the meantime.
|
2026-06-17 00:35:16 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public sealed class PerUserTuningDialog : Form
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly VoiceCatClient _client;
|
|
|
|
|
|
private readonly uint _userId;
|
|
|
|
|
|
|
2026-06-18 02:06:44 +02:00
|
|
|
|
private readonly List<StreamRow> _rows = new();
|
2026-06-17 00:35:16 +02:00
|
|
|
|
|
|
|
|
|
|
public PerUserTuningDialog(VoiceCatClient client, uint userId, string nickname)
|
|
|
|
|
|
{
|
|
|
|
|
|
_client = client;
|
|
|
|
|
|
_userId = userId;
|
|
|
|
|
|
|
2026-06-18 02:06:44 +02:00
|
|
|
|
AutoScaleMode = AutoScaleMode.Font;
|
|
|
|
|
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
|
|
|
|
|
MaximizeBox = false;
|
|
|
|
|
|
MinimizeBox = false;
|
|
|
|
|
|
StartPosition = FormStartPosition.CenterParent;
|
|
|
|
|
|
Text = $"User settings — {nickname}";
|
|
|
|
|
|
|
|
|
|
|
|
BuildControls(nickname);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BuildControls(string nickname)
|
|
|
|
|
|
{
|
2026-06-17 00:35:16 +02:00
|
|
|
|
var lblTitle = new Label
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = $"Settings for {nickname}",
|
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Font = new Font(Font, FontStyle.Bold),
|
|
|
|
|
|
Location = new Point(12, 12),
|
|
|
|
|
|
TabIndex = 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-18 02:06:44 +02:00
|
|
|
|
var streams = _client.ListUserStreams(_userId);
|
2026-06-17 00:35:16 +02:00
|
|
|
|
|
2026-06-18 02:06:44 +02:00
|
|
|
|
const int rowH = 70; // label + trackbar + mute/nr
|
|
|
|
|
|
const int rowGap = 8;
|
|
|
|
|
|
int y = 44;
|
2026-06-17 00:35:16 +02:00
|
|
|
|
|
2026-06-18 02:06:44 +02:00
|
|
|
|
if (streams.Count == 0)
|
2026-06-17 00:35:16 +02:00
|
|
|
|
{
|
2026-06-18 02:06:44 +02:00
|
|
|
|
var lblEmpty = new Label
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = "No active streams.",
|
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Location = new Point(12, y),
|
|
|
|
|
|
};
|
|
|
|
|
|
Controls.Add(lblTitle);
|
|
|
|
|
|
Controls.Add(lblEmpty);
|
|
|
|
|
|
ClientSize = new Size(420, 44 + lblEmpty.PreferredHeight + 16);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-17 00:35:16 +02:00
|
|
|
|
|
2026-06-18 02:06:44 +02:00
|
|
|
|
Controls.Add(lblTitle);
|
2026-06-17 00:35:16 +02:00
|
|
|
|
|
2026-06-18 02:06:44 +02:00
|
|
|
|
int tabIndex = 1;
|
|
|
|
|
|
foreach (var s in streams)
|
2026-06-17 00:35:16 +02:00
|
|
|
|
{
|
2026-06-18 02:06:44 +02:00
|
|
|
|
var row = CreateRow(s, y, ref tabIndex);
|
|
|
|
|
|
_rows.Add(row);
|
|
|
|
|
|
Controls.AddRange(row.Controls);
|
|
|
|
|
|
y += rowH + rowGap;
|
|
|
|
|
|
}
|
2026-06-17 00:35:16 +02:00
|
|
|
|
|
|
|
|
|
|
var btnClose = new Button
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = "&Close",
|
|
|
|
|
|
DialogResult = DialogResult.OK,
|
2026-06-18 02:06:44 +02:00
|
|
|
|
Location = new Point(336, y + 4),
|
2026-06-17 00:35:16 +02:00
|
|
|
|
Size = new Size(75, 27),
|
2026-06-18 02:06:44 +02:00
|
|
|
|
TabIndex = tabIndex,
|
|
|
|
|
|
};
|
|
|
|
|
|
AcceptButton = btnClose;
|
|
|
|
|
|
|
|
|
|
|
|
Controls.Add(btnClose);
|
|
|
|
|
|
|
|
|
|
|
|
ClientSize = new Size(420, y + 40);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private StreamRow CreateRow(StreamSummary s, int y, ref int tabIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Read back the listener's current state for this stream (defaults if never set).
|
|
|
|
|
|
float gain0 = 1.0f; bool mute0 = false; bool nr0 = false;
|
|
|
|
|
|
var (r, state) = _client.GetRemoteStream(_userId, s.StreamId);
|
|
|
|
|
|
if (r == VcResult.Ok && state is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
gain0 = state.Gain;
|
|
|
|
|
|
mute0 = state.Muted;
|
|
|
|
|
|
nr0 = state.NoiseReduction;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var lblName = new Label
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = StreamLabel(s),
|
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Font = new Font(Font, FontStyle.Bold),
|
|
|
|
|
|
Location = new Point(12, y),
|
|
|
|
|
|
TabIndex = tabIndex++,
|
2026-06-17 00:35:16 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-18 02:06:44 +02:00
|
|
|
|
var trkGain = new TrackBar
|
|
|
|
|
|
{
|
|
|
|
|
|
AccessibleName = $"Gain for {s.Label}",
|
2026-06-24 12:18:10 +02:00
|
|
|
|
AccessibleDescription = "Volume level for this stream. 100 is normal (1.0×); range 0–400 percent.",
|
2026-06-18 02:06:44 +02:00
|
|
|
|
Location = new Point(12, y + 20),
|
|
|
|
|
|
Size = new Size(260, 45),
|
|
|
|
|
|
Minimum = 0,
|
2026-06-24 12:18:10 +02:00
|
|
|
|
Maximum = 400,
|
2026-06-18 02:06:44 +02:00
|
|
|
|
Value = ClampToTrack(gain0),
|
|
|
|
|
|
TickFrequency = 25,
|
|
|
|
|
|
SmallChange = 5,
|
|
|
|
|
|
LargeChange = 25,
|
|
|
|
|
|
TabIndex = tabIndex++,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var lblGainValue = new Label
|
|
|
|
|
|
{
|
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Location = new Point(280, y + 28),
|
|
|
|
|
|
TabIndex = tabIndex++,
|
|
|
|
|
|
};
|
|
|
|
|
|
void UpdateGainLabel() =>
|
|
|
|
|
|
lblGainValue.Text = $"{trkGain.Value}% ({trkGain.Value / 100f:F1}×)";
|
|
|
|
|
|
UpdateGainLabel();
|
|
|
|
|
|
|
|
|
|
|
|
var chkMute = new CheckBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = "&Mute",
|
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Location = new Point(12, y + 48),
|
|
|
|
|
|
Checked = mute0,
|
|
|
|
|
|
TabIndex = tabIndex++,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var chkNr = new CheckBox
|
|
|
|
|
|
{
|
2026-06-23 14:11:18 +02:00
|
|
|
|
Text = "&Noise reduction",
|
2026-06-18 02:06:44 +02:00
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Location = new Point(96, y + 48),
|
|
|
|
|
|
Checked = nr0,
|
|
|
|
|
|
TabIndex = tabIndex++,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var row = new StreamRow(s.StreamId, trkGain, chkMute, chkNr,
|
|
|
|
|
|
new Control[] { lblName, trkGain, lblGainValue, chkMute, chkNr });
|
|
|
|
|
|
|
|
|
|
|
|
trkGain.Scroll += (_, _) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateGainLabel();
|
|
|
|
|
|
row.Apply(_client, _userId);
|
|
|
|
|
|
};
|
|
|
|
|
|
chkMute.CheckedChanged += (_, _) => row.Apply(_client, _userId);
|
|
|
|
|
|
chkNr.CheckedChanged += (_, _) => row.Apply(_client, _userId);
|
|
|
|
|
|
|
|
|
|
|
|
return row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static string StreamLabel(StreamSummary s)
|
|
|
|
|
|
{
|
|
|
|
|
|
var kind = s.Kind switch
|
|
|
|
|
|
{
|
|
|
|
|
|
VcStreamKind.Mic => "Mic",
|
|
|
|
|
|
VcStreamKind.ScreenAudio => "Screen audio",
|
|
|
|
|
|
VcStreamKind.AuxDevice => "Aux device",
|
|
|
|
|
|
_ => "Stream",
|
|
|
|
|
|
};
|
|
|
|
|
|
return string.IsNullOrWhiteSpace(s.Label) ? kind : $"{kind} — {s.Label}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static int ClampToTrack(float gain)
|
|
|
|
|
|
{
|
|
|
|
|
|
int v = (int)Math.Round(gain * 100f);
|
2026-06-24 12:18:10 +02:00
|
|
|
|
return Math.Max(0, Math.Min(400, v));
|
2026-06-18 02:06:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private sealed class StreamRow
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly uint _streamId;
|
|
|
|
|
|
private readonly TrackBar _trkGain;
|
|
|
|
|
|
private readonly CheckBox _chkMute;
|
|
|
|
|
|
private readonly CheckBox _chkNr;
|
|
|
|
|
|
public Control[] Controls { get; }
|
|
|
|
|
|
|
|
|
|
|
|
public StreamRow(uint streamId, TrackBar trkGain, CheckBox chkMute, CheckBox chkNr, Control[] controls)
|
|
|
|
|
|
{
|
|
|
|
|
|
_streamId = streamId;
|
|
|
|
|
|
_trkGain = trkGain;
|
|
|
|
|
|
_chkMute = chkMute;
|
|
|
|
|
|
_chkNr = chkNr;
|
|
|
|
|
|
Controls = controls;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Apply(VoiceCatClient client, uint userId)
|
2026-06-17 00:35:16 +02:00
|
|
|
|
{
|
|
|
|
|
|
float gain = _trkGain.Value / 100f;
|
2026-06-18 02:06:44 +02:00
|
|
|
|
client.SetRemoteStream(userId, _streamId, gain, _chkMute.Checked, _chkNr.Checked);
|
|
|
|
|
|
}
|
2026-06-17 00:35:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|