diff --git a/MANUAL.md b/MANUAL.md
index d303e35..e23dca0 100644
--- a/MANUAL.md
+++ b/MANUAL.md
@@ -59,8 +59,8 @@ There is no central server, no account, and nothing stored online. The sound goe
Let's assume you and a friend both have RemSound running, and that your two computers can reach each other on the network (the same Wi-Fi, the same Tailscale account, and so on).
1. Start RemSound. The first thing you'll see is the **profile picker**. On a brand-new install your only choice is **New profile** — select it and press Enter or click OK. Later, once you've saved a setup or two of your own, this is the dialog where you choose which one to load. See Profiles for the full story.
- 2. Once the main window opens, go to the **Audio inputs and outputs** tab. Tick **Receive audio (Alt+R)** , then tick the device you want incoming sound played through in **WASAPI outputs for received sound (Alt+3)**.
- 3. On the same tab, tick **Send my audio (Alt+S)** and tick your microphone in **WASAPI inputs to send (Alt+5)**.
+ 2. Once the main window opens, go to the **Audio inputs and outputs** tab. Tick **Receive audio (Alt+R)** , then tick the device you want incoming sound played through in **WASAPI outputs for received audio (Alt+3)**.
+ 3. On the same tab, tick **Send my audio (Alt+S)** and tick your microphone in **WASAPI audio inputs to send (Alt+5)**.
4. Go to the **Connectivity** tab, find your friend in the **Discovered peers (Alt+D)** list, and tick them. If they aren't showing up, use **Add peer by IP (Alt+A)** and type their address.
5. Have your friend do the same with you on their computer.
6. Within a second or two, both of you will hear each other.
@@ -276,7 +276,7 @@ Item| Shortcut| What it does
**Manage named peers …**| Alt+O, N| Opens a list of every peer you've given a friendly name to, showing each one's machine name and where and when you last connected to it. Pick one and **Rename (Alt+R** , or F2) to change its name, or **Delete (Alt+D** , or the Del key) to forget it — deleting only drops the name, the peer still connects as normal under its machine name. See the peer-naming notes on the Connectivity tab.
**Reset the default audio device prompt**| Alt+O, R| Brings back the “use only the default audio device?” question if you previously ticked “Don't ask me this again” on it. See Following the Windows default audio device.
**Enable / Disable Realtek ASIO**| —| Only shown if a Realtek ASIO driver is installed. Lets you reverse the choice RemSound offered about disabling that driver (Realtek's generic ASIO driver tends to grab the wrong device and clash with your screen reader).
-**Preferences …**| Ctrl+P, or Alt+O, P| Opens the Preferences dialog, organised into five tabs (move between them with Ctrl+Tab, or the arrow keys when the tab names have focus): **General** — the profiles folder, accept remote volume commands, UPnP router opening, and “Show the volume, pan and EQ for peers tab” (on by default; hides the Volume, pan and EQ for peers tab if you untick it); **Audio cues** — the cue list and its sounds (see Audio cue sounds); **Startup behaviour** — start minimised / with Windows / with a specific profile; **Update settings** — the update checks and install options; and **Logging** — enable logs, write logs now, and the log-folder housekeeping (see Logs and diagnostics). Esc or the Close button dismisses it.
+**Preferences …**| Ctrl+P, or Alt+O, P| Opens the Preferences dialog, organised into five tabs (move between them with Ctrl+Tab, or the arrow keys when the tab names have focus): **General** — the profiles folder, accept remote volume commands, UPnP router opening, “Show the volume, pan and EQ for peers tab” (on by default; hides the Volume, pan and EQ for peers tab if you untick it), and **Colour theme (Alt+T)** — Match Windows (the default), Light, or Dark; this only changes how the window looks, takes effect the next time you start RemSound, and has no effect on the screen reader; **Audio cues** — the cue list and its sounds (see Audio cue sounds); **Startup behaviour** — start minimised / with Windows / with a specific profile; **Update settings** — the update checks and install options; and **Logging** — enable logs, write logs now, and the log-folder housekeeping (see Logs and diagnostics). Esc or the Close button dismisses it.
### Help menu
@@ -297,9 +297,9 @@ Control| Shortcut| What it does
**Connected peers**| Alt+C| The people you currently have sound flowing with. Unticking a row disconnects that peer.
**Peer details**| Alt+E| A read-only box describing whichever connected peer you're on in the list above. Arrow through it to read: their name, their machine name, their IP address, how long you've been connected, the link health and ping, what they're sending (how many devices, on WASAPI or ASIO, at what sample rate and codec), and whether they're receiving your audio. The device and WASAPI/ASIO detail only shows while you're actually receiving that peer.
**Rename peer**| Alt+M or F2| Give the highlighted peer a friendly name of your choosing. It opens a box with the name and a **Clear custom name** button. The name sticks to that machine for good — across restarts, IP changes and networks — and shows everywhere that peer appears: the lists here, the volume, pan and EQ for peers tab, the status line and split-recording filenames. See below.
+**Add peer by IP**| Alt+A| Opens a small box where you type an address or computer name. It adds that peer to the remembered list and connects.
**Discovered peers**| Alt+D| People RemSound has heard from in the last few seconds. Tick someone to connect to them.
**Remembered peers**| Alt+R| People you've connected to before, or added by address. This list is kept between sessions. Tick someone to reconnect.
-**Add peer by IP**| Alt+A| Opens a small box where you type an address or computer name. It adds that peer to the remembered list and connects.
**Lock to these exact peer addresses**| Alt+L| When ticked, this profile uses only the exact addresses you set and never follows the other computer by name or switches to a different address — even if the address stops working. Off by default, saved with the profile. See Locking a profile to one exact address.
**Connection status**| Alt+S| A read-only box of text that sums up everything happening right now — how long you've been connected, how many peers you have, how much sound is flowing each way, and the connection health of each peer. Open it to read the current connection status.
@@ -319,19 +319,19 @@ Control| Shortcut| What it does
---|---|---
**ASIO driver**| Alt+D| A list that starts with _(none)_. Pick _(none)_ and the app uses the ordinary Windows sound path only; pick a real driver and the ASIO device lists appear below, and the Audio profile tab gains a second delay setting. If your computer has no ASIO drivers installed, this control is hidden completely.
**Receive audio**| Alt+R| The master switch for receiving. When it's off, no sound plays out, no matter which output devices are ticked.
-**WASAPI outputs for received sound**| Alt+3| Tick which ordinary Windows outputs (speakers, headsets) should play the received sound. Ticking more than one means the received sound plays out of all of them at once.
-**ASIO outputs for received sound**| Alt+1| (Shown when an ASIO driver is chosen.) Tick which ASIO channel pairs should play the received sound.
-**Master receive volume**| Alt+V| A slider: the master volume for everything coming in. There is no separate volume per device here; per-person volume lives on the Volume, pan and EQ for peers tab.
+**WASAPI outputs for received audio**| Alt+3| Tick which ordinary Windows outputs (speakers, headsets) should play the received sound. Ticking more than one means the received sound plays out of all of them at once.
+**ASIO outputs for received audio**| Alt+1| (Shown when an ASIO driver is chosen.) Tick which ASIO channel pairs should play the received sound.
+**Master volume for received audio**| Alt+V| A slider: the master volume for everything coming in. There is no separate volume per device here; per-person volume lives on the Volume, pan and EQ for peers tab.
**Send my audio**| Alt+S| The master switch for sending.
-**WASAPI outputs to send**| Alt+4| Tick which Windows output devices to capture from — this captures whatever is currently playing on those speakers and sends it.
-**WASAPI inputs to send**| Alt+5| Tick which Windows input devices to capture (microphones, line-ins).
-**ASIO inputs to send**| Alt+2| (Shown when an ASIO driver is chosen.) Tick which ASIO channel pairs to capture and send.
+**WASAPI audio outputs to send**| Alt+4| Tick which Windows output devices to capture from — this captures whatever is currently playing on those speakers and sends it.
+**WASAPI audio inputs to send**| Alt+5| Tick which Windows input devices to capture (microphones, line-ins).
+**ASIO audio inputs to send**| Alt+2| (Shown when an ASIO driver is chosen.) Tick which ASIO channel pairs to capture and send.
All the device lists are checkable lists — tick or untick an item to include or exclude that device. Profiles save which devices are ticked; a new profile starts with everything unticked.
### Following the Windows default audio device
-At the very top of the **WASAPI outputs for received sound** list and the **WASAPI inputs to send** list there's a special entry: **Use Windows default audio device, follows Windows changes**. Tick it and RemSound uses whatever Windows currently treats as the default — the default speakers for received sound, the default microphone for sending — and, the useful part, it **follows that default on its own**. Make a headset your default and RemSound switches to it; unplug it and RemSound moves back, all without you touching the list.
+At the very top of the **WASAPI outputs for received audio** list and the **WASAPI audio inputs to send** list there's a special entry: **Use Windows default audio device, follows Windows changes**. Tick it and RemSound uses whatever Windows currently treats as the default — the default speakers for received sound, the default microphone for sending — and, the useful part, it **follows that default on its own**. Make a headset your default and RemSound switches to it; unplug it and RemSound moves back, all without you touching the list.
It works alongside the specific devices: tick the default entry _and_ particular cards, and the sound plays out of (or is captured from) all of them at once. When you first tick it, RemSound asks whether you'd like to untick the other devices in that list, so you can choose to use only the default — with a “Don't ask me this again” option. If you tick that and later want the question back, use **Options → Reset the default audio device prompt**.
@@ -357,15 +357,15 @@ To send sound you need **Send my audio** ticked, plus at least one capture sourc
List| What it captures| Typical use
---|---|---
-WASAPI outputs to send| Whatever Windows is currently playing through that output. So picking your “Speakers” device captures whatever you're hearing.| Sharing music playback, sharing the sound from a video call, anything coming out of your own speakers.
-WASAPI inputs to send| Sound captured straight from a microphone or line input.| Your USB microphone, a headset mic, a line-in.
-ASIO inputs to send| An ASIO channel pair — usually a hardware input on a professional audio interface.| An instrument input on an Audient EVO, a microphone preamp on a Focusrite, and so on.
+WASAPI audio outputs to send| Whatever Windows is currently playing through that output. So picking your “Speakers” device captures whatever you're hearing.| Sharing music playback, sharing the sound from a video call, anything coming out of your own speakers.
+WASAPI audio inputs to send| Sound captured straight from a microphone or line input.| Your USB microphone, a headset mic, a line-in.
+ASIO audio inputs to send| An ASIO channel pair — usually a hardware input on a professional audio interface.| An instrument input on an Audient EVO, a microphone preamp on a Focusrite, and so on.
Tick any combination across the three lists. RemSound mixes them together into one stream and sends that to all your chosen peers. So you can send a mic plus a guitar plus your system sound all at once, mixed together, and your friends hear all three.
-> **Capturing your speakers can cause an echo loop.** If you tick the same device both in “WASAPI outputs to send” and in “WASAPI outputs for received sound”, then the received sound plays out of that device, gets captured again, and gets sent back. The other person ends up hearing their own voice on a delay. Don't tick the same device on both sides at once.
+> **Capturing your speakers can cause an echo loop.** If you tick the same device both in “WASAPI audio outputs to send” and in “WASAPI outputs for received audio”, then the received sound plays out of that device, gets captured again, and gets sent back. The other person ends up hearing their own voice on a delay. Don't tick the same device on both sides at once.
-> **If your microphone sends silence:** Windows can block desktop apps from using the microphone, and when it does, RemSound's mic capture still switches on but only sends silence — so you look like you're sending, but the other person hears nothing. RemSound watches for this: when you tick a microphone in **WASAPI inputs to send** while Windows is blocking it — or load a profile that already has one ticked — a message pops up telling you, with the exact two settings to turn on — open Windows Settings → Privacy & security → Microphone, then turn on both _Microphone access_ and _Let desktop apps access your microphone_. The check also catches the sneakier kinds of block: one aimed at RemSound alone in that same Settings page's per-app list, and one set by an administrator or workplace policy — that last kind doesn't show up as a switch you can flip, so if the warning says a policy is involved, it needs whoever manages the computer to lift it. (ASIO inputs aren't affected, because ASIO talks straight to the hardware and bypasses that Windows privacy gate.) It doesn't change anything you receive — only sending your own mic.
+> **If your microphone sends silence:** Windows can block desktop apps from using the microphone, and when it does, RemSound's mic capture still switches on but only sends silence — so you look like you're sending, but the other person hears nothing. RemSound watches for this: when you tick a microphone in **WASAPI audio inputs to send** while Windows is blocking it — or load a profile that already has one ticked — a message pops up telling you, with the exact two settings to turn on — open Windows Settings → Privacy & security → Microphone, then turn on both _Microphone access_ and _Let desktop apps access your microphone_. The check also catches the sneakier kinds of block: one aimed at RemSound alone in that same Settings page's per-app list, and one set by an administrator or workplace policy — that last kind doesn't show up as a switch you can flip, so if the warning says a policy is involved, it needs whoever manages the computer to lift it. (ASIO inputs aren't affected, because ASIO talks straight to the hardware and bypasses that Windows privacy gate.) It doesn't change anything you receive — only sending your own mic.
## 8. Audio profile tab
@@ -437,7 +437,7 @@ Control| What it does
**16 band parametric EQ** lets you place your own bands wherever you want them, up to sixteen. Instead of fixed sliders you build a list of bands:
* Tab past the mode picker to the **Add band** button and press it. A small dialog opens with three boxes: a **start frequency** , an **end frequency** and a **gain in dB** (from −12 to +12). Each box you can type into or spin with the arrow keys; they only accept sensible numbers. As you change the values you hear the band on that peer straight away. Press **OK** to add it, or **Cancel** / **Escape** to drop it.
- * Back on the tab, tab to the **Bands** list to hear your bands, one per row, each read out as its range and level — for example “200 Hz to 800 Hz, plus 3 dB”. The list is sorted low to high, so the bass bands are at the top and the treble at the bottom.
+ * Back on the tab, tab to the **Bands** list to hear your bands, one per row, each read out as its range and level — for example “200 Hz to 800 Hz, plus 3 dB”. The list is sorted low to high, so the bass bands are at the top and the treble at the bottom. Up and down arrow move between bands; **left and right arrow nudge the selected band's gain down or up by half a dB** , so you can fine-tune it on the fly and hear the change straight away.
* To remove a band, land on it and press **Delete** , or use the **Delete band** button. You can select several at once (hold Shift and arrow, or hold Ctrl and arrow then Space to pick out individual ones) and delete them together.
@@ -783,11 +783,11 @@ Key| Action
---|---
Alt+D| Focus the ASIO driver list (hidden if no ASIO drivers are installed)
Alt+R| Toggle Receive audio
-Alt+1| Focus ASIO outputs for received sound
-Alt+2| Focus ASIO inputs to send
-Alt+3| Focus WASAPI outputs for received sound
-Alt+4| Focus WASAPI outputs to send
-Alt+5| Focus WASAPI inputs to send
+Alt+1| Focus ASIO outputs for received audio
+Alt+2| Focus ASIO audio inputs to send
+Alt+3| Focus WASAPI outputs for received audio
+Alt+4| Focus WASAPI audio outputs to send
+Alt+5| Focus WASAPI audio inputs to send
Alt+V| Focus the volume slider
Alt+S| Toggle Send my audio
@@ -1124,9 +1124,9 @@ RemSound can save the sound passing through it to a file on your computer — us
Recording captures the sound at fully-mixed, fully-finished points: for the received side, after volume and mute have been applied (so the file matches what you hear); for the sent side, the raw captured sound just before it's packaged for sending (so the file is the same whatever codec you chose). The three source choices:
- * **Record all received audio** — the full mix of everything coming in from connected peers. This is the default.
+ * **Record both sent and received audio** — a single file with both directions mixed gently together. This is the default, and the right choice for capturing a complete two-way exchange.
+ * **Record all received audio** — the full mix of everything coming in from connected peers.
* **Record all sent audio** — what your microphones, captured speakers and ASIO inputs are sending out. Useful for checking what your collaborators are actually hearing from you.
- * **Record both sent and received audio** — a single file with both directions mixed gently together. The right choice for capturing a complete two-way exchange.
@@ -1379,7 +1379,7 @@ Term| Meaning
---|---
WASAPI| The normal Windows way of handling sound. Every speaker and microphone in your Windows sound settings works this way. The delay it adds is around 10–30 ms.
ASIO| A faster, more direct way of handling sound, used by professional audio equipment. It talks straight to the hardware, giving a delay of under 5 ms. It only works if your audio interface came with an ASIO driver.
-Loopback capture| Capturing what's currently being played out of an output device, rather than what's coming in from a microphone. The “WASAPI outputs to send” list does loopback capture.
+Loopback capture| Capturing what's currently being played out of an output device, rather than what's coming in from a microphone. The “WASAPI audio outputs to send” list does loopback capture.
Channel pair| A stereo pair of channels on an ASIO driver. Pair 1 is channels 1 and 2, Pair 2 is channels 3 and 4, and so on.
Codec| The method RemSound uses to package the sound before sending it. RemSound offers PCM (no compression) and Opus (compressed).
Opus| A high-quality codec that compresses sound to use much less network bandwidth, and can repair single lost packets on its own. The right choice for internet connections.
diff --git a/readme.html b/readme.html
index b4088bb..8007a14 100644
--- a/readme.html
+++ b/readme.html
@@ -83,8 +83,8 @@ ul, ol { padding-left: 1.4em; }
- Start RemSound. The first thing you'll see is the profile picker. On a brand-new install your only choice is New profile — select it and press Enter or click OK. Later, once you've saved a setup or two of your own, this is the dialog where you choose which one to load. See Profiles for the full story.
-- Once the main window opens, go to the Audio inputs and outputs tab. Tick Receive audio (Alt+R), then tick the device you want incoming sound played through in WASAPI outputs for received sound (Alt+3).
-- On the same tab, tick Send my audio (Alt+S) and tick your microphone in WASAPI inputs to send (Alt+5).
+- Once the main window opens, go to the Audio inputs and outputs tab. Tick Receive audio (Alt+R), then tick the device you want incoming sound played through in WASAPI outputs for received audio (Alt+3).
+- On the same tab, tick Send my audio (Alt+S) and tick your microphone in WASAPI audio inputs to send (Alt+5).
- Go to the Connectivity tab, find your friend in the Discovered peers (Alt+D) list, and tick them. If they aren't showing up, use Add peer by IP (Alt+A) and type their address.
- Have your friend do the same with you on their computer.
- Within a second or two, both of you will hear each other.
@@ -312,7 +312,7 @@ ul, ol { padding-left: 1.4em; }
| Manage named peers… | Alt+O, N | Opens a list of every peer you've given a friendly name to, showing each one's machine name and where and when you last connected to it. Pick one and Rename (Alt+R, or F2) to change its name, or Delete (Alt+D, or the Del key) to forget it — deleting only drops the name, the peer still connects as normal under its machine name. See the peer-naming notes on the Connectivity tab. |
| Reset the default audio device prompt | Alt+O, R | Brings back the “use only the default audio device?” question if you previously ticked “Don't ask me this again” on it. See Following the Windows default audio device. |
| Enable / Disable Realtek ASIO | — | Only shown if a Realtek ASIO driver is installed. Lets you reverse the choice RemSound offered about disabling that driver (Realtek's generic ASIO driver tends to grab the wrong device and clash with your screen reader). |
-| Preferences… | Ctrl+P, or Alt+O, P | Opens the Preferences dialog, organised into five tabs (move between them with Ctrl+Tab, or the arrow keys when the tab names have focus): General — the profiles folder, accept remote volume commands, UPnP router opening, and “Show the volume, pan and EQ for peers tab” (on by default; hides the Volume, pan and EQ for peers tab if you untick it); Audio cues — the cue list and its sounds (see Audio cue sounds); Startup behaviour — start minimised / with Windows / with a specific profile; Update settings — the update checks and install options; and Logging — enable logs, write logs now, and the log-folder housekeeping (see Logs and diagnostics). Esc or the Close button dismisses it. |
+| Preferences… | Ctrl+P, or Alt+O, P | Opens the Preferences dialog, organised into five tabs (move between them with Ctrl+Tab, or the arrow keys when the tab names have focus): General — the profiles folder, accept remote volume commands, UPnP router opening, “Show the volume, pan and EQ for peers tab” (on by default; hides the Volume, pan and EQ for peers tab if you untick it), and Colour theme (Alt+T) — Match Windows (the default), Light, or Dark; this only changes how the window looks, takes effect the next time you start RemSound, and has no effect on the screen reader; Audio cues — the cue list and its sounds (see Audio cue sounds); Startup behaviour — start minimised / with Windows / with a specific profile; Update settings — the update checks and install options; and Logging — enable logs, write logs now, and the log-folder housekeeping (see Logs and diagnostics). Esc or the Close button dismisses it. |
Help menu
@@ -335,9 +335,9 @@ ul, ol { padding-left: 1.4em; }
| Connected peers | Alt+C | The people you currently have sound flowing with. Unticking a row disconnects that peer. |
| Peer details | Alt+E | A read-only box describing whichever connected peer you're on in the list above. Arrow through it to read: their name, their machine name, their IP address, how long you've been connected, the link health and ping, what they're sending (how many devices, on WASAPI or ASIO, at what sample rate and codec), and whether they're receiving your audio. The device and WASAPI/ASIO detail only shows while you're actually receiving that peer. |
| Rename peer | Alt+M or F2 | Give the highlighted peer a friendly name of your choosing. It opens a box with the name and a Clear custom name button. The name sticks to that machine for good — across restarts, IP changes and networks — and shows everywhere that peer appears: the lists here, the volume, pan and EQ for peers tab, the status line and split-recording filenames. See below. |
+| Add peer by IP | Alt+A | Opens a small box where you type an address or computer name. It adds that peer to the remembered list and connects. |
| Discovered peers | Alt+D | People RemSound has heard from in the last few seconds. Tick someone to connect to them. |
| Remembered peers | Alt+R | People you've connected to before, or added by address. This list is kept between sessions. Tick someone to reconnect. |
-| Add peer by IP | Alt+A | Opens a small box where you type an address or computer name. It adds that peer to the remembered list and connects. |
| Lock to these exact peer addresses | Alt+L | When ticked, this profile uses only the exact addresses you set and never follows the other computer by name or switches to a different address — even if the address stops working. Off by default, saved with the profile. See Locking a profile to one exact address. |
| Connection status | Alt+S | A read-only box of text that sums up everything happening right now — how long you've been connected, how many peers you have, how much sound is flowing each way, and the connection health of each peer. Open it to read the current connection status. |
@@ -358,20 +358,20 @@ ul, ol { padding-left: 1.4em; }
| Control | Shortcut | What it does |
| ASIO driver | Alt+D | A list that starts with (none). Pick (none) and the app uses the ordinary Windows sound path only; pick a real driver and the ASIO device lists appear below, and the Audio profile tab gains a second delay setting. If your computer has no ASIO drivers installed, this control is hidden completely. |
| Receive audio | Alt+R | The master switch for receiving. When it's off, no sound plays out, no matter which output devices are ticked. |
-| WASAPI outputs for received sound | Alt+3 | Tick which ordinary Windows outputs (speakers, headsets) should play the received sound. Ticking more than one means the received sound plays out of all of them at once. |
-| ASIO outputs for received sound | Alt+1 | (Shown when an ASIO driver is chosen.) Tick which ASIO channel pairs should play the received sound. |
-| Master receive volume | Alt+V | A slider: the master volume for everything coming in. There is no separate volume per device here; per-person volume lives on the Volume, pan and EQ for peers tab. |
+| WASAPI outputs for received audio | Alt+3 | Tick which ordinary Windows outputs (speakers, headsets) should play the received sound. Ticking more than one means the received sound plays out of all of them at once. |
+| ASIO outputs for received audio | Alt+1 | (Shown when an ASIO driver is chosen.) Tick which ASIO channel pairs should play the received sound. |
+| Master volume for received audio | Alt+V | A slider: the master volume for everything coming in. There is no separate volume per device here; per-person volume lives on the Volume, pan and EQ for peers tab. |
| Send my audio | Alt+S | The master switch for sending. |
-| WASAPI outputs to send | Alt+4 | Tick which Windows output devices to capture from — this captures whatever is currently playing on those speakers and sends it. |
-| WASAPI inputs to send | Alt+5 | Tick which Windows input devices to capture (microphones, line-ins). |
-| ASIO inputs to send | Alt+2 | (Shown when an ASIO driver is chosen.) Tick which ASIO channel pairs to capture and send. |
+| WASAPI audio outputs to send | Alt+4 | Tick which Windows output devices to capture from — this captures whatever is currently playing on those speakers and sends it. |
+| WASAPI audio inputs to send | Alt+5 | Tick which Windows input devices to capture (microphones, line-ins). |
+| ASIO audio inputs to send | Alt+2 | (Shown when an ASIO driver is chosen.) Tick which ASIO channel pairs to capture and send. |
All the device lists are checkable lists — tick or untick an item to include or exclude that device. Profiles save which devices are ticked; a new profile starts with everything unticked.
Following the Windows default audio device
-At the very top of the WASAPI outputs for received sound list and the WASAPI inputs to send list there's a special entry: Use Windows default audio device, follows Windows changes. Tick it and RemSound uses whatever Windows currently treats as the default — the default speakers for received sound, the default microphone for sending — and, the useful part, it follows that default on its own. Make a headset your default and RemSound switches to it; unplug it and RemSound moves back, all without you touching the list.
+At the very top of the WASAPI outputs for received audio list and the WASAPI audio inputs to send list there's a special entry: Use Windows default audio device, follows Windows changes. Tick it and RemSound uses whatever Windows currently treats as the default — the default speakers for received sound, the default microphone for sending — and, the useful part, it follows that default on its own. Make a headset your default and RemSound switches to it; unplug it and RemSound moves back, all without you touching the list.
It works alongside the specific devices: tick the default entry and particular cards, and the sound plays out of (or is captured from) all of them at once. When you first tick it, RemSound asks whether you'd like to untick the other devices in that list, so you can choose to use only the default — with a “Don't ask me this again” option. If you tick that and later want the question back, use Options → Reset the default audio device prompt.
@@ -396,19 +396,19 @@ ul, ol { padding-left: 1.4em; }
| List | What it captures | Typical use |
-| WASAPI outputs to send | Whatever Windows is currently playing through that output. So picking your “Speakers” device captures whatever you're hearing. | Sharing music playback, sharing the sound from a video call, anything coming out of your own speakers. |
-| WASAPI inputs to send | Sound captured straight from a microphone or line input. | Your USB microphone, a headset mic, a line-in. |
-| ASIO inputs to send | An ASIO channel pair — usually a hardware input on a professional audio interface. | An instrument input on an Audient EVO, a microphone preamp on a Focusrite, and so on. |
+| WASAPI audio outputs to send | Whatever Windows is currently playing through that output. So picking your “Speakers” device captures whatever you're hearing. | Sharing music playback, sharing the sound from a video call, anything coming out of your own speakers. |
+| WASAPI audio inputs to send | Sound captured straight from a microphone or line input. | Your USB microphone, a headset mic, a line-in. |
+| ASIO audio inputs to send | An ASIO channel pair — usually a hardware input on a professional audio interface. | An instrument input on an Audient EVO, a microphone preamp on a Focusrite, and so on. |
Tick any combination across the three lists. RemSound mixes them together into one stream and sends that to all your chosen peers. So you can send a mic plus a guitar plus your system sound all at once, mixed together, and your friends hear all three.
-Capturing your speakers can cause an echo loop. If you tick the same device both in “WASAPI outputs to send” and in “WASAPI outputs for received sound”, then the received sound plays out of that device, gets captured again, and gets sent back. The other person ends up hearing their own voice on a delay. Don't tick the same device on both sides at once.
+Capturing your speakers can cause an echo loop. If you tick the same device both in “WASAPI audio outputs to send” and in “WASAPI outputs for received audio”, then the received sound plays out of that device, gets captured again, and gets sent back. The other person ends up hearing their own voice on a delay. Don't tick the same device on both sides at once.
-If your microphone sends silence: Windows can block desktop apps from using the microphone, and when it does, RemSound's mic capture still switches on but only sends silence — so you look like you're sending, but the other person hears nothing. RemSound watches for this: when you tick a microphone in WASAPI inputs to send while Windows is blocking it — or load a profile that already has one ticked — a message pops up telling you, with the exact two settings to turn on — open Windows Settings → Privacy & security → Microphone, then turn on both Microphone access and Let desktop apps access your microphone. The check also catches the sneakier kinds of block: one aimed at RemSound alone in that same Settings page's per-app list, and one set by an administrator or workplace policy — that last kind doesn't show up as a switch you can flip, so if the warning says a policy is involved, it needs whoever manages the computer to lift it. (ASIO inputs aren't affected, because ASIO talks straight to the hardware and bypasses that Windows privacy gate.) It doesn't change anything you receive — only sending your own mic.
+If your microphone sends silence: Windows can block desktop apps from using the microphone, and when it does, RemSound's mic capture still switches on but only sends silence — so you look like you're sending, but the other person hears nothing. RemSound watches for this: when you tick a microphone in WASAPI audio inputs to send while Windows is blocking it — or load a profile that already has one ticked — a message pops up telling you, with the exact two settings to turn on — open Windows Settings → Privacy & security → Microphone, then turn on both Microphone access and Let desktop apps access your microphone. The check also catches the sneakier kinds of block: one aimed at RemSound alone in that same Settings page's per-app list, and one set by an administrator or workplace policy — that last kind doesn't show up as a switch you can flip, so if the warning says a policy is involved, it needs whoever manages the computer to lift it. (ASIO inputs aren't affected, because ASIO talks straight to the hardware and bypasses that Windows privacy gate.) It doesn't change anything you receive — only sending your own mic.
8. Audio profile tab
@@ -486,7 +486,7 @@ ul, ol { padding-left: 1.4em; }
- Tab past the mode picker to the Add band button and press it. A small dialog opens with three boxes: a start frequency, an end frequency and a gain in dB (from −12 to +12). Each box you can type into or spin with the arrow keys; they only accept sensible numbers. As you change the values you hear the band on that peer straight away. Press OK to add it, or Cancel / Escape to drop it.
-- Back on the tab, tab to the Bands list to hear your bands, one per row, each read out as its range and level — for example “200 Hz to 800 Hz, plus 3 dB”. The list is sorted low to high, so the bass bands are at the top and the treble at the bottom.
+- Back on the tab, tab to the Bands list to hear your bands, one per row, each read out as its range and level — for example “200 Hz to 800 Hz, plus 3 dB”. The list is sorted low to high, so the bass bands are at the top and the treble at the bottom. Up and down arrow move between bands; left and right arrow nudge the selected band's gain down or up by half a dB, so you can fine-tune it on the fly and hear the change straight away.
- To remove a band, land on it and press Delete, or use the Delete band button. You can select several at once (hold Shift and arrow, or hold Ctrl and arrow then Space to pick out individual ones) and delete them together.
@@ -823,11 +823,11 @@ Audient USB Audio ASIO Driver — Pair 3 (channels 5/6): Loop-back 1 (L) / L
| Key | Action |
| Alt+D | Focus the ASIO driver list (hidden if no ASIO drivers are installed) |
| Alt+R | Toggle Receive audio |
-| Alt+1 | Focus ASIO outputs for received sound |
-| Alt+2 | Focus ASIO inputs to send |
-| Alt+3 | Focus WASAPI outputs for received sound |
-| Alt+4 | Focus WASAPI outputs to send |
-| Alt+5 | Focus WASAPI inputs to send |
+| Alt+1 | Focus ASIO outputs for received audio |
+| Alt+2 | Focus ASIO audio inputs to send |
+| Alt+3 | Focus WASAPI outputs for received audio |
+| Alt+4 | Focus WASAPI audio outputs to send |
+| Alt+5 | Focus WASAPI audio inputs to send |
| Alt+V | Focus the volume slider |
| Alt+S | Toggle Send my audio |
@@ -1183,9 +1183,9 @@ Use whatever key combinations you prefer (for example Ctrl+Shift+Up / Ctrl+Shift
Recording captures the sound at fully-mixed, fully-finished points: for the received side, after volume and mute have been applied (so the file matches what you hear); for the sent side, the raw captured sound just before it's packaged for sending (so the file is the same whatever codec you chose). The three source choices:
-- Record all received audio — the full mix of everything coming in from connected peers. This is the default.
+- Record both sent and received audio — a single file with both directions mixed gently together. This is the default, and the right choice for capturing a complete two-way exchange.
+- Record all received audio — the full mix of everything coming in from connected peers.
- Record all sent audio — what your microphones, captured speakers and ASIO inputs are sending out. Useful for checking what your collaborators are actually hearing from you.
-- Record both sent and received audio — a single file with both directions mixed gently together. The right choice for capturing a complete two-way exchange.
File formats
@@ -1420,7 +1420,7 @@ RemSound.exe --connect 192.168.1.42
| Term | Meaning |
| WASAPI | The normal Windows way of handling sound. Every speaker and microphone in your Windows sound settings works this way. The delay it adds is around 10–30 ms. |
| ASIO | A faster, more direct way of handling sound, used by professional audio equipment. It talks straight to the hardware, giving a delay of under 5 ms. It only works if your audio interface came with an ASIO driver. |
-| Loopback capture | Capturing what's currently being played out of an output device, rather than what's coming in from a microphone. The “WASAPI outputs to send” list does loopback capture. |
+| Loopback capture | Capturing what's currently being played out of an output device, rather than what's coming in from a microphone. The “WASAPI audio outputs to send” list does loopback capture. |
| Channel pair | A stereo pair of channels on an ASIO driver. Pair 1 is channels 1 and 2, Pair 2 is channels 3 and 4, and so on. |
| Codec | The method RemSound uses to package the sound before sending it. RemSound offers PCM (no compression) and Opus (compressed). |
| Opus | A high-quality codec that compresses sound to use much less network bandwidth, and can repair single lost packets on its own. The right choice for internet connections. |
diff --git a/src/RemSound.App/AddBandDialog.cs b/src/RemSound.App/AddBandDialog.cs
index 7d12279..dd47bf8 100644
--- a/src/RemSound.App/AddBandDialog.cs
+++ b/src/RemSound.App/AddBandDialog.cs
@@ -76,14 +76,18 @@ internal sealed class AddBandDialog : Form
Dock = DockStyle.Fill,
Padding = new Padding(12),
ColumnCount = 2,
- RowCount = 4,
+ RowCount = 5,
};
grid.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
grid.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
- AddRow(grid, 0, "&Start frequency in Hz (Alt+S)", startFreq);
- AddRow(grid, 1, "&End frequency in Hz (Alt+E)", endFreq);
- AddRow(grid, 2, "&Gain in dB (Alt+G)", gainDb);
+ var heading = Theme.Heading("Add EQ band");
+ grid.Controls.Add(heading, 0, 0);
+ grid.SetColumnSpan(heading, 2);
+
+ AddRow(grid, 1, "&Start frequency in Hz (Alt+S)", startFreq);
+ AddRow(grid, 2, "&End frequency in Hz (Alt+E)", endFreq);
+ AddRow(grid, 3, "&Gain in dB (Alt+G)", gainDb);
var okButton = new Button { Text = "OK", AutoSize = true, DialogResult = DialogResult.None, TabIndex = 0 };
var cancelButton = new Button { Text = "Cancel", AutoSize = true, DialogResult = DialogResult.Cancel, TabIndex = 1 };
@@ -99,7 +103,7 @@ internal sealed class AddBandDialog : Form
};
buttonRow.Controls.Add(okButton);
buttonRow.Controls.Add(cancelButton);
- grid.Controls.Add(buttonRow, 1, 3);
+ grid.Controls.Add(buttonRow, 1, 4);
Controls.Add(grid);
AcceptButton = okButton;
@@ -109,6 +113,10 @@ internal sealed class AddBandDialog : Form
{
box.ValueChanged += (_, _) => Preview();
box.TextChanged += (_, _) => Preview();
+ // Select the whole value when the box gains focus, so a typed number REPLACES what's there
+ // instead of being inserted next to it (which parsed as junk and reverted). Deferred so it
+ // runs after focus settles.
+ box.Enter += (_, _) => box.BeginInvoke(() => box.Select(0, box.Text.Length));
}
Shown += (_, _) =>
diff --git a/src/RemSound.App/EqCurveControl.cs b/src/RemSound.App/EqCurveControl.cs
index decdb02..d8a062c 100644
--- a/src/RemSound.App/EqCurveControl.cs
+++ b/src/RemSound.App/EqCurveControl.cs
@@ -68,8 +68,19 @@ internal sealed class EqCurveControl : Panel
pts[x] = new PointF(x, y);
}
- using var curvePen = new Pen(dark ? Color.FromArgb(90, 200, 255) : Color.FromArgb(0, 120, 200), 2f);
- if (w >= 2) g.DrawLines(curvePen, pts);
+ Color curveColor = dark ? Color.FromArgb(90, 200, 255) : Color.FromArgb(0, 120, 200);
+ if (w >= 2)
+ {
+ // Soft translucent fill between the curve and the 0 dB line, then the line on top.
+ var fill = new PointF[pts.Length + 2];
+ Array.Copy(pts, fill, pts.Length);
+ fill[^2] = new PointF(w - 1, midY);
+ fill[^1] = new PointF(0, midY);
+ using (var fillBrush = new SolidBrush(Color.FromArgb(38, curveColor)))
+ g.FillPolygon(fillBrush, fill);
+ using var curvePen = new Pen(curveColor, 2f);
+ g.DrawLines(curvePen, pts);
+ }
}
/// Approximate combined EQ response in dB at one frequency. Cosmetic only — analytic bell /
diff --git a/src/RemSound.App/MainForm.cs b/src/RemSound.App/MainForm.cs
index 73642c2..2d5a584 100644
--- a/src/RemSound.App/MainForm.cs
+++ b/src/RemSound.App/MainForm.cs
@@ -211,6 +211,9 @@ public sealed class MainForm : Form
// Profile-management buttons retired 2026-05-08 — these actions live in File menu now.
// The methods (SaveProfileAs / UpdateExistingProfile) are still here; they're called from
// the menu item Click handlers in BuildFileMenu.
+ // A colour cue for connection health (green streaming / amber idle / grey disconnected) beside the
+ // health text. Purely visual — invisible to NVDA; the health text is unchanged.
+ private readonly StatusDot healthDot = new();
private readonly Label healthLabel = new() { Text = "Health: disconnected", AutoSize = true };
private readonly Label statusLabel = new() { Text = "Disconnected", AutoSize = true };
@@ -750,6 +753,7 @@ public sealed class MainForm : Form
Height = 600;
MinimumSize = new Size(560, 520);
StartPosition = FormStartPosition.CenterScreen;
+ if (Theme.AppIcon is { } appIcon) Icon = appIcon; // window title bar + Alt+Tab + taskbar
// No AccessibleName / AccessibleRole on the form. Andre's accessible app does not
// set these and NVDA reads cleanly there; setting them here was over-engineering.
@@ -881,16 +885,16 @@ public sealed class MainForm : Form
// AccessibleName text guarantees NVDA announces it consistently right after the
// control name. CheckBoxes own their own &-mnemonic via their Text and don't need
// the suffix in AccessibleName — they're left as bare names.
- volumeBar.AccessibleName = "Set volume for all received audio (Alt+V)";
- receiveOutputDevicesList.AccessibleName = "WASAPI outputs for received sound (Alt+3)";
+ volumeBar.AccessibleName = "Master volume for received audio (Alt+V)";
+ receiveOutputDevicesList.AccessibleName = "WASAPI outputs for received audio (Alt+3)";
receiveOutputDevicesStatusLabel.AccessibleName = "Selected receive output device status";
- sendOutputDevicesList.AccessibleName = "WASAPI outputs to send (Alt+4)";
+ sendOutputDevicesList.AccessibleName = "WASAPI audio outputs to send (Alt+4)";
sendOutputDevicesStatusLabel.AccessibleName = "Selected output device status";
- sendInputDevicesList.AccessibleName = "WASAPI inputs to send (Alt+5)";
+ sendInputDevicesList.AccessibleName = "WASAPI audio inputs to send (Alt+5)";
sendInputDevicesStatusLabel.AccessibleName = "Selected input device status";
- asioReceiveOutputDevicesList.AccessibleName = "ASIO outputs for received sound (Alt+1)";
+ asioReceiveOutputDevicesList.AccessibleName = "ASIO outputs for received audio (Alt+1)";
asioReceiveOutputDevicesStatusLabel.AccessibleName = "Selected ASIO receive channel status";
- asioSendDevicesList.AccessibleName = "ASIO inputs to send (Alt+2)";
+ asioSendDevicesList.AccessibleName = "ASIO audio inputs to send (Alt+2)";
asioSendDevicesStatusLabel.AccessibleName = "Selected ASIO send channel status";
// Keyboard shortcuts / Minimise to tray / Save / Save as buttons retired 2026-05-08
// (now File menu items in BuildFileMenu).
@@ -1769,6 +1773,7 @@ public sealed class MainForm : Form
WrapContents = false,
Padding = new Padding(8, 4, 8, 4),
};
+ statusPanel.Controls.Add(healthDot);
statusPanel.Controls.Add(healthLabel);
statusPanel.Controls.Add(new Label { Text = " ", AutoSize = true });
statusPanel.Controls.Add(statusLabel);
@@ -3013,24 +3018,32 @@ public sealed class MainForm : Form
// Preferences dialog (File → Preferences, Ctrl+P) as the last two items.
// === Layout ===
- // 8 rows: 0 connected peers, 1 the details box + 2 rename button (both about the highlighted
- // connected peer), 3–4 discovered & remembered lists, 5 manual-add, 6 the lock toggle, 7 status.
- panel.RowCount = 8;
- FormLayoutRows.AddCheckedListRow(panel, 0, "Connected peers (Alt+&C)", connectedPeersList, connectedPeersStatus, FocusListControl);
+ // Tab order (row order = add order): 0 "Peers" header; 1 connected peers; 2 details box; 3 rename;
+ // 4 add-by-IP; 5 discovered; 6 remembered; 7 lock toggle; 8 status. The "Peers" header is a
+ // visual grouping only. Add-by-IP sits with the connected-peer actions, per Ed's requested order.
+ panel.RowCount = 9;
+
+ var peersHeader = Theme.SectionHeader("Peers");
+ panel.Controls.Add(peersHeader, 0, 0);
+ panel.SetColumnSpan(peersHeader, 2);
+
+ FormLayoutRows.AddCheckedListRow(panel, 1, "Connected peers (Alt+&C)", connectedPeersList, connectedPeersStatus, FocusListControl);
// Details of, and a rename for, the peer highlighted in the connected list above.
var detailsLabel = new MnemonicLabel { Text = "Peer d&etails (Alt+E)", AutoSize = true, Anchor = AnchorStyles.Left, MnemonicTarget = peerDetailsBox };
detailsLabel.Click += (_, _) => peerDetailsBox.Focus();
- panel.Controls.Add(detailsLabel, 0, 1);
- panel.Controls.Add(peerDetailsBox, 1, 1);
+ panel.Controls.Add(detailsLabel, 0, 2);
+ panel.Controls.Add(peerDetailsBox, 1, 2);
renamePeerButton.Click += (_, _) => OnRenamePeer();
- panel.Controls.Add(renamePeerButton, 1, 2);
+ panel.Controls.Add(renamePeerButton, 1, 3);
connectedPeersList.SelectedIndexChanged += (_, _) => UpdatePeerDetails();
- FormLayoutRows.AddCheckedListRow(panel, 3, "Discovered peers (Alt+&D)", discoveredPeersList, discoveredPeersStatus, FocusListControl);
- FormLayoutRows.AddCheckedListRow(panel, 4, "Remembered peers (Alt+&R)", rememberedPeersList, rememberedPeersStatus, FocusListControl);
- panel.Controls.Add(new Label { Text = "Manual peer", AutoSize = true, Anchor = AnchorStyles.Left }, 0, 5);
- panel.Controls.Add(manualAddButton, 1, 5);
+ // Add a peer by address — grouped with the connected-peer actions, before the discovery lists.
+ panel.Controls.Add(new Label { Text = "Manual peer", AutoSize = true, Anchor = AnchorStyles.Left }, 0, 4);
+ panel.Controls.Add(manualAddButton, 1, 4);
+
+ FormLayoutRows.AddCheckedListRow(panel, 5, "Discovered peers (Alt+&D)", discoveredPeersList, discoveredPeersStatus, FocusListControl);
+ FormLayoutRows.AddCheckedListRow(panel, 6, "Remembered peers (Alt+&R)", rememberedPeersList, rememberedPeersStatus, FocusListControl);
lockPeerAddressesBox.Checked = settings.LoadLockPeerAddresses();
lockPeerAddressesBox.CheckedChanged += (_, _) =>
@@ -3039,14 +3052,14 @@ public sealed class MainForm : Form
MarkProfileDirty();
logFile.Event($"lock peer addresses: {(lockPeerAddressesBox.Checked ? "on" : "off")}");
};
- panel.Controls.Add(lockPeerAddressesBox, 0, 6);
+ panel.Controls.Add(lockPeerAddressesBox, 0, 7);
panel.SetColumnSpan(lockPeerAddressesBox, 2);
// Connection status readout — last row, tab-into-able.
var statusLabel = new MnemonicLabel { Text = "Connection status (Alt+&S)", AutoSize = true, Anchor = AnchorStyles.Left, MnemonicTarget = statusReadout };
statusLabel.Click += (_, _) => statusReadout.Focus();
- panel.Controls.Add(statusLabel, 0, 7);
- panel.Controls.Add(statusReadout, 1, 7);
+ panel.Controls.Add(statusLabel, 0, 8);
+ panel.Controls.Add(statusReadout, 1, 8);
UpdatePeerDetails();
// Initial render so the box has content the moment the user tabs into it.
@@ -3112,15 +3125,15 @@ public sealed class MainForm : Form
var receiveCheckboxPanel = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Fill };
receiveCheckboxPanel.Controls.Add(receiveAudioCheckbox);
panel.Controls.Add(receiveCheckboxPanel, 1, 2);
- receiveOutputDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 3, "WASAPI outputs for received sound (Alt+&3)", receiveOutputDevicesList, receiveOutputDevicesStatusLabel, FocusListControl);
- asioReceiveOutputDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 4, "ASIO outputs for received sound (Alt+&1)", asioReceiveOutputDevicesList, asioReceiveOutputDevicesStatusLabel, FocusListControl);
- FormLayoutRows.AddRow(panel, 5, "Set volume for all received audio (Alt+&V)", volumeBar, FocusControl);
+ receiveOutputDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 3, "WASAPI outputs for received audio (Alt+&3)", receiveOutputDevicesList, receiveOutputDevicesStatusLabel, FocusListControl);
+ asioReceiveOutputDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 4, "ASIO outputs for received audio (Alt+&1)", asioReceiveOutputDevicesList, asioReceiveOutputDevicesStatusLabel, FocusListControl);
+ FormLayoutRows.AddRow(panel, 5, "Master volume for received audio (Alt+&V)", volumeBar, FocusControl);
var sendCheckboxPanel = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Fill };
sendCheckboxPanel.Controls.Add(sendMyAudioCheckbox);
panel.Controls.Add(sendCheckboxPanel, 1, 6);
- sendOutputDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 7, "WASAPI outputs to send (Alt+&4)", sendOutputDevicesList, sendOutputDevicesStatusLabel, FocusListControl);
- sendInputDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 8, "WASAPI inputs to send (Alt+&5)", sendInputDevicesList, sendInputDevicesStatusLabel, FocusListControl);
- asioSendDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 9, "ASIO inputs to send (Alt+&2)", asioSendDevicesList, asioSendDevicesStatusLabel, FocusListControl);
+ sendOutputDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 7, "WASAPI audio outputs to send (Alt+&4)", sendOutputDevicesList, sendOutputDevicesStatusLabel, FocusListControl);
+ sendInputDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 8, "WASAPI audio inputs to send (Alt+&5)", sendInputDevicesList, sendInputDevicesStatusLabel, FocusListControl);
+ asioSendDevicesLabel = FormLayoutRows.AddCheckedListRow(panel, 9, "ASIO audio inputs to send (Alt+&2)", asioSendDevicesList, asioSendDevicesStatusLabel, FocusListControl);
audioIOTabPage.Controls.Add(panel);
}
@@ -3697,6 +3710,24 @@ public sealed class MainForm : Form
OnDeleteParametricBands();
e.Handled = true;
e.SuppressKeyPress = true;
+ return;
+ }
+ // Left / Right nudge the selected band(s) gain by half a dB — quick on-the-fly editing, heard
+ // in real time. Up / Down still move between bands as normal.
+ if ((e.KeyCode == Keys.Left || e.KeyCode == Keys.Right)
+ && selectedShapingKey is not null && parametricBandList.SelectedItems.Count > 0)
+ {
+ float delta = e.KeyCode == Keys.Right ? 0.5f : -0.5f;
+ foreach (var it in parametricBandList.SelectedItems.Cast())
+ it.Band.GainDb = Math.Clamp(it.Band.GainDb + delta, -PeerEqBands.MaxGainDb, PeerEqBands.MaxGainDb);
+ parametricBandList.Invalidate(); // redraw the rows with their updated dB (ToString reads live)
+ ApplyPeerShaping(selectedShapingKey);
+ UpdateEqCurve();
+ MarkProfileDirty();
+ // Make NVDA re-read the focused band with its new dB value.
+ WinEventNotifier.NotifyFocus(parametricBandList);
+ e.Handled = true;
+ e.SuppressKeyPress = true;
}
}
@@ -4430,13 +4461,16 @@ public sealed class MainForm : Form
ClientSize = new Size(600, 420),
};
- var root = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(12), ColumnCount = 1, RowCount = 4 };
+ var root = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(12), ColumnCount = 1, RowCount = 5 };
root.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
+ root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
+ root.Controls.Add(Theme.Heading("Named peers"), 0, 0);
+
var intro = new Label
{
Text = "The peers you've given a name to. Rename or delete any of them. Deleting forgets the "
@@ -4445,14 +4479,14 @@ public sealed class MainForm : Form
MaximumSize = new Size(560, 0),
Margin = new Padding(0, 0, 0, 8),
};
- root.Controls.Add(intro, 0, 0);
+ root.Controls.Add(intro, 0, 1);
// Plain label with '&' focuses the next control in tab order (the list).
var peersLabel = new Label { Text = "&Peers", AutoSize = true, Anchor = AnchorStyles.Left };
- root.Controls.Add(peersLabel, 0, 1);
+ root.Controls.Add(peersLabel, 0, 2);
var list = new ListBox { Dock = DockStyle.Fill, IntegralHeight = false, AccessibleName = "Peers (Alt+P)", TabIndex = 0 };
- root.Controls.Add(list, 0, 2);
+ root.Controls.Add(list, 0, 3);
var renameBtn = new Button { Text = "&Rename (Alt+R)", AutoSize = true, AccessibleName = "Rename", TabIndex = 1 };
var deleteBtn = new Button { Text = "&Delete (Alt+D)", AutoSize = true, AccessibleName = "Delete", TabIndex = 2 };
@@ -4461,7 +4495,7 @@ public sealed class MainForm : Form
buttonRow.Controls.Add(renameBtn);
buttonRow.Controls.Add(deleteBtn);
buttonRow.Controls.Add(closeBtn);
- root.Controls.Add(buttonRow, 0, 3);
+ root.Controls.Add(buttonRow, 0, 4);
dialog.Controls.Add(root);
dialog.AcceptButton = closeBtn;
@@ -6605,9 +6639,11 @@ public sealed class MainForm : Form
var peerCount = knownPeers.Count;
var hbSummary = heartbeatService?.GetHealthSummary() ?? "no peers";
statusLabel.Text = $"Connected for {since}. {peerCount} peer(s) known. {sendText}. {receiveText}. Heartbeat: {hbSummary}.";
+ bool streaming = connected && (sender.IsRunning || receiver.IsRunning);
healthLabel.Text = connected
- ? sender.IsRunning || receiver.IsRunning ? "Health: streaming" : "Health: idle"
+ ? streaming ? "Health: streaming" : "Health: idle"
: "Health: disconnected";
+ healthDot.SetColor(!connected ? Theme.Neutral : streaming ? Theme.Healthy : Theme.Warning);
}
private void SnapshotLogIfDue()
diff --git a/src/RemSound.App/MainFormTrayController.cs b/src/RemSound.App/MainFormTrayController.cs
index cb1e62d..8cd6e32 100644
--- a/src/RemSound.App/MainFormTrayController.cs
+++ b/src/RemSound.App/MainFormTrayController.cs
@@ -83,7 +83,7 @@ internal sealed class MainFormTrayController : IDisposable
// user hid + re-showed the icon. By computing the right text once, just before
// we set Visible = true for the first time, the shell sees the live state from
// NIM_ADD onward.
- trayIcon.Icon = SystemIcons.Application;
+ trayIcon.Icon = Theme.AppIcon ?? SystemIcons.Application;
trayIcon.Visible = false;
trayIcon.DoubleClick += (_, _) => Restore();
diff --git a/src/RemSound.App/PreferencesDialog.cs b/src/RemSound.App/PreferencesDialog.cs
index 95b42ea..4ac5df1 100644
--- a/src/RemSound.App/PreferencesDialog.cs
+++ b/src/RemSound.App/PreferencesDialog.cs
@@ -278,6 +278,15 @@ internal sealed class PreferencesDialog : Form
AutoSize = true,
};
+ // Colour theme picker — "Match Windows (system)" / Light / Dark. Applied at startup via
+ // Application.SetColorMode, so a change takes effect on the next launch. Purely visual.
+ private readonly ComboBox themeBox = new()
+ {
+ DropDownStyle = ComboBoxStyle.DropDownList,
+ Width = 200,
+ AccessibleName = "Colour theme (Alt+T)",
+ };
+
private readonly Label upnpStatusLabel = new()
{
Text = "",
@@ -620,6 +629,26 @@ internal sealed class PreferencesDialog : Form
try { cfg.Save(); } catch { /* harmless — choice just won't survive a restart */ }
};
+ themeBox.Items.AddRange(["Match Windows (system)", "Light", "Dark"]);
+ themeBox.SelectedIndex = (cfgForLoad.ThemeMode ?? "system").Trim().ToLowerInvariant() switch
+ {
+ "light" => 1,
+ "dark" => 2,
+ _ => 0,
+ };
+ themeBox.SelectedIndexChanged += (_, _) =>
+ {
+ var cfg = AppConfig.Load();
+ cfg.ThemeMode = themeBox.SelectedIndex switch { 1 => "light", 2 => "dark", _ => "system" };
+ try { cfg.Save(); } catch { /* harmless — choice just won't survive a restart */ }
+ };
+ var themeLabel = new MnemonicLabel { Text = "Colour &theme (Alt+T)", AutoSize = true, Anchor = AnchorStyles.Left, MnemonicTarget = themeBox };
+ var themeHint = new Label { Text = "— takes effect next launch", AutoSize = true, ForeColor = SystemColors.GrayText, Anchor = AnchorStyles.Left, Padding = new Padding(8, 4, 0, 0) };
+ var themeRow = new FlowLayoutPanel { AutoSize = true, WrapContents = false, Margin = new Padding(0, 4, 0, 0) };
+ themeRow.Controls.Add(themeLabel);
+ themeRow.Controls.Add(themeBox);
+ themeRow.Controls.Add(themeHint);
+
// Live UPnP status — the RouterPortMapper raises StatusChanged from a thread-pool
// thread, so marshal back onto the UI thread before touching the label. Subscribe
// on show and unsubscribe on close to avoid leaking the handler past the dialog.
@@ -813,7 +842,7 @@ internal sealed class PreferencesDialog : Form
// is a field (declared above) so OnShown can focus it when the dialog opens. Logging is its
// own tab (2026-06-19); the two logging controls moved off the General tab to lead it.
tabs.TabPages.Add(MakeTab("General",
- browseProfilesFolderButton, acceptRemoteVolumeBox, upnpEnabledBox, upnpStatusLabel, showPanEqTabBox));
+ themeRow, browseProfilesFolderButton, acceptRemoteVolumeBox, upnpEnabledBox, upnpStatusLabel, showPanEqTabBox));
tabs.TabPages.Add(MakeTab("Audio cues", cueGroup));
tabs.TabPages.Add(MakeTab("Startup behaviour",
startMinimisedBox, startWithUserBox, startWithProfileBox, startupListPanel));
diff --git a/src/RemSound.App/Program.cs b/src/RemSound.App/Program.cs
index 2302d11..5bbfb31 100644
--- a/src/RemSound.App/Program.cs
+++ b/src/RemSound.App/Program.cs
@@ -85,6 +85,23 @@ internal static class Program
ApplicationConfiguration.Initialize();
+ // Follow the user's chosen colour theme — "system" (match Windows light/dark) by default. This
+ // is an experimental WinForms API; guarded so any failure just leaves the classic light theme
+ // rather than stopping RemSound launching. Colours only — no effect on the screen reader.
+ try
+ {
+ var mode = (AppConfig.Load().ThemeMode ?? "system").Trim().ToLowerInvariant();
+#pragma warning disable WFO5001
+ Application.SetColorMode(mode switch
+ {
+ "light" => SystemColorMode.Classic,
+ "dark" => SystemColorMode.Dark,
+ _ => SystemColorMode.System,
+ });
+#pragma warning restore WFO5001
+ }
+ catch { /* older runtime or API change — stay on the classic light theme */ }
+
// Consolidate every older layout (loose files, or the interim config\ folder) into the single
// "user settings and logs" folder before anything reads config/profiles/logs. Idempotent +
// best-effort; upgrades users from any older build. Shown to the user once if files moved.
diff --git a/src/RemSound.App/RecordingSettingsDialog.cs b/src/RemSound.App/RecordingSettingsDialog.cs
index af14ad9..6b29cf8 100644
--- a/src/RemSound.App/RecordingSettingsDialog.cs
+++ b/src/RemSound.App/RecordingSettingsDialog.cs
@@ -197,7 +197,7 @@ internal sealed class RecordingSettingsDialog : Form
sourceList.SelectedIndexChanged += (_, _) =>
{
if (sourceList.SelectedIndex < 0) return;
- working.Source = (RecordingSource)sourceList.SelectedIndex;
+ working.Source = SourceOrder[sourceList.SelectedIndex];
};
formatList.SelectedIndexChanged += (_, _) =>
@@ -372,10 +372,11 @@ internal sealed class RecordingSettingsDialog : Form
{
sourceList.BeginUpdate();
sourceList.Items.Clear();
- // Order MUST match RecordingSource enum values 0/1/2.
+ // Display order is decoupled from the enum via SourceOrder (both first as the default, then
+ // received, then sent). Add the labels in that same order.
+ sourceList.Items.Add("Record both sent and received audio");
sourceList.Items.Add("Record all received audio");
sourceList.Items.Add("Record all sent audio");
- sourceList.Items.Add("Record both sent and received audio");
sourceList.EndUpdate();
}
@@ -489,9 +490,14 @@ internal sealed class RecordingSettingsDialog : Form
channelList.EndUpdate();
}
+ // Display order for the recording-source list (decoupled from the RecordingSource enum values): both
+ // at the top as the default, then received, then sent.
+ private static readonly RecordingSource[] SourceOrder =
+ [RecordingSource.Both, RecordingSource.ReceivedOnly, RecordingSource.SentOnly];
+
private void SelectFromSource(RecordingSource src)
{
- var idx = (int)src;
+ var idx = Array.IndexOf(SourceOrder, src);
if (idx >= 0 && idx < sourceList.Items.Count) sourceList.SelectedIndex = idx;
}
diff --git a/src/RemSound.App/RemSound.App.csproj b/src/RemSound.App/RemSound.App.csproj
index 70c7c5e..029d7f6 100644
--- a/src/RemSound.App/RemSound.App.csproj
+++ b/src/RemSound.App/RemSound.App.csproj
@@ -10,6 +10,10 @@
app.manifest
true
SystemAware
+
+ Segoe UI, 9pt
+ remsound.ico
+
diff --git a/src/RemSound.App/RenamePeerDialog.cs b/src/RemSound.App/RenamePeerDialog.cs
index e11b048..b0bcb58 100644
--- a/src/RemSound.App/RenamePeerDialog.cs
+++ b/src/RemSound.App/RenamePeerDialog.cs
@@ -33,11 +33,15 @@ internal sealed class RenamePeerDialog : Form
Dock = DockStyle.Fill,
Padding = new Padding(12),
ColumnCount = 2,
- RowCount = 3,
+ RowCount = 4,
};
grid.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
grid.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
+ var heading = Theme.Heading("Rename peer");
+ grid.Controls.Add(heading, 0, 0);
+ grid.SetColumnSpan(heading, 2);
+
var machineLabel = new Label
{
Text = $"Machine name: {machineName}",
@@ -45,13 +49,13 @@ internal sealed class RenamePeerDialog : Form
Anchor = AnchorStyles.Left,
Margin = new Padding(0, 0, 0, 8),
};
- grid.Controls.Add(machineLabel, 0, 0);
+ grid.Controls.Add(machineLabel, 0, 1);
grid.SetColumnSpan(machineLabel, 2);
// Plain label with '&' wires the mnemonic to the next control in tab order (the text box).
var nameLabel = new Label { Text = "Friendly &name (Alt+N)", AutoSize = true, Anchor = AnchorStyles.Left, Margin = new Padding(0, 4, 8, 0) };
- grid.Controls.Add(nameLabel, 0, 1);
- grid.Controls.Add(nameBox, 1, 1);
+ grid.Controls.Add(nameLabel, 0, 2);
+ grid.Controls.Add(nameBox, 1, 2);
var clearButton = new Button { Text = "&Clear custom name (Alt+C)", AutoSize = true, AccessibleName = "Clear custom name", TabIndex = 1 };
var okButton = new Button { Text = "OK", AutoSize = true, DialogResult = DialogResult.OK, TabIndex = 2 };
@@ -73,7 +77,7 @@ internal sealed class RenamePeerDialog : Form
buttonRow.Controls.Add(clearButton);
buttonRow.Controls.Add(okButton);
buttonRow.Controls.Add(cancelButton);
- grid.Controls.Add(buttonRow, 1, 2);
+ grid.Controls.Add(buttonRow, 1, 3);
Controls.Add(grid);
AcceptButton = okButton;
diff --git a/src/RemSound.App/Theme.cs b/src/RemSound.App/Theme.cs
new file mode 100644
index 0000000..6744357
--- /dev/null
+++ b/src/RemSound.App/Theme.cs
@@ -0,0 +1,115 @@
+using System.Drawing.Drawing2D;
+
+namespace RemSound.App;
+
+/// Central visual style for RemSound. The app font is set globally (csproj
+/// ApplicationDefaultFont) and dark/light follows the OS (Application.SetColorMode in Program.Main), so
+/// this only holds the extra touches: an accent colour, section-header + dialog-heading label factories,
+/// and health-status colours. All of it is visual only — it changes no control types and no
+/// accessibility wiring, so the screen-reader experience is untouched.
+internal static class Theme
+{
+ private static Icon? appIcon;
+ private static bool triedIcon;
+
+ /// The RemSound window icon, loaded once from the embedded multi-size .ico so title bars,
+ /// the taskbar, Alt+Tab and the tray icon all render crisply. Null if it can't be loaded.
+ public static Icon? AppIcon
+ {
+ get
+ {
+ if (!triedIcon)
+ {
+ triedIcon = true;
+ try
+ {
+ var asm = typeof(Theme).Assembly;
+ var name = Array.Find(asm.GetManifestResourceNames(),
+ n => n.EndsWith("remsound.ico", StringComparison.OrdinalIgnoreCase));
+ if (name is not null)
+ {
+ using var s = asm.GetManifestResourceStream(name);
+ if (s is not null) appIcon = new Icon(s);
+ }
+ }
+ catch { appIcon = null; }
+ }
+ return appIcon;
+ }
+ }
+
+ /// True when the app is currently showing on a dark background. Cosmetic heuristic
+ /// (mirrors ) — used to pick contrasting accent/health colours.
+ public static bool IsDark => SystemColors.Window.GetBrightness() < 0.5f;
+
+ /// Accent used for section headings and the EQ curve. Lighter blue in dark mode.
+ public static Color Accent => IsDark ? Color.FromArgb(88, 166, 255) : Color.FromArgb(0, 99, 177);
+
+ public static Color Healthy => IsDark ? Color.FromArgb(70, 200, 100) : Color.FromArgb(24, 140, 56);
+ public static Color Warning => IsDark ? Color.FromArgb(232, 184, 64) : Color.FromArgb(176, 120, 0);
+ public static Color Bad => IsDark ? Color.FromArgb(240, 100, 100) : Color.FromArgb(190, 40, 40);
+ public static Color Neutral => SystemColors.GrayText;
+
+ /// A bold, accent-coloured section header — groups a run of rows on the busier tabs. Not a
+ /// tab stop and carries no shortcut; it's static text the screen reader simply reads as a heading.
+ public static Label SectionHeader(string text) => new()
+ {
+ Text = text,
+ AutoSize = true,
+ Font = new Font("Segoe UI Semibold", 9.75f, FontStyle.Bold),
+ ForeColor = Accent,
+ Margin = new Padding(0, 10, 0, 2),
+ Anchor = AnchorStyles.Left,
+ };
+
+ /// A bold heading label for the top of a dialog.
+ public static Label Heading(string text) => new()
+ {
+ Text = text,
+ AutoSize = true,
+ Font = new Font("Segoe UI Semibold", 11f, FontStyle.Bold),
+ Margin = new Padding(0, 0, 0, 8),
+ Anchor = AnchorStyles.Left,
+ };
+}
+
+/// A small filled circle used as a colour cue (e.g. connection health) beside a text label. It
+/// is deliberately invisible to the screen reader — not a tab stop, empty accessible name, Graphic role
+/// — so the neighbouring text (which NVDA does read) remains the single source of truth.
+internal sealed class StatusDot : Control
+{
+ private Color dot = SystemColors.GrayText;
+
+ public StatusDot()
+ {
+ TabStop = false;
+ SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer
+ | ControlStyles.UserPaint | ControlStyles.SupportsTransparentBackColor, true);
+ Size = new Size(12, 12);
+ Margin = new Padding(0, 5, 6, 0);
+ AccessibleName = "";
+ }
+
+ public void SetColor(Color c)
+ {
+ if (dot == c) return;
+ dot = c;
+ Invalidate();
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
+ int d = Math.Min(Width, Height) - 2;
+ using var b = new SolidBrush(dot);
+ e.Graphics.FillEllipse(b, (Width - d) / 2f, (Height - d) / 2f, d, d);
+ }
+
+ protected override AccessibleObject CreateAccessibilityInstance() => new HiddenAcc(this);
+
+ private sealed class HiddenAcc(Control owner) : ControlAccessibleObject(owner)
+ {
+ public override AccessibleRole Role => AccessibleRole.Graphic;
+ public override string? Name { get => ""; set { } }
+ }
+}
diff --git a/src/RemSound.Core/AppConfig.cs b/src/RemSound.Core/AppConfig.cs
index 2b095b0..cc86ccf 100644
--- a/src/RemSound.Core/AppConfig.cs
+++ b/src/RemSound.Core/AppConfig.cs
@@ -75,6 +75,11 @@ public sealed class AppConfig
/// .
public bool ShowPanEqTab { get; set; } = true;
+ /// Which colour theme the window uses: "system" (follow Windows light/dark — the default),
+ /// "light", or "dark". Read once at startup by Program.Main and applied via Application.SetColorMode;
+ /// changing it takes effect on the next launch. Machine-wide, purely visual.
+ public string ThemeMode { get; set; } = "system";
+
/// The machine-wide "named peers" book — peers the user has deliberately renamed, keyed by
/// the peer's stable identity (machine name, or address for a nameless manual peer). A name applies in
/// every profile and shows wherever that peer appears. Managed via the Connectivity tab's Rename peer
diff --git a/src/RemSound.Core/RecordingTypes.cs b/src/RemSound.Core/RecordingTypes.cs
index 04dda22..98c13e4 100644
--- a/src/RemSound.Core/RecordingTypes.cs
+++ b/src/RemSound.Core/RecordingTypes.cs
@@ -60,7 +60,7 @@ public enum RecordingChannelMode
///
public sealed class RecordingSettings
{
- public RecordingSource Source { get; set; } = RecordingSource.ReceivedOnly;
+ public RecordingSource Source { get; set; } = RecordingSource.Both;
public RecordingFileFormat FileFormat { get; set; } = RecordingFileFormat.Wav;
public RecordingChannelMode ChannelMode { get; set; } = RecordingChannelMode.Stereo;