Skip to content

Aetherial Audio: Channel Strip — RX DSP window #2425

Description

@ten9876

Plan: Aetherial Audio Channel Strip — RX DSP window

Sibling of #2301 (TX strip). Wires the existing-but-dead m_rxBtn
toggle in AetherialAudioStrip and lands the RX-mode counterpart
of every component the TX strip ships.

Goal

Make the strip's TX/RX toggle functional. Mirror the TX panel grid

  • chain widget for RX, with the chain order and layout the
    maintainer specified.

Motivation

  • TX strip is built. RX side has only the docked
    ClientRxChainWidget + ClientRxDspApplet + scattered editors.
  • m_rxBtn already exists in the chain row at
    src/gui/AetherialAudioStrip.cpp:212-218 with a "coming soon"
    tooltip — wiring it up is the entry point.
  • Same architectural lift as Aetherial Audio: Channel Strip — unified TX DSP window #2301: duplicate-then-cut-over.

Architecture decisions

Mode-aware AetherialAudioStrip, sibling RX widgets

The strip becomes mode-aware. The TX/RX toggle swaps:

  • The embedded chain widget (StripChainWidgetStripRxChainWidget)
  • The panel grid (TX 4-row layout ↔ RX 4-row layout)
  • The BYPASS button's engine target (TX bypass ↔ RX bypass)
  • Title-bar suffix (" — TX" ↔ " — RX")

Implementation: two QStackedWidgets (chain row, panel grid),
indexed 0=TX / 1=RX, swapped on m_rxBtn::toggled.

Chain order

[RADIO] → [ADSP] → [AGC-T] → [EQ] → [AGC-C] → [DESS] → [TUBE] → [EVO] → [SPEAK]
  • 9 tiles total. RADIO and SPEAK are status-only endpoints —
    same semantics as ClientRxChainWidget. RADIO greens when PC
    Audio is enabled, SPEAK greens when output is unmuted.
  • ADSP (renamed from current "DSP") is a clickable launcher tile
    for the in-strip ADSP panel (the AetherDsp NR cluster). Greens
    when any of NR2 / NR4 / DFNR / BNR are active; rotates the
    active module's short label.
  • The 6 middle stages (AGC-T / EQ / AGC-C / DESS / TUBE / EVO)
    map onto RxChainStage enum values:
    Gate → "AGC-T", Eq → "EQ", Comp → "AGC-C",
    (new) DeEss → "DESS", Tube → "TUBE", Pudu → "EVO".
    Existing relabel mapping is in
    src/gui/ClientRxChainWidget.cpp:74-77; DeEss is new and
    needs to be added to the enum and that stageLabel() switch.
  • Single-click toggles bypass; double-click opens the panel —
    identical to StripChainWidget.

Panel layout

7 cells, 2 : 1 : 2 : 2 cells per row across 4 rows (the EQ row
contains a single cell that spans both columns):

┌────────────────────┬────────────────────┐
│  ADSP              │  AGC-T             │   row 1 — 2 cells
├────────────────────┴────────────────────┤
│  EQ                                     │   row 2 — spans cols
├────────────────────┬────────────────────┤
│  AGC-C             │  DESS              │   row 3 — 2 cells
├────────────────────┼────────────────────┤
│  TUBE              │  EVO               │   row 4 — 2 cells
└────────────────────┴────────────────────┘

Mapping mirrors the chain: row order follows signal flow
(ADSP → AGC-T, EQ wide, AGC-C → DESS, TUBE → EVO).

Engine: add RxChainStage::DeEss + master RX bypass

New stage value

Add DeEss = 6 to AudioEngine::RxChainStage
(src/core/AudioEngine.h:286-293). Default chain order in
AudioEngine.cpp:1726-1730 becomes:
Eq, Gate, Comp, DeEss, Tube, Pudu — matching the chain order
left-to-right after the static endpoints. kMaxRxChainStages
already 8, headroom is fine.

Add case DeEss: /* TODO */ to the audio-thread block dispatch
(AudioEngine.cpp:1587-1592) and to the name↔enum maps
(:1699-1716).

Master RX bypass

Add setRxBypassed(bool), isRxBypassed(),
rxBypassChanged(bool) as exact siblings of the TX equivalents
(AudioEngine.h:317-319). Same snapshot-and-restore semantics:
on bypass-on, snapshot the currently-enabled RX stages and disable
them; on bypass-off, re-enable only that snapshot. Manual stage
toggles during bypass survive the restore.

Persistence: AppSettings("RxChainBypassed"), sibling of
TxChainBypassed.

Coexist via duplication

Same call as #2301: copy each TX Strip*Panel to a sibling
StripRx*Panel (or rename appropriately for AGC), swap the engine
getters/setters to the RX-side equivalents, leave the TX panels
alone. Cutover deletion is a future pass.

Files to add

StripRxChainWidget.{h,cpp}

Mirror of StripChainWidget (~800 LOC). Same single-row painted
strip with drag-reorder + click semantics. Iterates RxChainStage
instead of TxChainStage. Status tiles: RADIO, ADSP, SPEAK.

Per-stage panels (7 total)

Render cell Source duplicate Engine binding
ADSP AetherDspWidget (thin wrapper) unchanged — reuse
AGC-T StripGatePanel RX gate getters/setters
EQ StripEqPanel RX EQ getters/setters
AGC-C StripCompPanel RX comp getters/setters
DESS StripDeEssPanel RX de-esser getters/setters
TUBE StripTubePanel RX tube getters/setters
EVO StripPuduPanel RX pudu getters/setters

