Channel Strip TX/RX: BYPASS suppresses RN2; RN2 captured in presets (#3054). Principle VI.#3066
Conversation
…3054). Principle VI. Closes the four-part regression in the Aetherial Channel Strip where RN2 TX (RNNoise mic pre-amp, #2813) functioned independently of the chain BYPASS and the preset system that surround every other voice stage: - setTxBypassed snapshots/restores m_rn2TxEnabled alongside the TxChainStage entries, so pressing BYPASS now produces a truly transparent TX path instead of "voice still passes, other audio is suppressed" (RN2 trained on speech). - setRxBypassed gets the symmetric fix for m_rn2Enabled. RX RN2 lives in the NR cluster, not RxChainStage, so the existing kAllStages loop missed it. - ChannelStripPresets::capturePresetJson writes a top-level "rn2" bool for TX and a sibling "rn2" inside the "rx" block for RX. applyPresetJson only acts when the key is present, so old preset files without it leave RN2 untouched — matching the precedent of the v0.9.8 "rx" block (#2425). - StripTubePanel, AetherialAudioStrip, ClientChainApplet tooltips now state the BYPASS scope (global, per-engine) and confirm RN2 is suppressed alongside the chain. BYPASS scope (global vs per-profile) is left as global per the open question in the issue — that is a UX decision for the maintainer; the tooltips make the current scope discoverable without prejudging it. Principle VI: the visual CHAIN widget is the primary entry point for the TX DSP path. Before this change, RN2 was a stage that bypassed the BYPASS gesture the CHAIN widget exposes, fragmenting the user's mental model of the chain. After this change, BYPASS suppresses every voice-shaping stage that runs on the TX path, RN2 included, restoring the CHAIN widget as the single point of control. Blast radius: risk_score=0.256, 4 high-risk affected (top: AppSettings::instance, AppSettings::setValue, AppSettings::save). All four are downstream of the existing per-stage save* helpers the bypass methods already call; the RN2 addition routes its persistence through setRn2TxEnabled / setRn2Enabled, which use the existing saveAetherialTubePreampTxSettings path. No new AppSettings surface. Upstream callers (AetherialAudioStrip::onBypassToggled, ClientChainApplet::onBypassToggled) are unchanged.
There was a problem hiding this comment.
Nice fix — the BYPASS-leaks-RN2 behavior was a real footgun and the comments tying RN2's special-case to its out-of-enum location (#2813) make the intent very clear to a future reader. TX/RX mirror each other cleanly, the contains("rn2") guard in applyPresetJson preserves backward compatibility with old preset files, and the parallel-bool snapshot pattern is the right shape given RxChainStage/TxChainStage can't hold RN2.
A couple of observations rather than blockers:
1. BYPASS now writes RN2 state to AppSettings on every toggle (TX side).
setRn2TxEnabled() calls saveAetherialTubePreampTxSettings() unconditionally. So engaging BYPASS persists "RN2 TX = off", and releasing it persists "RN2 TX = on". This matches the precedent in setStageEnabled (which calls saveClient*Settings() per stage), so it's consistent — but worth being aware that a crash mid-BYPASS leaves AppSettings out of sync with the user's intended RN2 state. The RX side doesn't have this issue because setRn2Enabled doesn't persist (a pre-existing asymmetry, not introduced here).
2. Manual RN2 toggle during BYPASS is silently overwritten on un-bypass.
If a user clicks the RN2 button in StripTubePanel while BYPASS is engaged, the next un-bypass will restore the snapshotted value and clobber their change. This is identical to the existing stage-bypass behavior, so not a regression — just worth noting that the new tooltip language ("the button stays pressed across Channel Strip profile switches") might lead users to expect RN2 toggles to "stick" too. Probably fine to leave as-is.
3. Preset apply order interaction is safe today, but fragile.
setRn2Enabled(true) disables NR2/NR4/BNR/DFNR/MNR via mutual exclusion. The preset format doesn't currently capture those NR-cluster siblings, so there's no conflict — but if someone later adds nr2/bnr/etc. to the preset schema and applies them after rn2, the last-write-wins ordering will silently disable RN2. Worth a brief comment near the RX rn2 apply block flagging that any future NR-cluster keys must apply before rn2, or after considering the mutex.
None of these block merge — the PR delivers what #3054 asks for and the code is clear. Thanks for the careful comments and the consistent tooltip updates across AetherialAudioStrip, ClientChainApplet, and StripTubePanel.
🤖 aethersdr-agent · cost: $4.1706 · model: claude-opus-4-7
Summary
Fixes #3054
What was changed
Channel Strip TX/RX: BYPASS suppresses RN2; RN2 captured in presets (#3054). Principle VI.
Files modified
src/core/AudioEngine.cppsrc/core/AudioEngine.hsrc/core/ChannelStripPresets.cppsrc/gui/AetherialAudioStrip.cppsrc/gui/ClientChainApplet.cppsrc/gui/StripTubePanel.cppGenerated by AetherClaude (automated agent for AetherSDR)
🤖 aethersdr-agent · cost: $21.6045 · model: claude-opus-4-7