From 45b709d49ddaa77d7810b2d9e0f12237ec86c499 Mon Sep 17 00:00:00 2001 From: Ednunp <29843396+Ednunp@users.noreply.github.com> Date: Fri, 15 May 2026 13:40:42 +0100 Subject: [PATCH] Recording-settings dialog rework + menu mnemonic clean-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recording settings dialog ========================= Promote what were row attributes inside the format-attributes table into their own dedicated listboxes. Pre-rework each format's attribute list contained a row per (quality knob × channel mode) combination — doubling the row count for no real benefit. WAV had 6 rows, MP3 / OGG-Opus had 8, FLAC had 4. Channel mode is now its own listbox; per-format attribute lists shrink to 3 (WAV), 4 (MP3 / OGG-Opus), 2 (FLAC). FLAC compression level was previously a hidden RecordingSettings field locked at the libFLAC default of 5. Now exposed as its own conditional listbox with all 9 levels (0..8). Visible only when the selected file format is FLAC; the column collapses to 0% width otherwise so the dialog reads cleanly as 4 columns for non-FLAC formats and 5 for FLAC. Mnemonics: * Source Alt+S (unchanged) * File format Alt+F (unchanged) * Attributes Alt+A (unchanged) * Compression Alt+L (new, FLAC-only visible) * Channels Alt+C (new) * OK Alt+O (unchanged) * Cancel Alt+N (was Alt+C — moved to free C for Channels) Record menu mnemonics ===================== Item mnemonics inside the Record menu reorganised so the keystroke inside the menu matches the same letter used for Ctrl+R outside it, and Settings reads naturally: * Start recording / Stop recording Alt+R (was Alt+S) * Recording settings Alt+S (was Alt+T) * Open recordings folder Alt+O (unchanged) * Change recordings folder Alt+C (unchanged) UpdateStartStopRecordingMenuLabel keeps the underline on a literal R in both states ("Sta&rt recording" → "Stop &recording") so the keystroke does the same job regardless of which label is active. No version bump — the running version stays at 1.3.0. These changes will be bundled with the next public release alongside whatever else is in flight. Co-Authored-By: Claude Opus 4.7 --- readme.html | 18 +- src/RemSound.App/MainForm.cs | 16 +- src/RemSound.App/RecordingSettingsDialog.cs | 321 ++++++++++++++------ 3 files changed, 249 insertions(+), 106 deletions(-) diff --git a/readme.html b/readme.html index 75d5f3b..6532bb5 100644 --- a/readme.html +++ b/readme.html @@ -196,8 +196,8 @@ ul, ol { padding-left: 1.4em; } - - + +
ItemShortcutWhat it does
Start recording / Stop recordingCtrl+R, or Alt+O, SToggle. The label flips between the two states so NVDA announces whichever action the next press would perform. Each press of Start plays a short cue sound (if enabled in Preferences), then creates a new file in your recordings folder named RemSound-yyyy-MM-dd_HH-mm-ss.<ext>. Stop closes the file then plays the stop cue. Ctrl+R works from anywhere in the main window.
Recording settings…Alt+O, TOpens the Recording settings dialog. Three listboxes side-by-side: Recording source (Alt+S), File format (Alt+F), Audio format attributes (Alt+A). The attributes list repopulates based on the selected format. OK saves to the current profile; Cancel discards.
Start recording / Stop recordingCtrl+R, or Alt+O, RToggle. The label flips between “Start recording” and “Stop recording” so NVDA announces whichever action the next press would perform. Either label is activated by Alt+R (the underline stays on a letter R in both). Each press of Start plays a short cue sound (if enabled in Preferences), then creates a new file in your recordings folder named RemSound-yyyy-MM-dd_HH-mm-ss.<ext>. Stop closes the file then plays the stop cue. Ctrl+R works from anywhere in the main window.
Recording settings…Alt+O, SOpens the Recording settings dialog. Up to five listboxes: Recording source (Alt+S), File format (Alt+F), Audio format attributes (Alt+A), FLAC compression level (Alt+L — visible only when FLAC is selected), Channels (Alt+C). The attributes list repopulates based on the selected format. OK saves to the current profile; Cancel discards.
Open current recordings folderAlt+O, OOpens your current recordings folder in Windows Explorer. Creates the folder if it doesn’t exist yet (first launch after a fresh install).
Change recordings folder…Alt+O, CFolder picker. Pick a different folder for future recordings. The choice is saved on the current profile, so different profiles can record to different places.
@@ -612,8 +612,8 @@ Audient USB Audio ASIO Driver — Pair 3 (channels 5/6): Loop-back 1 (L) / L Ctrl+KOpen Keyboard shortcuts dialog Ctrl+POpen Preferences dialog Ctrl+RStart or stop recording (toggles) -Alt+O, SStart or stop recording (via menu — Record menu is Alt+O, item is S) -Alt+O, TRecording settings dialog +Alt+O, RStart or stop recording (via menu — Record menu is Alt+O, item is R for “recording”) +Alt+O, SRecording settings dialog Alt+O, OOpen current recordings folder Alt+O, CChange recordings folder Alt+F, OOpen profile @@ -829,16 +829,18 @@ Bind whatever key combinations you prefer (e.g. Ctrl+Shift+Up / Ctrl+Shift+Down

Recording settings dialog

-

Reached via Record → Recording settings. Three keyboard-navigable listboxes:

+

Reached via Record → Recording settings. Up to five keyboard-navigable listboxes laid out left-to-right. Channel mode used to be folded into every attribute row, which doubled their length for no real gain; it’s now its own dedicated list. FLAC’s compression level has its own list too, but it only shows when the file format is FLAC.

- - + + + +
ListShortcutWhat goes in it
Recording sourceAlt+SReceive only / Send only / Both. See the source explainer above.
File formatAlt+FWAV / MP3 / Ogg-Opus / FLAC. The attributes list to the right repopulates whenever you change this.
Audio format attributesAlt+ARepopulates per format, with the 48 kHz sample rate stated on every row so there’s no ambiguity. WAV: six rows pairing bit-depth (16 / 24 / 32-bit float) with channel mode (stereo / mono). MP3: eight rows pairing CBR bitrate (128 / 192 / 256 / 320 kbps) with channel mode. OGG-Opus: eight rows pairing VBR bitrate (96 / 128 / 192 / 256 kbps) with channel mode. FLAC: four rows pairing bit-depth (16 / 24-bit) with channel mode — FLAC is integer-PCM only, so no 32-bit-float row. Compression level for FLAC is fixed at 5 (the libFLAC reference default).
File formatAlt+FWAV / MP3 / Ogg-Opus / FLAC. The attributes (and FLAC compression) lists to the right repopulate whenever you change this.
Audio format attributesAlt+ARepopulates per format, with the 48 kHz sample rate stated on every row so there’s no ambiguity. WAV: three rows (16-bit PCM / 24-bit PCM / 32-bit float). MP3: four rows (128 / 192 / 256 / 320 kbps CBR). OGG-Opus: four rows (96 / 128 / 192 / 256 kbps VBR). FLAC: two rows (16-bit / 24-bit) — FLAC is integer-PCM only, so no 32-bit-float row.
FLAC compression levelAlt+LVisible only when FLAC is the selected file format. Nine rows for levels 0..8, with friendly labels on the endpoints (“0 — fastest encode, biggest file”, “5 — default (libFLAC reference)”, “8 — slowest encode, smallest file”). All levels produce bit-identical lossless audio — it’s a pure encode-time vs file-size trade-off.
ChannelsAlt+CStereo or Mono. Applies to every format.
-

OK saves the choices to the current profile. Cancel (or Esc) discards. Settings persist via the normal profile-save mechanism — changes here flag the profile as having unsaved changes and you’ll be prompted on exit if you haven’t saved.

+

OK (Alt+O) saves the choices to the current profile. Cancel (Alt+N) or Esc discards. Settings persist via the normal profile-save mechanism — changes here flag the profile as having unsaved changes and you’ll be prompted on exit if you haven’t saved.

19. Logs and diagnostics

diff --git a/src/RemSound.App/MainForm.cs b/src/RemSound.App/MainForm.cs index e787406..af1b5cd 100644 --- a/src/RemSound.App/MainForm.cs +++ b/src/RemSound.App/MainForm.cs @@ -1139,14 +1139,20 @@ public sealed class MainForm : Form // / Record) and reads as "Recording" naturally enough for the mnemonic to stick. var recordMenu = new ToolStripMenuItem("Rec&ord") { AccessibleName = "Record menu" }; - startStopRecordingMenuItem = new ToolStripMenuItem("&Start recording") + // Start/Stop uses Alt+R — matches the Ctrl+R global toggle so the same letter does + // the same job from either entry point. The "&" position shifts when the label flips + // (Sta&rt → Stop &recording) so the underline stays on an R in both states. See + // UpdateStartStopRecordingMenuLabel for the runtime label flip. + startStopRecordingMenuItem = new ToolStripMenuItem("Sta&rt recording") { ShortcutKeys = Keys.Control | Keys.R, AccessibleName = "Start recording", }; startStopRecordingMenuItem.Click += (_, _) => ToggleRecording(); - var settingsItem = new ToolStripMenuItem("Recording se&ttings...") + // Recording settings → Alt+S (was Alt+T). S reads more naturally than T for + // "settings", and the slot freed up when Start/Stop moved off Alt+S. + var settingsItem = new ToolStripMenuItem("Recording &settings...") { AccessibleName = "Recording settings", }; @@ -1216,7 +1222,11 @@ public sealed class MainForm : Form void Apply() { if (startStopRecordingMenuItem is null) return; - startStopRecordingMenuItem.Text = nowRecording ? "&Stop recording" : "&Start recording"; + // Mnemonic stays on an "R" in both states: "Sta&rt recording" (Alt+R activates + // the R in Start) when not recording, "Stop &recording" (Alt+R activates the R + // in recording) when recording. Same keystroke does the same job in both states + // — matches the Ctrl+R global toggle. + startStopRecordingMenuItem.Text = nowRecording ? "Stop &recording" : "Sta&rt recording"; startStopRecordingMenuItem.AccessibleName = nowRecording ? "Stop recording" : "Start recording"; } if (InvokeRequired) BeginInvoke(Apply); diff --git a/src/RemSound.App/RecordingSettingsDialog.cs b/src/RemSound.App/RecordingSettingsDialog.cs index f085cb4..5a570ef 100644 --- a/src/RemSound.App/RecordingSettingsDialog.cs +++ b/src/RemSound.App/RecordingSettingsDialog.cs @@ -3,15 +3,26 @@ using RemSound.Core; namespace RemSound.App; /// -/// Recording settings dialog. Three listboxes laid out left-to-right: -/// * Recording &source (Alt+S) — what audio gets captured -/// * File &format (Alt+F) — WAV / MP3 / Ogg / FLAC -/// * Audio &attributes (Alt+A) — bit depth or bitrate, plus channel mode +/// Recording settings dialog. Up to five listboxes laid out left-to-right: +/// * Recording &source (Alt+S) — what audio gets captured +/// * File &format (Alt+F) — WAV / MP3 / OGG-Opus / FLAC +/// * Audio format &attributes (Alt+A) — bit depth (WAV/FLAC) or bitrate (MP3/Ogg) +/// * FLAC compression &level (Alt+L) — 0..8; visible only when FLAC is selected +/// * &Channels (Alt+C) — Stereo / Mono /// -/// The attributes list repopulates whenever the file-format selection changes, so the user -/// always sees only the choices that make sense for the format. Selecting a WAV-only -/// attribute then switching the format to MP3 doesn't carry forward — the format-attributes -/// list resets to a sensible default for the new format. +/// Channel mode was originally folded into every attribute row (16-bit stereo / 16-bit +/// mono / 24-bit stereo / 24-bit mono / …) which doubled the attribute count for no real +/// benefit. Pulling it out into its own listbox keeps each list focused on one decision. +/// +/// FLAC has TWO independent quality knobs (bit depth + compression level); the bit depth +/// stays in the attributes column, the compression level gets its own conditional column +/// that only appears when FLAC is selected. Compression level 0 = fastest encode and +/// biggest file; 8 = slowest and smallest. The libFLAC reference default is 5, which is +/// where the slider defaults if nothing's been set. All levels produce bit-identical +/// audio — it's a pure encode-time-vs-file-size tradeoff. +/// +/// The attribute and compression lists repopulate whenever the file-format selection +/// changes, so the user always sees only the choices that make sense for the format. /// /// Settings are written back to the profile only when the user presses OK. Cancel / Esc /// discards. The dialog also exposes so the caller can @@ -68,6 +79,52 @@ internal sealed class RecordingSettingsDialog : Form Height = 200, }; + // FLAC compression level. Wrapped in its own column that toggles visibility on the + // current file-format selection — visible for FLAC, hidden otherwise. Held as fields + // so the column-visibility refresh in + // can flip them together. + private readonly Label flacCompressionLabel = new() + { + Text = "FLAC compression &level (Alt+L):", + AutoSize = true, + Padding = new Padding(0, 0, 0, 4), + }; + + private readonly ListBox flacCompressionList = new() + { + AccessibleName = "FLAC compression level", + SelectionMode = SelectionMode.One, + IntegralHeight = false, + Height = 200, + }; + + // Channel mode (Stereo / Mono) extracted out of the per-format attributes so the + // attributes list only carries the bit-depth-or-bitrate decision. Always visible — + // every format has a channel-count choice. + private readonly Label channelLabel = new() + { + // Alt+C — the natural letter for Channels. The Cancel button gives this letter + // up (it's on Alt+N) so the mnemonic is unambiguous. + Text = "&Channels (Alt+C):", + AutoSize = true, + Padding = new Padding(0, 0, 0, 4), + }; + + private readonly ListBox channelList = new() + { + AccessibleName = "Channels", + SelectionMode = SelectionMode.One, + IntegralHeight = false, + Height = 80, + }; + + // The compression column is wrapped in a Panel held here so its Visible can be + // toggled atomically with the column's ColumnStyle width — visible+20% width when + // FLAC, hidden+0% width otherwise. Kept as a field so UpdateFlacCompressionVisibility + // and the layout-building code share the same instance. + private Control? compressionColumn; + private TableLayoutPanel? grid; + private readonly Button okButton = new() { Text = "&OK", @@ -77,7 +134,10 @@ internal sealed class RecordingSettingsDialog : Form private readonly Button cancelButton = new() { - Text = "&Cancel", + // Alt+N rather than the conventional Alt+C — Channels takes Alt+C as its natural + // mnemonic. Esc still dismisses the dialog (CancelButton wiring below), so the + // less-conventional letter has no real cost. + Text = "Ca&ncel", AutoSize = true, DialogResult = DialogResult.Cancel, }; @@ -102,15 +162,19 @@ internal sealed class RecordingSettingsDialog : Form ShowInTaskbar = false; StartPosition = FormStartPosition.CenterParent; KeyPreview = true; - ClientSize = new Size(700, 360); + ClientSize = new Size(900, 380); PopulateSourceList(); PopulateFormatList(); PopulateAttributesList(); + PopulateFlacCompressionList(); + PopulateChannelList(); SelectFromSource(working.Source); SelectFromFormat(working.FileFormat); SelectFromAttributes(working); + SelectFromFlacCompression(working); + SelectFromChannelMode(working); sourceList.SelectedIndexChanged += (_, _) => { @@ -126,6 +190,7 @@ internal sealed class RecordingSettingsDialog : Form working.FileFormat = newFormat; PopulateAttributesList(); SelectFromAttributes(working); + UpdateFlacCompressionVisibility(); }; attributesList.SelectedIndexChanged += (_, _) => @@ -134,32 +199,59 @@ internal sealed class RecordingSettingsDialog : Form ApplyAttributesSelection(); }; + flacCompressionList.SelectedIndexChanged += (_, _) => + { + if (flacCompressionList.SelectedIndex < 0) return; + // The compression list always carries levels 0..8 at indices 0..8 — pure + // 1:1 mapping. Saved even when FLAC isn't the active format so flipping + // formats back-and-forth preserves the user's compression choice. + working.FlacCompressionLevel = flacCompressionList.SelectedIndex; + }; + + channelList.SelectedIndexChanged += (_, _) => + { + if (channelList.SelectedIndex < 0) return; + working.ChannelMode = (RecordingChannelMode)channelList.SelectedIndex; + }; + okButton.Click += (_, _) => { ChangedAnything = !SettingsEqual(initialSnapshot, working); }; - // Three columns side by side, OK/Cancel row beneath. - var grid = new TableLayoutPanel + // Five columns side by side, OK/Cancel row beneath. The FLAC-compression column + // (index 3) is collapsed to zero width when the selected file format isn't FLAC, + // so non-FLAC formats see a clean 4-column layout without a blank gap. + grid = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(12), - ColumnCount = 3, + ColumnCount = 5, RowCount = 2, }; - for (var i = 0; i < 3; i++) grid.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 33.3f)); + for (var i = 0; i < 5; i++) grid.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20f)); grid.RowStyles.Add(new RowStyle(SizeType.AutoSize)); grid.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); var sourceColumn = MakeColumn(sourceLabel, sourceList); var formatColumn = MakeColumn(formatLabel, formatList); var attributesColumn = MakeColumn(attributesLabel, attributesList); + compressionColumn = MakeColumn(flacCompressionLabel, flacCompressionList); + var channelColumn = MakeColumn(channelLabel, channelList); grid.Controls.Add(sourceColumn, 0, 0); grid.SetRowSpan(sourceColumn, 2); grid.Controls.Add(formatColumn, 1, 0); grid.SetRowSpan(formatColumn, 2); grid.Controls.Add(attributesColumn, 2, 0); grid.SetRowSpan(attributesColumn, 2); + grid.Controls.Add(compressionColumn, 3, 0); + grid.SetRowSpan(compressionColumn, 2); + grid.Controls.Add(channelColumn, 4, 0); + grid.SetRowSpan(channelColumn, 2); + + // Initial column-visibility refresh, AFTER the grid has all five children so we + // can flip the compression column's ColumnStyle width together with its Visible. + UpdateFlacCompressionVisibility(); var buttonRow = new FlowLayoutPanel { @@ -178,12 +270,38 @@ internal sealed class RecordingSettingsDialog : Form AcceptButton = okButton; CancelButton = cancelButton; - // Tab order top-to-bottom of the visible flow: source, format, attributes, OK, Cancel. + // Tab order left-to-right across the visible columns. The FLAC compression list + // sits between attributes and channels in tab order so the Alt+L mnemonic and Tab + // both land there when it's visible; when hidden it's still in tab order but + // skipped because Visible=false controls aren't focusable. sourceList.TabIndex = 0; formatList.TabIndex = 1; attributesList.TabIndex = 2; - okButton.TabIndex = 3; - cancelButton.TabIndex = 4; + flacCompressionList.TabIndex = 3; + channelList.TabIndex = 4; + okButton.TabIndex = 5; + cancelButton.TabIndex = 6; + } + + /// Show or hide the FLAC compression column based on the current file-format + /// selection. Driven from the formatList change handler and once at dialog open. + /// When hidden, the column's ColumnStyle width is set to 0% and the other four columns + /// each take 25% — visually the dialog reads as a clean 4-column layout. When visible, + /// all five columns share 20% each. + private void UpdateFlacCompressionVisibility() + { + if (grid is null || compressionColumn is null) return; + var isFlac = working.FileFormat == RecordingFileFormat.Flac; + compressionColumn.Visible = isFlac; + if (isFlac) + { + for (var i = 0; i < 5; i++) grid.ColumnStyles[i].Width = 20f; + } + else + { + // Distribute the hidden column's space evenly across the four remaining ones. + for (var i = 0; i < 5; i++) grid.ColumnStyles[i].Width = (i == 3) ? 0f : 25f; + } } private static Control MakeColumn(Label label, ListBox list) @@ -227,58 +345,45 @@ internal sealed class RecordingSettingsDialog : Form } // === Per-format attribute tables === - // All four formats currently record at the engine's 48 kHz mix rate, so labels include - // "48 kHz" to make the sample rate explicit (it's not a choice — it's a statement of fact - // about what gets written, which removes a common surprise for users who expected to see - // a rate picker). Channel mode is part of every row because it determines file shape - // alongside the format-specific quality knob. + // All four formats record at the engine's 48 kHz mix rate, so labels include "48 kHz" + // to make the sample rate explicit (it's not a choice — it's a statement of fact about + // what gets written, which removes a common surprise for users who expected to see a + // rate picker). Channel mode is NOT part of any row — it has its own dedicated listbox + // since 2026-05-15 (was previously folded into every row, doubling the count for no + // real benefit). - private static readonly (int Bits, RecordingChannelMode Mode, string Label)[] WavAttributes = + private static readonly (int Bits, string Label)[] WavAttributes = { - (16, RecordingChannelMode.Stereo, "16-bit PCM, 48 kHz, stereo"), - (16, RecordingChannelMode.Mono, "16-bit PCM, 48 kHz, mono"), - (24, RecordingChannelMode.Stereo, "24-bit PCM, 48 kHz, stereo"), - (24, RecordingChannelMode.Mono, "24-bit PCM, 48 kHz, mono"), - (32, RecordingChannelMode.Stereo, "32-bit float, 48 kHz, stereo"), - (32, RecordingChannelMode.Mono, "32-bit float, 48 kHz, mono"), + (16, "16-bit PCM, 48 kHz"), + (24, "24-bit PCM, 48 kHz"), + (32, "32-bit float, 48 kHz"), }; - private static readonly (int Kbps, RecordingChannelMode Mode, string Label)[] Mp3Attributes = + private static readonly (int Kbps, string Label)[] Mp3Attributes = { - (128, RecordingChannelMode.Stereo, "128 kbps, 48 kHz, stereo"), - (128, RecordingChannelMode.Mono, "128 kbps, 48 kHz, mono"), - (192, RecordingChannelMode.Stereo, "192 kbps, 48 kHz, stereo"), - (192, RecordingChannelMode.Mono, "192 kbps, 48 kHz, mono"), - (256, RecordingChannelMode.Stereo, "256 kbps, 48 kHz, stereo"), - (256, RecordingChannelMode.Mono, "256 kbps, 48 kHz, mono"), - (320, RecordingChannelMode.Stereo, "320 kbps, 48 kHz, stereo"), - (320, RecordingChannelMode.Mono, "320 kbps, 48 kHz, mono"), + (128, "128 kbps, 48 kHz"), + (192, "192 kbps, 48 kHz"), + (256, "256 kbps, 48 kHz"), + (320, "320 kbps, 48 kHz"), }; // OGG-Opus is VBR — kbps numbers are the encoder's target average. Opus' music-quality // sweet spot starts around 96 kbps; we expose 96 / 128 / 192 / 256 so users have a // smaller-file option without it sounding obviously lossy on dense material. - private static readonly (int Kbps, RecordingChannelMode Mode, string Label)[] OggOpusAttributes = + private static readonly (int Kbps, string Label)[] OggOpusAttributes = { - (96, RecordingChannelMode.Stereo, "96 kbps, 48 kHz, stereo"), - (96, RecordingChannelMode.Mono, "96 kbps, 48 kHz, mono"), - (128, RecordingChannelMode.Stereo, "128 kbps, 48 kHz, stereo"), - (128, RecordingChannelMode.Mono, "128 kbps, 48 kHz, mono"), - (192, RecordingChannelMode.Stereo, "192 kbps, 48 kHz, stereo"), - (192, RecordingChannelMode.Mono, "192 kbps, 48 kHz, mono"), - (256, RecordingChannelMode.Stereo, "256 kbps, 48 kHz, stereo"), - (256, RecordingChannelMode.Mono, "256 kbps, 48 kHz, mono"), + (96, "96 kbps, 48 kHz"), + (128, "128 kbps, 48 kHz"), + (192, "192 kbps, 48 kHz"), + (256, "256 kbps, 48 kHz"), }; - // FLAC is lossless — quality knob is just bit depth (and silently, compression level, - // which we hard-fix at the reference encoder's default 5). 32-bit float isn't a FLAC - // option (FLAC stores integer PCM), so it's deliberately absent. - private static readonly (int Bits, RecordingChannelMode Mode, string Label)[] FlacAttributes = + // FLAC is integer-PCM only — quality knob in this column is just bit depth. The + // compression level is its own listbox (visible only when FLAC is selected). + private static readonly (int Bits, string Label)[] FlacAttributes = { - (16, RecordingChannelMode.Stereo, "16-bit, 48 kHz, stereo"), - (16, RecordingChannelMode.Mono, "16-bit, 48 kHz, mono"), - (24, RecordingChannelMode.Stereo, "24-bit, 48 kHz, stereo"), - (24, RecordingChannelMode.Mono, "24-bit, 48 kHz, mono"), + (16, "16-bit, 48 kHz"), + (24, "24-bit, 48 kHz"), }; private void PopulateAttributesList() @@ -288,16 +393,16 @@ internal sealed class RecordingSettingsDialog : Form switch (working.FileFormat) { case RecordingFileFormat.Wav: - foreach (var (_, _, label) in WavAttributes) attributesList.Items.Add(label); + foreach (var (_, label) in WavAttributes) attributesList.Items.Add(label); break; case RecordingFileFormat.Mp3: - foreach (var (_, _, label) in Mp3Attributes) attributesList.Items.Add(label); + foreach (var (_, label) in Mp3Attributes) attributesList.Items.Add(label); break; case RecordingFileFormat.Ogg: - foreach (var (_, _, label) in OggOpusAttributes) attributesList.Items.Add(label); + foreach (var (_, label) in OggOpusAttributes) attributesList.Items.Add(label); break; case RecordingFileFormat.Flac: - foreach (var (_, _, label) in FlacAttributes) attributesList.Items.Add(label); + foreach (var (_, label) in FlacAttributes) attributesList.Items.Add(label); break; default: attributesList.Items.Add("Default settings"); @@ -306,6 +411,37 @@ internal sealed class RecordingSettingsDialog : Form attributesList.EndUpdate(); } + /// Populate the FLAC compression listbox with all 9 levels (0..8). Levels are + /// identified verbatim — the user picks by index, which maps 1:1 to the level number. + /// Headers on the "fastest" and "smallest" ends are tagged so the trade-off is obvious + /// without needing a separate explanatory label. + private void PopulateFlacCompressionList() + { + flacCompressionList.BeginUpdate(); + flacCompressionList.Items.Clear(); + flacCompressionList.Items.Add("0 — fastest encode, biggest file"); + flacCompressionList.Items.Add("1"); + flacCompressionList.Items.Add("2"); + flacCompressionList.Items.Add("3"); + flacCompressionList.Items.Add("4"); + flacCompressionList.Items.Add("5 — default (libFLAC reference)"); + flacCompressionList.Items.Add("6"); + flacCompressionList.Items.Add("7"); + flacCompressionList.Items.Add("8 — slowest encode, smallest file"); + flacCompressionList.EndUpdate(); + } + + /// Populate the channel listbox. Order MUST match RecordingChannelMode enum + /// values 0 / 1 so the SelectedIndex → enum mapping is direct. + private void PopulateChannelList() + { + channelList.BeginUpdate(); + channelList.Items.Clear(); + channelList.Items.Add("Stereo"); + channelList.Items.Add("Mono"); + channelList.EndUpdate(); + } + private void SelectFromSource(RecordingSource src) { var idx = (int)src; @@ -325,50 +461,46 @@ internal sealed class RecordingSettingsDialog : Form case RecordingFileFormat.Wav: for (var i = 0; i < WavAttributes.Length; i++) { - var (bits, mode, _) = WavAttributes[i]; - if (bits == s.WavBitsPerSample && mode == s.ChannelMode) + if (WavAttributes[i].Bits == s.WavBitsPerSample) { attributesList.SelectedIndex = i; return; } } - attributesList.SelectedIndex = 2; // 24-bit stereo default + attributesList.SelectedIndex = 1; // 24-bit default break; case RecordingFileFormat.Mp3: for (var i = 0; i < Mp3Attributes.Length; i++) { - var (kbps, mode, _) = Mp3Attributes[i]; - if (kbps == s.Mp3BitrateKbps && mode == s.ChannelMode) + if (Mp3Attributes[i].Kbps == s.Mp3BitrateKbps) { attributesList.SelectedIndex = i; return; } } - attributesList.SelectedIndex = 6; // 320 kbps stereo default + attributesList.SelectedIndex = 3; // 320 kbps default break; case RecordingFileFormat.Ogg: for (var i = 0; i < OggOpusAttributes.Length; i++) { - var (kbps, mode, _) = OggOpusAttributes[i]; - if (kbps == s.OggOpusBitrateKbps && mode == s.ChannelMode) + if (OggOpusAttributes[i].Kbps == s.OggOpusBitrateKbps) { attributesList.SelectedIndex = i; return; } } - attributesList.SelectedIndex = 4; // 192 kbps stereo default + attributesList.SelectedIndex = 2; // 192 kbps default break; case RecordingFileFormat.Flac: for (var i = 0; i < FlacAttributes.Length; i++) { - var (bits, mode, _) = FlacAttributes[i]; - if (bits == s.FlacBitsPerSample && mode == s.ChannelMode) + if (FlacAttributes[i].Bits == s.FlacBitsPerSample) { attributesList.SelectedIndex = i; return; } } - attributesList.SelectedIndex = 2; // 24-bit stereo default + attributesList.SelectedIndex = 1; // 24-bit default break; default: if (attributesList.Items.Count > 0) attributesList.SelectedIndex = 0; @@ -383,42 +515,41 @@ internal sealed class RecordingSettingsDialog : Form switch (working.FileFormat) { case RecordingFileFormat.Wav: - if (idx < WavAttributes.Length) - { - var (bits, mode, _) = WavAttributes[idx]; - working.WavBitsPerSample = bits; - working.ChannelMode = mode; - } + if (idx < WavAttributes.Length) working.WavBitsPerSample = WavAttributes[idx].Bits; break; case RecordingFileFormat.Mp3: - if (idx < Mp3Attributes.Length) - { - var (kbps, mode, _) = Mp3Attributes[idx]; - working.Mp3BitrateKbps = kbps; - working.ChannelMode = mode; - } + if (idx < Mp3Attributes.Length) working.Mp3BitrateKbps = Mp3Attributes[idx].Kbps; break; case RecordingFileFormat.Ogg: - if (idx < OggOpusAttributes.Length) - { - var (kbps, mode, _) = OggOpusAttributes[idx]; - working.OggOpusBitrateKbps = kbps; - working.ChannelMode = mode; - } + if (idx < OggOpusAttributes.Length) working.OggOpusBitrateKbps = OggOpusAttributes[idx].Kbps; break; case RecordingFileFormat.Flac: - if (idx < FlacAttributes.Length) - { - var (bits, mode, _) = FlacAttributes[idx]; - working.FlacBitsPerSample = bits; - working.ChannelMode = mode; - } + if (idx < FlacAttributes.Length) working.FlacBitsPerSample = FlacAttributes[idx].Bits; break; default: break; } } + /// Select the FLAC compression list row that matches the current settings. + /// Levels 0..8 map directly to list indices 0..8. Out-of-range or unset values fall + /// back to level 5 (the libFLAC reference default). + private void SelectFromFlacCompression(RecordingSettings s) + { + var level = s.FlacCompressionLevel; + if (level < 0 || level > 8) level = 5; + flacCompressionList.SelectedIndex = level; + } + + /// Select the channel-mode row matching the current setting. Falls back to + /// Stereo (index 0) for any unrecognised value. + private void SelectFromChannelMode(RecordingSettings s) + { + var idx = (int)s.ChannelMode; + if (idx < 0 || idx >= channelList.Items.Count) idx = 0; + channelList.SelectedIndex = idx; + } + private static bool SettingsEqual(RecordingSettings a, RecordingSettings b) => a.Source == b.Source && a.FileFormat == b.FileFormat