Naming: StripAdspPanel, StripAgcTPanel, StripRxEqPanel,
StripAgcCPanel, StripRxDeEssPanel, StripRxTubePanel,
StripEvoPanel. (Alternative: prefix everything with StripRx.
Maintainer call.)

StripAdspPanel is a thin wrapper around a fresh AetherDspWidget
instance — that widget is already designed for reuse
(AetherDspDialog and ClientRxDspApplet already share it).

Step-by-step

Step 1 — Engine RX scaffolding

  • Add RxChainStage::DeEss to the enum.
  • Update applyRxChain() block dispatch with a case DeEss
    TODO branch.
  • Update name↔enum map functions to handle "DeEss".
  • Update default setRxChainStages order to include DeEss in
    chain position (between Comp and Tube).
  • Add setRxBypassed / isRxBypassed / rxBypassChanged and
    m_rxBypassSnapshot / m_isRxBypassed members.
  • Persist via AppSettings("RxChainBypassed").

Step 2 — StripRxChainWidget

  • New file pair modeled on StripChainWidget.
  • Endpoint tiles: RADIO (leftmost, green when PC Audio enabled),
    ADSP (between RADIO and user stages), SPEAK (rightmost, green
    when output unmuted).
  • User stages: AGC-T / EQ / AGC-C / DESS / TUBE / EVO via the
    existing stageLabel() map (extended for DeEss).
  • Single-click: toggle bypass on user stages, open AetherDsp
    in-strip panel (or focus it) on ADSP. Double-click: open the
    in-strip panel (or open the floating editor while panels are
    still iterating).
  • Drag-reorder: same as TX side — emits chainReordered() after
    the engine has the new order.

Step 3 — Strip RX panels

  • 6 stage panels duplicated from TX siblings, RX engine wiring.
  • 1 StripAdspPanel wrapping AetherDspWidget.
  • Each panel renders a "— RX" suffix in its title to match the
    TX panels' "— TX" convention seen in the screenshot.
  • Engine getters/setters for the RX stages: most don't exist yet
    (RX block dispatch is all TODO). This issue ships UI
    scaffolding bound to placeholder getters/setters; actual DSP is
    out of scope.

Step 4 — Mode toggle wiring in AetherialAudioStrip

  • Two QStackedWidgets for the chain row and the panel grid. Page
    0 = TX (existing widgets), page 1 = RX (new widgets).
  • m_rxBtn::toggled(true) switches both to page 1, repoints the
    BYPASS button to RX bypass, updates the title-bar suffix.
  • Drop "coming soon" from the RX button tooltip.

Step 5 — Persistence

  • AppSettings("AetherialStripMode") — last selected TX/RX.
    Restored on show. Default = TX.
  • AetherialStripGeometry / AetherialStripVisible stay shared
    across modes (single window).

Step 6 — MainWindow wiring

  • stageEnabledChanged overload for RxChainStage so the docked
    ClientRxChainWidget repaints in lock-step.
  • BYPASS chain-applet button needs to know which mode the strip is
    in, OR the docked applet gets its own RX BYPASS — TBD.
  • cutoffsDragRequested is TX-only; RX has no transmit filter, so
    no RX equivalent.

Decisions made

  • Panel naming follows the user-facing label. Files and
    classes are StripAgcTPanel, StripAgcCPanel, StripAdspPanel,
    StripEvoPanel — not the underlying engine enum names
    (Gate / Comp / Pudu). For stages whose RX label matches the
    engine enum (EQ, TUBE, DESS), use StripRxEqPanel,
    StripRxTubePanel, StripRxDeEssPanel so they're clearly the
    RX siblings of the existing TX Strip*Panel files.

  • ADSP panel = wrapper for AetherDSP Settings
    (AetherDspWidget). No full duplicate — the strip embeds a
    fresh AetherDspWidget instance with strip-styled chrome. Same
    pattern AetherDspDialog and ClientRxDspApplet already use.

  • BYPASS is coordinated across all four UI surfaces. The
    docked ClientChainApplet already has a TX/RX toggle and its
    own BYPASS button (src/gui/ClientChainApplet.cpp:91-174), so
    the natural coordination is:

    • Docked BYPASS targets engine TX bypass when its mode = TX,
      engine RX bypass when its mode = RX
    • Strip BYPASS targets engine TX bypass when strip mode = TX,
      engine RX bypass when strip mode = RX
    • Engine emits txBypassChanged / rxBypassChanged per
      mode; every BYPASS button observes both and updates its
      visual state to match the bypass for whichever mode it is
      currently displaying

    Strip mode and docked mode are independent UI states — only the
    engine state is shared. Step 1 lands the engine signal
    (rxBypassChanged); Step 4 wires the strip's button to switch
    observers on mode toggle; Step 6 extends the docked applet's
    onBypassToggled to dispatch to the engine setter for its
    current mode.

Out of scope (subsequent issues)

  • Actual RX DSP implementations. RX block dispatch is stubbed
    TODOs. This issue ships UI scaffolding only; each stage's
    signal-processing implementation is a separate phase per memory
    project_tx_dsp_chain_architecture.md's pattern.
  • Cutover deletion. The docked ClientRxChainWidget /
    ClientRxDspApplet stay in place; deletion is a follow-up after
    the strip is feature-complete.
  • Footer elements. RX equivalent of TX OUTPUT meter / master /
    mute / S/N decoration — iteration 2+.
  • Hot-swap visuals. Animated TX↔RX transition (slide / cross-
    fade). Not required for iteration 1; a hard swap is fine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    GUIUser interfaceNew FeatureNew feature requestaudioAudio engine and streamingenhancementImprovement to existing featuremaintainer-reviewRequires maintainer review before any action is taken

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions