Files
RemSound/src/RemSound.App/CheckSoundService.cs
T
EdnunpandClaude Opus 4.8 2fb9274a95 v5.2: stability and polish — deep-audit bug fixes + install-flow fixes
Verified findings from a multi-dimension code audit, plus the two install-flow bugs:
- Fix Opus encoder use-after-free on a codec/rate change while streaming (guard swap vs encode).
- Fix "both" single-file recording dropping audio + drifting (drain both directions in lockstep).
- Fix broken clip counter, UPnP teardown on exit, auto-update-restart foreground grant, and a
  malformed-Opus-format packet orphaning a playout session forever.
- Post-install relaunch now respects start-minimised; uninstall is path-aware so it won't clear a
  different copy's run-at-startup.
- Perf/hygiene: cache AppConfig off UI hot paths, fold per-peer EQ+gain into one pass, deterministic
  disposal (tray menu, timers, COM shortcut, Process handles, process meter), ring-buffer overflow
  guard, receiver session-lock fix, remote-control allow-list moved onto the UI thread.
- Remove dead code (two IsAsioBackend, SessionPlayout.Reset, IsSameEndpoint, RemSoundUpdater
  IDisposable); several stale-doc fixes.

Deferred (not in this release): drift-estimator tweak, peer-discovery pruning, uninstall retry-loop,
encryption nonce. Wire format unchanged (interops v3.3-v5.1). Version -> 5.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:54:53 +01:00

62 lines
2.9 KiB
C#

using RemSound.Core;
namespace RemSound.App;
/// <summary>
/// Plays a short tick / untick sound whenever the user checks or unchecks a checkbox anywhere in
/// RemSound - instant audible feedback of which way a box just went, which matters most in the busy
/// inputs/outputs lists. Two normal machine-wide cues: CheckboxOn (check.wav) and CheckboxOff
/// (uncheck.wav), each with numbered variants + an off option in Preferences, like any other cue.
///
/// Wired from <see cref="AccessibleCheckBox"/> and the device <c>LiveCheckedListBox</c>, both gated
/// on the control being focused - so a genuine user toggle clicks, but the bulk programmatic
/// checking done on profile load or by "uncheck all" stays silent.
/// </summary>
internal static class CheckSoundService
{
private static CuePlayer? checkSound;
private static CuePlayer? uncheckSound;
// Cached at Reload() so Play() (which fires on the UI thread for every genuine checkbox toggle)
// doesn't re-read + re-deserialize the whole config file from disk just to fetch one bool. Reload()
// is already the hook that runs whenever cue settings change, so these stay current.
private static bool enableCheckboxOn;
private static bool enableCheckboxOff;
/// <summary>When true, <see cref="Play"/> is a no-op. MainForm sets this around bulk programmatic
/// control updates (profile load, "uncheck all", device-list refresh). The per-call Focused gate
/// already silences MOST programmatic ticks, but it leaks when the box we tick in code happens to
/// be the focused control on launch — which is exactly what made loading a profile blast a
/// checkbox click. This flag closes that gap: only genuine user toggles ever click.</summary>
public static bool Suppressed { get; set; }
/// <summary>(Re)load the tick/untick sounds from the current cue configuration. Call at startup
/// and whenever cue settings change.</summary>
public static void Reload()
{
var cfg = AppConfig.Load();
enableCheckboxOn = cfg.EnableCheckboxOnCue;
enableCheckboxOff = cfg.EnableCheckboxOffCue;
checkSound = LoadCue(MainForm.CueId.CheckboxOn, "check.wav", cfg);
uncheckSound = LoadCue(MainForm.CueId.CheckboxOff, "uncheck.wav", cfg);
}
public static void Play(bool isChecked)
{
if (Suppressed) return;
if (isChecked) { if (enableCheckboxOn) checkSound?.Play(); }
else { if (enableCheckboxOff) uncheckSound?.Play(); }
}
private static CuePlayer? LoadCue(string cueId, string defaultFile, AppConfig cfg)
{
try
{
string? path = cfg.MachineCueCustomPaths.TryGetValue(cueId, out var custom) && File.Exists(custom)
? custom
: CueSounds.ResolveDefaultPath(cueId, defaultFile, cfg);
return path is not null && File.Exists(path) ? new CuePlayer(path) : null;
}
catch { return null; }
}
}