feat(windows): UI overhaul -- toolbar, unified log, PM windows, channel counts, output volume

- Voice actions (Join Voice, Share Screen Audio) moved to a ToolStrip toolbar and
  a new Voice menu in the menu bar; removed from the bottom voice panel
- Activity log and chat log collapsed into a single RichTextBox (rtbLog); activity
  events appear in gray, chat messages in default color
- Private messaging reworked: each conversation opens in its own modeless
  PrivateMessageForm instead of sharing the main chat log via a scope dropdown;
  cboScope removed; main compose bar always sends to the current channel
- New "Messages -> New Private Message..." menu item (Ctrl+P) opens a UserPickerDialog
  listing all connected server users (not just the current channel) so you can PM
  anyone on the server
- Channel tree now shows live user counts, e.g. "General (3)" -- counts sourced from
  the existing _users dictionary which already tracks all server users with channel IDs
- Global output volume slider (TrackBar, 0-100, default 80) added to the right panel;
  wired to new vc_set_output_volume C ABI function that applies a master gain multiplier
  in the audio engine playback callback after mixing all streams
- vc_set_output_volume added end-to-end: voicecat.h, audio_engine.h/.cpp,
  client.h/.cpp, voicecat.cpp, NativeMethods.cs, VoiceCatClient.cs
- Documented Windows PowerShell ctest requirement in AGENTS.md and CLAUDE.md:
  MinGW binaries exit 0xc0000139 in Git Bash; always run ctest/.exe via PowerShell

22/22 ctest green (PowerShell); dotnet build 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 14:24:54 +02:00
parent fdcd8d1427
commit 97fa659422
14 changed files with 504 additions and 236 deletions

View File

@@ -659,8 +659,11 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
}
}
for (ma_uint32 i = 0; i < frames * pb_channels; ++i)
out[i] = static_cast<int16_t>(std::clamp(mix[i], -32768, 32767));
const float ovol = output_volume_.load(std::memory_order_relaxed);
for (ma_uint32 i = 0; i < frames * pb_channels; ++i) {
int32_t s = static_cast<int32_t>(static_cast<float>(mix[i]) * ovol);
out[i] = static_cast<int16_t>(std::clamp(s, -32768, 32767));
}
#else
(void)out;
(void)frames;

View File

@@ -163,6 +163,9 @@ class AudioEngine {
// Called by the net thread when a decoded voice frame arrives for a remote stream.
void push_recv_frame(uint32_t ssrc, JitterBuffer::Frame f);
// Global output volume applied after mixing all streams (default 1.0). Thread-safe.
void set_output_volume(float gain) { output_volume_.store(gain, std::memory_order_relaxed); }
// Per-stream receive-side controls (safe from any thread).
void set_stream_gain(uint32_t ssrc, float gain); // 0.02.0, default 1.0
void set_stream_mute(uint32_t ssrc, bool mute);
@@ -337,7 +340,8 @@ class AudioEngine {
AudioParams params_{};
CaptureCallback capture_cb_;
std::atomic<bool> running_{false};
std::atomic<bool> running_{false};
std::atomic<float> output_volume_{1.0f};
// Capture-side frame accumulators: miniaudio fires the capture (and loopback) callback at
// whatever period the hardware/driver chooses — commonly 480 samples (10 ms) on WASAPI