0.16 review: standalone settings persistence and output channel policy - #547
Merged
Merged
Conversation
Four review findings in the standalone host. SA-2 and SA-4 are one change - they are the same confusion between what the user asked for and what the machine turned out to have - and SA-3 and SA-5 are small and sit in the same two functions. SA-2 and SA-4, intent versus runtime. captureAudioSettings() wrote the *resolved* device names and used flags back to disk, but those describe the stream as it was actually opened: resolveOutputDevice() falls back to the default when the saved device is absent, and startAudioThreadOn() clears the used flag when a side cannot be opened. saveSettings() runs on every window move and on WM_DESTROY, so one launch with the interface unplugged persisted the laptop speakers over the user's choice before they could notice, and one first run in an RDP session with no playback endpoint disabled output on every later launch at the console, because applyAudioSettings() ANDs the stored flag with the probe. Split the two meanings. settings.*DeviceName and settings.audio*Used now hold only what the user asked for, written by the frontend at the point of choice - a device combo, the mute-input menu - which is the split openMidiPorts() already had with settings.midiPortNames. The runtime flags stay runtime: selectDefaultDevices() computes them as intent AND probe, and nothing writes them back. The menu check mark reads intent, so it no longer goes stale when a machine has no capture device, and a mute survives an API switch, which the check mark already claimed it did. SA-3, untrusted config. The .conf is the supported way to configure a build with no settings UI, and clapProcess() terminates the process if a callback delivers utilityBufferSize frames or more. RtAudio opens a WASAPI stream at whatever size it is asked for, so a hand-edited bufferSize=100000 took the standalone down on the first callback. Clamp to the largest size the settings panel itself offers. The sample rate needs no clamp and the reasoning is recorded next to it. SA-5, output channel policy. The interleave repeated the plugin's last main-bus channel into every surplus device channel. That was indistinguishable from a mono rule while devices were always opened with two channels, but the Windows frontend now opens them at their full count, so a stereo plugin on an 8-output interface put its right channel on outputs 3 to 8 - a hard-right signal into whatever those feed. Now: a mono bus is duplicated onto the first two device channels, anything else maps channel-for-channel, and every device channel the plugin does not address is silent. The input path keeps the repeat-last rule deliberately, to be decided separately; the comment that claimed output and input shared one policy is corrected rather than left to mislead. Verified: standalone target builds clean (MSVC, exit 0, no warnings in the touched files), clang-format clean. Runtime behaviour - an RDP session, an unplugged interface, an 8-channel device - was not exercised; there is no such hardware here. Reported, not changed: a saved sample rate the device does not support still falls back to preferred and that fallback is persisted, which is the same class as SA-2. An input-only stream with a garbage rate reaches openStream(), which errors rather than crashes.
baconpaul
approved these changes
Sep 14, 2026
baconpaul
left a comment
Collaborator
There was a problem hiding this comment.
Again this code is all fine but we really do need to do a comments cleanup pass
Keep the why, drop the narration; no code changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Third batch from the 0.16 release review of
main...next. Independent of #545 and #546.What is here
SA-2 and SA-4 — one bug wearing two hats.
captureAudioSettings()wrote the resolved device names and used-flags back to disk. Those describe the stream as it was actually opened, not what the user asked for:resolveOutputDevice()falls back to the default when the saved device is absent, andstartAudioThreadOn()clears the used flag when a side cannot be opened. SincesaveSettings()runs on every window move and onWM_DESTROY, the fallback was persisted before the user could ever notice.outputDeviceNamebecomesSpeakers (Realtek). Plug it back in and you are on laptop speakers, with no indication why.audioOutputUsed=false, andapplyAudioSettings()ANDs the stored flag with the probe — so output is disabled on every later launch at the console, while the settings panel cheerfully shows the default output selected.Reading every use of
audioOutputUsedshowed it was really runtime state ("open this side now"), clobbered by three different functions, with genuine user intent only ever entering through the mute menu and the device combos. The two meanings are now separate:settings.*holds intent, written by the frontend at the point of choice — the same splitopenMidiPorts()already had withsettings.midiPortNames— and the runtime flags are recomputed as intent AND probe each launch and never persisted.Side effect, and an intended one: the mute-input check mark now reads intent, so it no longer goes stale on a machine with no capture device, and a mute survives an API switch — which the check mark already claimed it did.
SA-3 — a hand-edited config could terminate the process.
clapProcess()callsstd::terminate()if a callback deliversutilityBufferSizeframes or more, RtAudio opens a WASAPI stream at whatever size it is asked for, andstandalone_settings.hadvertises the.confas the supported way to configure a build with no settings UI.bufferSize=100000took the standalone down on the first callback. Now clamped to the largest size the settings panel itself offers. Thestd::terminatestays — it is a real invariant for the utility buffers; the fix is to never reach it. Sample rate was checked and needs no clamp; the reasoning is recorded next to it.SA-5 — surplus output channels carried a duplicated channel. The interleave repeated the plugin's last main-bus channel into every surplus device channel. Indistinguishable from a mono rule while devices were always opened with two channels — but the Windows frontend now opens them at full channel count, so a stereo plugin on an 8-output interface put its right channel on outputs 3 through 8, a hard-right signal into whatever those feed (typically a cue mix or a second monitor pair).
New policy: a mono bus duplicates onto the device's first two channels, anything else maps channel-for-channel up to
min(bus, device), and every device channel the plugin does not address is silent.The input path is deliberately unchanged
It uses the same repeat-last rule in the other direction: a plugin input bus wider than the device repeats the last device channel into the surplus (mono mic → both channels of a stereo bus, but a stereo device into a 6-channel bus puts the right input on bus channels 3–6), and a device wider than the bus has its surplus dropped (a stereo plugin on an 8-in interface reads inputs 1/2 only).
That has different ergonomics from the output case and nobody has reported a problem with it, so it is left for a separate decision. The comment that claimed output and input shared one policy is corrected rather than left to mislead.
Reported, not changed
A saved sample rate the device does not support still falls back to preferred, and that fallback is persisted — the same class as SA-2. An input-only stream with a garbage rate reaches
openStream(), which errors rather than crashes.