Files
voice-cat/clients/windows/VoiceCat.App/Notifications/EventFeedback.cs
Talon 50416c33a2 feat(clients): event sound effects + optional text-to-speech
Add audible cues and optional spoken announcements for session events
(join/leave, channel + PM sent/recv, login, logout/connection-lost,
mic on/off, voice-activity, PTT) across all three clients, driven off
the shared C ABI vc_event stream so the mapping stays consistent.

TTS is off by default; when enabled it announces events and reads
message/PM bodies aloud. Master toggles + a sound-volume slider; the
per-utterance voice-activity and PTT cues default off. WAVs ship from
assets/sounds/.

Windows (built + verified): new VoiceCat.App/Notifications/ layer
(FeedbackSettings -> %AppData%\VoiceCat\feedback.json, SoundPlayerPool
via System.Media.SoundPlayer, SpeechAnnouncer via Prismatoid 0.3.0,
EventFeedback dispatcher); MainForm hooks; NotificationSettingsForm
under Settings > Notifications; csproj adds the Prismatoid PackageRef
and copies the WAVs into sounds\.

macOS + iOS (written, not yet built -- needs a Mac): shared
VoiceCatCore/Feedback/ (SoundEvent, EventFeedback = AVAudioPlayer pool
+ native AVSpeechSynthesizer, FeedbackSettings over UserDefaults); WAVs
bundled via Package.swift resources (.process). Hooks in SessionState/
AppState (iOS) and MainWindowController (macOS); settings UI in
SettingsView (iOS) and SettingsWindowController (macOS).

No core/server code touched; ctest --preset dev unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:20:31 +02:00

43 lines
1.5 KiB
C#

namespace VoiceCat.App.Notifications;
/// <summary>
/// Combines the sound pool and the speech announcer behind the user's <see cref="FeedbackSettings"/>.
/// MainForm calls <see cref="PlaySound"/> / <see cref="Speak"/> from inside its existing event
/// handlers (which already resolve nicknames and channel membership), so this type only owns the
/// "should I, and how" policy — not the event routing.
/// </summary>
public sealed class EventFeedback : IDisposable
{
private readonly SoundPlayerPool _sounds = new();
private readonly SpeechAnnouncer _speech = new();
public FeedbackSettings Settings { get; set; }
public EventFeedback(FeedbackSettings settings) => Settings = settings;
/// <summary>True when speech is both enabled and actually available on this machine.</summary>
public bool SpeechAvailable => _speech.Available;
public void PlaySound(SoundEvent ev)
{
if (!Settings.Sounds || Settings.Volume <= 0f) return;
// The two opt-in categories are gated by their own flags.
if ((ev is SoundEvent.VaStart or SoundEvent.VaStop) && !Settings.SelfTalkSounds) return;
if (ev is SoundEvent.Ptt && !Settings.PttSound) return;
_sounds.Play(ev);
}
/// <summary>Announce <paramref name="text"/> when spoken feedback is enabled.</summary>
public void Speak(string text)
{
if (!Settings.Speech) return;
_speech.Speak(text);
}
public void Dispose()
{
_sounds.Dispose();
_speech.Dispose();
}
}