Reset-ASIO button + checkbox tick/untick sounds (parts 1-2 of the cue/preferences overhaul)
Part 1 - "Uncheck all inputs and outputs" button now also resets the ASIO driver to "(none)": renamed to say so, and UncheckAllDevices sets asioDriverBox to row 0 for a full clean WASAPI-only, nothing-selected state. Part 2 - checkbox tick/untick sounds: every checkbox toggle anywhere in RemSound now plays a short cue (check.wav on tick, uncheck.wav on untick) - instant feedback on which way a box went, especially in the inputs/outputs lists. New CheckSoundService + two machine-wide cues (CheckboxOn/Off) with the usual numbered-variant + Preferences treatment. Hooked from AccessibleCheckBox.OnCheckedChanged and the device lists' WireCheckedListAccessibility, both gated on the control being Focused so a genuine user toggle clicks but bulk programmatic (un)checking (profile load, "uncheck all") stays silent. Reloaded at startup and on cue change. Tests + manual updated; .sfk byproducts cleared. Remaining for the overhaul (next): tabbed Preferences (General / Audio cues / Startup behaviour / Update settings), the cue-list redesign with a "none" option replacing per-cue checkboxes, moving Startup behaviour out of the Options menu, and a front-most "missing sound file" error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8763470037
commit
aed555cc2e
@@ -866,7 +866,7 @@ Toggle| What it does
|
|||||||
|
|
||||||
## 18. Audio cue sounds
|
## 18. Audio cue sounds
|
||||||
|
|
||||||
RemSound plays a short sound at moments where you might want an audible confirmation that something just happened. These are called **cue sounds**. Fifteen events have a cue:
|
RemSound plays a short sound at moments where you might want an audible confirmation that something just happened. These are called **cue sounds**. Sixteen kinds of event have a cue:
|
||||||
|
|
||||||
Cue| Plays when
|
Cue| Plays when
|
||||||
---|---
|
---|---
|
||||||
@@ -883,6 +883,7 @@ Cue| Plays when
|
|||||||
**Receive turned on / off sound**| You turn receiving audio on or off — from the **Receive audio** box or its mute shortcut. Again, a separate sound for on and for off.
|
**Receive turned on / off sound**| You turn receiving audio on or off — from the **Receive audio** box or its mute shortcut. Again, a separate sound for on and for off.
|
||||||
**Minimise (hide) sound**| RemSound's window minimises to the notification area (hides).
|
**Minimise (hide) sound**| RemSound's window minimises to the notification area (hides).
|
||||||
**Restore (show) sound**| RemSound's window is brought back from the notification area (shows).
|
**Restore (show) sound**| RemSound's window is brought back from the notification area (shows).
|
||||||
|
**Checkbox ticked / unticked sound**| You tick or untick _any_ checkbox anywhere in RemSound — a click for ticked, a different one for unticked. This gives instant feedback on which way a box just went, which is especially handy in the busy inputs and outputs lists. There's a separate sound for ticking and for unticking.
|
||||||
|
|
||||||
All of these cues play through your default Windows sound output, which is separate from the audio RemSound is sending or receiving. They don't appear in a normal recording. (The exception: if your sending side is capturing the very output device the cues play through, then they get captured along with everything else from that device.)
|
All of these cues play through your default Windows sound output, which is separate from the audio RemSound is sending or receiving. They don't appear in a normal recording. (The exception: if your sending side is capturing the very output device the cues play through, then they get captured along with everything else from that device.)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -919,7 +919,7 @@ Use whatever key combinations you prefer (for example Ctrl+Shift+Up / Ctrl+Shift
|
|||||||
|
|
||||||
<h2 id="audio-cues">18. Audio cue sounds</h2>
|
<h2 id="audio-cues">18. Audio cue sounds</h2>
|
||||||
|
|
||||||
<p>RemSound plays a short sound at moments where you might want an audible confirmation that something just happened. These are called <strong>cue sounds</strong>. Fifteen events have a cue:</p>
|
<p>RemSound plays a short sound at moments where you might want an audible confirmation that something just happened. These are called <strong>cue sounds</strong>. Sixteen kinds of event have a cue:</p>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr><th>Cue</th><th>Plays when</th></tr>
|
<tr><th>Cue</th><th>Plays when</th></tr>
|
||||||
@@ -936,6 +936,7 @@ Use whatever key combinations you prefer (for example Ctrl+Shift+Up / Ctrl+Shift
|
|||||||
<tr><td><strong>Receive turned on / off sound</strong></td><td>You turn receiving audio on or off — from the <strong>Receive audio</strong> box or its mute shortcut. Again, a separate sound for on and for off.</td></tr>
|
<tr><td><strong>Receive turned on / off sound</strong></td><td>You turn receiving audio on or off — from the <strong>Receive audio</strong> box or its mute shortcut. Again, a separate sound for on and for off.</td></tr>
|
||||||
<tr><td><strong>Minimise (hide) sound</strong></td><td>RemSound's window minimises to the notification area (hides).</td></tr>
|
<tr><td><strong>Minimise (hide) sound</strong></td><td>RemSound's window minimises to the notification area (hides).</td></tr>
|
||||||
<tr><td><strong>Restore (show) sound</strong></td><td>RemSound's window is brought back from the notification area (shows).</td></tr>
|
<tr><td><strong>Restore (show) sound</strong></td><td>RemSound's window is brought back from the notification area (shows).</td></tr>
|
||||||
|
<tr><td><strong>Checkbox ticked / unticked sound</strong></td><td>You tick or untick <em>any</em> checkbox anywhere in RemSound — a click for ticked, a different one for unticked. This gives instant feedback on which way a box just went, which is especially handy in the busy inputs and outputs lists. There's a separate sound for ticking and for unticking.</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p>All of these cues play through your default Windows sound output, which is separate from the audio RemSound is sending or receiving. They don't appear in a normal recording. (The exception: if your sending side is capturing the very output device the cues play through, then they get captured along with everything else from that device.)</p>
|
<p>All of these cues play through your default Windows sound output, which is separate from the audio RemSound is sending or receiving. They don't appear in a normal recording. (The exception: if your sending side is capturing the very output device the cues play through, then they get captured along with everything else from that device.)</p>
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ $soundsPath = Join-Path $publishDir 'sounds'
|
|||||||
$wavCount = @(Get-ChildItem -LiteralPath $soundsPath -Filter *.wav -ErrorAction SilentlyContinue).Count
|
$wavCount = @(Get-ChildItem -LiteralPath $soundsPath -Filter *.wav -ErrorAction SilentlyContinue).Count
|
||||||
if ($wavCount -ge 3) { Pass "cue sounds bundled ($wavCount .wav)" } else { Fail "cue sounds missing (found $wavCount) - this is the bug that shipped v3.9 with no sounds" }
|
if ($wavCount -ge 3) { Pass "cue sounds bundled ($wavCount .wav)" } else { Fail "cue sounds missing (found $wavCount) - this is the bug that shipped v3.9 with no sounds" }
|
||||||
# Cues ship as numbered variants ("connect 1.wav", ...); each required cue needs at least one.
|
# Cues ship as numbered variants ("connect 1.wav", ...); each required cue needs at least one.
|
||||||
foreach ($base in @('connect', 'disconnect', 'start up', 'send on', 'send off', 'recieve on', 'recieve off', 'minimise', 'maximise')) {
|
foreach ($base in @('connect', 'disconnect', 'start up', 'send on', 'send off', 'recieve on', 'recieve off', 'minimise', 'maximise', 'check', 'uncheck')) {
|
||||||
$variants = @(Get-ChildItem -LiteralPath $soundsPath -Filter "$base*.wav" -ErrorAction SilentlyContinue)
|
$variants = @(Get-ChildItem -LiteralPath $soundsPath -Filter "$base*.wav" -ErrorAction SilentlyContinue)
|
||||||
if ($variants.Count -gt 0) { Pass "'$base' cue has $($variants.Count) sound variant(s)" } else { Fail "no sound variant for the '$base' cue" }
|
if ($variants.Count -gt 0) { Pass "'$base' cue has $($variants.Count) sound variant(s)" } else { Fail "no sound variant for the '$base' cue" }
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -60,6 +60,9 @@ internal sealed class AccessibleCheckBox : CheckBox
|
|||||||
if (Focused)
|
if (Focused)
|
||||||
{
|
{
|
||||||
NotifyWinEvent(EVENT_OBJECT_FOCUS, Handle, OBJID_CLIENT, CHILDID_SELF);
|
NotifyWinEvent(EVENT_OBJECT_FOCUS, Handle, OBJID_CLIENT, CHILDID_SELF);
|
||||||
|
// Audible tick/untick feedback. Gated on Focused so it fires for a genuine user toggle
|
||||||
|
// (click or spacebar) but stays silent for the bulk programmatic checking on profile load.
|
||||||
|
CheckSoundService.Play(Checked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
checkSound = LoadCue(MainForm.CueId.CheckboxOn, "check.wav", cfg);
|
||||||
|
uncheckSound = LoadCue(MainForm.CueId.CheckboxOff, "uncheck.wav", cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Play(bool isChecked)
|
||||||
|
{
|
||||||
|
var cfg = AppConfig.Load();
|
||||||
|
if (isChecked) { if (cfg.EnableCheckboxOnCue) checkSound?.Play(); }
|
||||||
|
else { if (cfg.EnableCheckboxOffCue) 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -776,8 +776,8 @@ public sealed class MainForm : Form
|
|||||||
// "Uncheck all inputs and outputs on all soundcards" — clears every device tick in one
|
// "Uncheck all inputs and outputs on all soundcards" — clears every device tick in one
|
||||||
// press. Button owns its own &-mnemonic (Alt+U), so AccessibleName stays clean per Ed's
|
// press. Button owns its own &-mnemonic (Alt+U), so AccessibleName stays clean per Ed's
|
||||||
// mnemonic convention.
|
// mnemonic convention.
|
||||||
uncheckAllDevicesButton.Text = "Uncheck all inputs and outputs on all soundcards (Alt+&U)";
|
uncheckAllDevicesButton.Text = "Uncheck all inputs and outputs on all soundcards and set ASIO driver to none (Alt+&U)";
|
||||||
uncheckAllDevicesButton.AccessibleName = "Uncheck all inputs and outputs on all soundcards";
|
uncheckAllDevicesButton.AccessibleName = "Uncheck all inputs and outputs on all soundcards and set ASIO driver to none";
|
||||||
uncheckAllDevicesButton.Click += (_, _) => UncheckAllDevices();
|
uncheckAllDevicesButton.Click += (_, _) => UncheckAllDevices();
|
||||||
|
|
||||||
// Populate ASIO driver list at startup. Discovers all ASIO drivers via NAudio + a
|
// Populate ASIO driver list at startup. Discovers all ASIO drivers via NAudio + a
|
||||||
@@ -4572,6 +4572,11 @@ public sealed class MainForm : Form
|
|||||||
}
|
}
|
||||||
finally { suppressDeviceCheckChange = false; }
|
finally { suppressDeviceCheckChange = false; }
|
||||||
|
|
||||||
|
// Also reset the ASIO driver to "(none)" (row 0): the button now does a full reset to a
|
||||||
|
// clean WASAPI-only, nothing-selected state. Setting the index fires the driver-change
|
||||||
|
// handler, which saves the change and rebuilds the engine in WASAPI-only mode.
|
||||||
|
if (asioDriverBox.SelectedIndex > 0) asioDriverBox.SelectedIndex = 0;
|
||||||
|
|
||||||
ApplyAudioRuntime();
|
ApplyAudioRuntime();
|
||||||
ApplyReceiveDevices();
|
ApplyReceiveDevices();
|
||||||
MarkProfileDirty();
|
MarkProfileDirty();
|
||||||
@@ -6512,6 +6517,9 @@ public sealed class MainForm : Form
|
|||||||
public const string ReceiveOff = "receive-off";
|
public const string ReceiveOff = "receive-off";
|
||||||
public const string Hide = "hide";
|
public const string Hide = "hide";
|
||||||
public const string Show = "show";
|
public const string Show = "show";
|
||||||
|
// Played on every checkbox tick/untick across the whole app (CheckSoundService).
|
||||||
|
public const string CheckboxOn = "checkbox-on";
|
||||||
|
public const string CheckboxOff = "checkbox-off";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Load one cue sound. Resolution order:
|
/// <summary>Load one cue sound. Resolution order:
|
||||||
@@ -6589,6 +6597,8 @@ public sealed class MainForm : Form
|
|||||||
TryLoadCueSound(CueId.ReceiveOff, "recieve off.wav", out receiveOffSound);
|
TryLoadCueSound(CueId.ReceiveOff, "recieve off.wav", out receiveOffSound);
|
||||||
TryLoadCueSound(CueId.Hide, "minimise.wav", out hideSound);
|
TryLoadCueSound(CueId.Hide, "minimise.wav", out hideSound);
|
||||||
TryLoadCueSound(CueId.Show, "maximise.wav", out showSound);
|
TryLoadCueSound(CueId.Show, "maximise.wav", out showSound);
|
||||||
|
// The app-wide checkbox tick/untick sounds live in their own service; keep them in step.
|
||||||
|
CheckSoundService.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -7209,6 +7219,13 @@ public sealed class MainForm : Form
|
|||||||
|
|
||||||
private void WireCheckedListAccessibility(CheckedListBox list, Label statusLabel, string itemKind)
|
private void WireCheckedListAccessibility(CheckedListBox list, Label statusLabel, string itemKind)
|
||||||
{
|
{
|
||||||
|
// Tick/untick sound for the inputs/outputs lists. Gated on the list being focused so a real
|
||||||
|
// user click/spacebar clicks, but the bulk programmatic (un)checking done on profile load or
|
||||||
|
// by "uncheck all" (focus is on the button, not the list) stays silent.
|
||||||
|
list.ItemCheck += (_, e) =>
|
||||||
|
{
|
||||||
|
if (list.Focused) CheckSoundService.Play(e.NewValue == CheckState.Checked);
|
||||||
|
};
|
||||||
list.SelectedIndexChanged += (_, _) =>
|
list.SelectedIndexChanged += (_, _) =>
|
||||||
{
|
{
|
||||||
if (list.SelectedIndex >= 0) lastFocusedListIndices[list] = list.SelectedIndex;
|
if (list.SelectedIndex >= 0) lastFocusedListIndices[list] = list.SelectedIndex;
|
||||||
|
|||||||
@@ -194,6 +194,11 @@ internal sealed class PreferencesDialog : Form
|
|||||||
c => c.EnableHideCue, (c, v) => c.EnableHideCue = v),
|
c => c.EnableHideCue, (c, v) => c.EnableHideCue = v),
|
||||||
MachineRow("Restore (show) sound", MainForm.CueId.Show, "maximise.wav",
|
MachineRow("Restore (show) sound", MainForm.CueId.Show, "maximise.wav",
|
||||||
c => c.EnableShowCue, (c, v) => c.EnableShowCue = v),
|
c => c.EnableShowCue, (c, v) => c.EnableShowCue = v),
|
||||||
|
// Played on every checkbox tick / untick across the whole app (CheckSoundService).
|
||||||
|
MachineRow("Checkbox ticked sound", MainForm.CueId.CheckboxOn, "check.wav",
|
||||||
|
c => c.EnableCheckboxOnCue, (c, v) => c.EnableCheckboxOnCue = v),
|
||||||
|
MachineRow("Checkbox unticked sound", MainForm.CueId.CheckboxOff, "uncheck.wav",
|
||||||
|
c => c.EnableCheckboxOffCue, (c, v) => c.EnableCheckboxOffCue = v),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ internal static class Program
|
|||||||
KeyClickService.Initialize(AppConfig.Load().EnableKeyboardClicks);
|
KeyClickService.Initialize(AppConfig.Load().EnableKeyboardClicks);
|
||||||
Application.ApplicationExit += (_, _) => KeyClickService.Shutdown();
|
Application.ApplicationExit += (_, _) => KeyClickService.Shutdown();
|
||||||
|
|
||||||
|
// Tick/untick sounds for checkbox toggles app-wide (CheckSoundService). Loaded here; reloaded
|
||||||
|
// by MainForm.ReloadAllCueSounds whenever cue settings change in Preferences.
|
||||||
|
CheckSoundService.Reload();
|
||||||
|
|
||||||
// One-time "your settings moved" notice — only the launch that actually relocated files
|
// One-time "your settings moved" notice — only the launch that actually relocated files
|
||||||
// shows it (idempotent migration ⇒ MovedAnything is false on every later launch). Shown
|
// shows it (idempotent migration ⇒ MovedAnything is false on every later launch). Shown
|
||||||
// here, after the guard and before the profile picker, so the user reads it once up front.
|
// here, after the guard and before the profile picker, so the user reads it once up front.
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ internal static class SelfTest
|
|||||||
{
|
{
|
||||||
"connect.wav", "disconnect.wav", "start up.wav",
|
"connect.wav", "disconnect.wav", "start up.wav",
|
||||||
"send on.wav", "send off.wav", "recieve on.wav", "recieve off.wav", "minimise.wav", "maximise.wav",
|
"send on.wav", "send off.wav", "recieve on.wav", "recieve off.wav", "minimise.wav", "maximise.wav",
|
||||||
|
"check.wav", "uncheck.wav",
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Check(CueSounds.Variants(cue).Count > 0,
|
Check(CueSounds.Variants(cue).Count > 0,
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ public sealed class AppConfig
|
|||||||
public bool EnableReceiveOffCue { get; set; } = true;
|
public bool EnableReceiveOffCue { get; set; } = true;
|
||||||
public bool EnableHideCue { get; set; } = true;
|
public bool EnableHideCue { get; set; } = true;
|
||||||
public bool EnableShowCue { get; set; } = true;
|
public bool EnableShowCue { get; set; } = true;
|
||||||
|
/// <summary>Tick / untick sounds played on every checkbox toggle anywhere in the app.</summary>
|
||||||
|
public bool EnableCheckboxOnCue { get; set; } = true;
|
||||||
|
public bool EnableCheckboxOffCue { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>Custom WAV overrides for the machine-wide cues above, keyed by cue id. The
|
/// <summary>Custom WAV overrides for the machine-wide cues above, keyed by cue id. The
|
||||||
/// equivalent of <see cref="Profile.CustomCuePaths"/> but machine-wide, since these cues don't
|
/// equivalent of <see cref="Profile.CustomCuePaths"/> but machine-wide, since these cues don't
|
||||||
|
|||||||
Reference in New Issue
Block a user