fix(dax-iq): make DAX-IQ fully usable — meter, rate switching, state#3522
Conversation
There was a problem hiding this comment.
Thanks @Ozy311 — this is a well-documented series, and the meter rework is a clear improvement (the dBFS window with attack/decay ballistics is the right model for a wideband IQ level meter, and the int16-scale convention is well-evidenced). The FIFO/state work follows repo conventions (AppSettings, owner-only XDG runtime dir mirroring the DAX-audio hardening). I reviewed the 5 commits unique to this PR; the DaxIqModel plumbing commits are #3521's and should get their review there.
A few real issues to address, all in the new auto-start / restore paths:
1. Double stream create after a DAX stop → IQ enable (the !m_daxBridge guard is not equivalent to "wiring absent"). stopDax() (MainWindow.cpp:15568) tears down the bridge but does not disconnect the IQ connections startDax() wires (statusReceived→applyStreamStatus, iqEnableRequested→createStream, iqRateChanged→setSampleRate — they're connected to this/the model, not to m_daxBridge). So after a start→stop cycle, an IQ enable click fires the surviving iqEnableRequested→createStream connection and your new lambda (which sees m_daxBridge == nullptr, calls startDax() again — wiring a duplicate set of connections — then calls createStream(ch) directly): two stream create type=dax_iq commands for one click, and every subsequent enable double-creates via the duplicated connections. The connection accumulation across start/stop cycles is pre-existing, but this PR makes it user-visible. Options, best first:
- Take the alternative you offered in the description: un-lazy the DAX-IQ wiring so it's established once at construction on all platforms (the IQ path doesn't actually need the audio bridge). That deletes the auto-start guard entirely and fixes the pre-existing duplication too. I'd prefer this route.
- Or: disconnect the IQ connections in
stopDax()and track "IQ wiring up" with a member flag instead of inferring it fromm_daxBridge.
2. The first-enable-before-DAX-start path loses a non-default rate. In buildUI() the enable handler emits iqRateChanged then iqEnableRequested — but on PipeWire/macOS before startDax(), iqRateChanged isn't connected to anything yet, so the rate emit is dropped, m_desiredRate stays at default, and the stream your lambda creates comes up at 48 k (and the exists combo-sync then flips the combo back to 48 k, clobbering the visible selection). This is the exact flow commit 5 fixes for the meter, so the rate should survive it too: in the auto-start lambda, call daxIqModel().setSampleRate(ch, rate) (rate read from the applet) before createStream(ch) — or moot the whole thing via option 1 above.
3. Please confirm m_desiredRate is initialized to {48000, …} in DaxIqModel.h (the header hunk was cut off in my diff view). If it zero-initializes, the path in issue 2 — createStream with no prior setSampleRate call — makes applyStreamStatus see isNew && m_desiredRate(0) != 48000 and send stream set … daxiq_rate=0 to the radio.
4. Suppressed streamChanged can leave a restored channel stuck "Off". When reapplyPending suppresses the isNew emit, the On-button sync (which the startup restore deliberately relies on) only happens when the follow-up rate status arrives. If the radio rejects the stream set … daxiq_rate= (or never echoes it), the button stays "Off" while the stream exists — and since setDaxIqLevel now gates on the button text, the meter stays pinned at 0 even with IQ flowing; clicking the button then issues a second createStream. Consider emitting streamChanged unconditionally and suppressing only the combo sync for the transient 48 k state (a flag or skipping the combo update when reapplyPending), rather than suppressing the whole signal.
5. Minor: overlapping restore timers can double-create. restoreEnabledChannels() can be scheduled from both the connect handler and startDax() within the same 1.5 s window (e.g. user clicks DAX On right after connect). The exists idempotence guard lags the radio round-trip, so two timers firing close together both pass it. A simple "restore pending" flag (or a single reusable QTimer member) would close this.
None of these block the core value here — the meter, rate persistence, and unbind-zeroing logic are all sound, and the live verification across three platforms is appreciated. Issue 1 is the one I'd consider required before merge; if you take the un-lazy-wiring route it resolves 1 and 2 together. Agreed that #3521 should land first.
🤖 aethersdr-agent · cost: $12.7408 · model: claude-fable-5
…eview aethersdr#3522) Address aethersdr-agent's review on aethersdr#3522: - Un-lazy the DAX-IQ wiring (review aethersdr#1, aethersdr#2). The IQ-side connections (stream status, VITA-49 feed, level meter, enable/disable/rate) are now established once at construction on every platform instead of lazily in startDax() on Mac/PipeWire. They never needed the DAX audio bridge, so they survive a DAX-audio stop and stopDax() can no longer strand an iqEnableRequested->createStream connection. This removes the auto-start lambda and the per-start duplicate wiring: one DAX-IQ enable now issues exactly one `stream create`, regardless of DAX on/off cycles (was one duplicate per stranded cycle). Verified live on a FLEX-6700: 3 creates pre-patch across two DAX toggles -> exactly 1 post-patch. - Always emit streamChanged; gate only the rate combo (review aethersdr#4). A restored channel no longer sticks at "Off" with the meter pinned at 0 when the radio rejects or never echoes the daxiq_rate re-apply. A new IqStream::rateSettling flag suppresses only the rate-combo sync during the transient; it tracks the actual rate gap (m_desiredRate != sampleRate) so an interleaved non-rate status (e.g. a pan bind) can't clear it early and flicker the combo. - Guard overlapping restoreEnabledChannels() timers with m_restorePending (review aethersdr#5). - aethersdr#3: confirmed already safe -- m_desiredRate and IqStream::sampleRate both default to 48000, so daxiq_rate=0 is never sent. Co-authored-by: Don @ cloaked.agency <don@cloaked.agency> & K6OZY
|
Thanks for the thorough review — addressed all five points (numbering below matches your review). Your preferred route (un-lazy the wiring) did collapse points 1 and 2 as you predicted. What changedItems 1 & 2 — un-lazy the DAX-IQ wiring. The IQ-side connections (stream-status routing, VITA-49 feed, level meter, and enable/disable/rate) are now wired once at construction on all platforms — the
This does mean enabling DAX-IQ no longer spins up the DAX audio bridge on Mac/PipeWire (it goes straight to Item 3 — confirmed safe, no change. Item 4 — emit Item 5 — Verification
73, Ozy K6OZY |
Review: DAX-stack stability, #3496 guard interactions, and audio-sink-factory impactWe took a close look at this PR specifically for (a) stability risk to the rest of the DAX stack, (b) interactions with the guards added in #3496, and (c) any impact on the audio sink factory work (#3306). Summary below. Audio sink factory (#3306): no interaction ✅Nothing here reaches DAX audio stack: net stability gain ✅The wiring relocation fixes a latent duplicate-connection bug rather than creating one. Previously (Mac/PipeWire), #3496 guard interactions: none unwound or circumvented ✅
One issue to fix before merge: stale
|
jensenpat
left a comment
There was a problem hiding this comment.
See change request above.
aethersdr#3522) Address @jensenpat's CHANGES_REQUESTED: stale `exists` blocked restore after reconnect. restoreEnabledChannels() skips a channel whose stream(i+1).exists is true, but nothing cleared DaxIqModel::m_streams on a radio disconnect — RadioModel's teardown cleared panStream registrations but never reset m_daxIqModel, and handleStreamRemoved only fires on a radio-sent "removed" status, which a hard disconnect never delivers. So after an unexpected disconnect -> auto-reconnect, `exists` was stale-true with a dead streamId, the restore timer skipped the persisted IQ channels, and the applet showed "On" with no stream on the radio. Fix (the "erase, don't zero" shape, per the review): add DaxIqModel::handleDisconnect() — reset all four IqStreams (preserving the channel index, mirroring handleStreamRemoved) and destroy their pipes on the worker thread — and call it from RadioModel::onDisconnected(), beside the existing panStream teardown. m_desiredRate is intentionally preserved, so the user's rate selection is re-applied by restoreEnabledChannels() on reconnect. The `exists` skip is kept (it now reads correctly false after a disconnect), so the double-timer idempotence guarded by m_restorePending + the skip is unchanged. Incremental ninja clean (Nobara 43, Qt 6.10.3, gcc 15.2.1). Co-authored-by: Don @ cloaked.agency <don@cloaked.agency> & K6OZY
|
Thanks @jensenpat — fixed the stale-
On the merge conflict: it's #3351's 73, Ozy K6OZY |
|
Heads-up — Good news: this is a mechanical rebase, no code changes required. I checked the diff — it doesn't reference any of the members or methods that the decomposition relocated, so the conflict is pure line-churn from MainWindow.cpp shrinking (~19.5k → ~8.1k lines across #3508–#3545). One thing to watch when you resolve it: if your conflict hunk falls in a region that moved out of MainWindow.cpp, the new home is a sibling TU ( Happy to take the rebase off your hands if you'd prefer — just say so. |
DAX-IQ samples are the radio's raw baseband amplitude (int16 full-scale ~32768), not normalized [-1,1] like DAX audio, so the old rms * 200 pegged the bar on real signals. Map RMS to dBFS against int16 full-scale over a [-70,-10] dBFS window with attack-fast/decay-slow ballistics, turning the bar into a level/overload meter that only fills near digital overload. Co-authored-by: Don @ cloaked.agency <don@cloaked.agency> & K6OZY
Store the user-selected rate in m_desiredRate as soon as it is chosen, even when no stream exists yet. When a stream first appears at the radio's 48k default, re-apply the desired rate via "stream set ... daxiq_rate=" and suppress the transient streamChanged so the combo never visibly flips to 48k. On the applet side, push the combo's rate to the model just before enabling, and sync the combo from radio state only while the stream exists so a disable (which resets sampleRate to 48k) cannot clobber the user's selection. Co-authored-by: Don @ cloaked.agency <don@cloaked.agency> & K6OZY
A levelReady queued just before a disable or pan-unbind would freeze the bar at its last (random, timing-dependent) value. Force the bar to 0 and reset the dBFS ballistics whenever the channel is not live — button != "On", no model, the stream does not exist, or it is unbound from a pan (pan==0x0). Mirror the same bound check in the streamChanged sync so an unbound-but- existing stream also drops to 0 instead of freezing. Co-authored-by: Don @ cloaked.agency <don@cloaked.agency> & K6OZY
Extract the connect-time restore into a public restoreEnabledChannels() that re-creates persisted-enabled IQ streams ~1.5s after connect, idempotently (skips channels whose stream already exists) and restores each saved rate via iqRateChanged before iqEnableRequested. Trigger it from the connect handler, from the already-connected path in setRadioModel (the initial connectionStateChanged can fire before setRadioModel runs and be missed), and from MainWindow::startDax once the lazy IQ enable->createStream wiring exists on PipeWire/macOS. The On button is left to the streamChanged sync so a restore that runs before the wiring is in place cannot show a dead "On". Co-authored-by: Don @ cloaked.agency <don@cloaked.agency> & K6OZY
…nabling DAX audio On PipeWire/macOS the DAX-IQ enable->createStream wiring is established lazily inside startDax() (Windows wires it at construction). So enabling a DAX-IQ channel before DAX audio was started silently dropped the request — no stream create reached the radio and the meter never moved. Auto-start DAX on the first DAX-IQ enable and create the triggering channel directly; once the bridge is up the guard is inert and startDax()s own connection handles later enables. Co-authored-by: Don @ cloaked.agency <don@cloaked.agency> & K6OZY
…eview aethersdr#3522) Address aethersdr-agent's review on aethersdr#3522: - Un-lazy the DAX-IQ wiring (review aethersdr#1, aethersdr#2). The IQ-side connections (stream status, VITA-49 feed, level meter, enable/disable/rate) are now established once at construction on every platform instead of lazily in startDax() on Mac/PipeWire. They never needed the DAX audio bridge, so they survive a DAX-audio stop and stopDax() can no longer strand an iqEnableRequested->createStream connection. This removes the auto-start lambda and the per-start duplicate wiring: one DAX-IQ enable now issues exactly one `stream create`, regardless of DAX on/off cycles (was one duplicate per stranded cycle). Verified live on a FLEX-6700: 3 creates pre-patch across two DAX toggles -> exactly 1 post-patch. - Always emit streamChanged; gate only the rate combo (review aethersdr#4). A restored channel no longer sticks at "Off" with the meter pinned at 0 when the radio rejects or never echoes the daxiq_rate re-apply. A new IqStream::rateSettling flag suppresses only the rate-combo sync during the transient; it tracks the actual rate gap (m_desiredRate != sampleRate) so an interleaved non-rate status (e.g. a pan bind) can't clear it early and flicker the combo. - Guard overlapping restoreEnabledChannels() timers with m_restorePending (review aethersdr#5). - aethersdr#3: confirmed already safe -- m_desiredRate and IqStream::sampleRate both default to 48000, so daxiq_rate=0 is never sent. Co-authored-by: Don @ cloaked.agency <don@cloaked.agency> & K6OZY
aethersdr#3522) Address @jensenpat's CHANGES_REQUESTED: stale `exists` blocked restore after reconnect. restoreEnabledChannels() skips a channel whose stream(i+1).exists is true, but nothing cleared DaxIqModel::m_streams on a radio disconnect — RadioModel's teardown cleared panStream registrations but never reset m_daxIqModel, and handleStreamRemoved only fires on a radio-sent "removed" status, which a hard disconnect never delivers. So after an unexpected disconnect -> auto-reconnect, `exists` was stale-true with a dead streamId, the restore timer skipped the persisted IQ channels, and the applet showed "On" with no stream on the radio. Fix (the "erase, don't zero" shape, per the review): add DaxIqModel::handleDisconnect() — reset all four IqStreams (preserving the channel index, mirroring handleStreamRemoved) and destroy their pipes on the worker thread — and call it from RadioModel::onDisconnected(), beside the existing panStream teardown. m_desiredRate is intentionally preserved, so the user's rate selection is re-applied by restoreEnabledChannels() on reconnect. The `exists` skip is kept (it now reads correctly false after a disconnect), so the double-timer idempotence guarded by m_restorePending + the skip is unchanged. Incremental ninja clean (Nobara 43, Qt 6.10.3, gcc 15.2.1). Co-authored-by: Don @ cloaked.agency <don@cloaked.agency> & K6OZY
3dca722 to
53f2dfe
Compare
|
Rebased A heads-up on one wrinkle, since it was slightly more than pure line-churn: #3521 landed as a squash, so I rebased only this PR's own seven commits onto
Verification after the rebase:
All seven commits are GPG-signed. Thanks for the offer to take the rebase — appreciated, but happy to keep it on our side. |
NF0T
left a comment
There was a problem hiding this comment.
Reviewing the 7-commit rebase at head 53f2dfeb. @Ozy311 addressed all five items from the aethersdr-agent COMMENTED review (formally adopted by @jensenpat as the CHANGES_REQUESTED).
All five items resolved:
| # | Item | Resolution |
|---|---|---|
| 1 | Double stream create after DAX stop → IQ enable |
✅ Took Option A: wireDaxIq() wires IQ unconditionally at construction on all platforms; IQ wiring block removed from startDax() entirely. Auto-start lambda gone — no more connection accumulation across stop/start cycles. |
| 2 | Non-default rate lost on first-enable-before-DAX-start | ✅ Moot via item 1 (lambda path eliminated); additionally m_desiredRate[] now persists the user's rate selection in setSampleRate() even before a stream exists. |
| 3 | m_desiredRate zero-init risk |
✅ int m_desiredRate[NUM_CHANNELS]{48000, 48000, 48000, 48000} — explicitly initialized in DaxIqModel.h. |
| 4 | Suppressed streamChanged leaves restored channel stuck "Off" |
✅ streamChanged now emitted unconditionally in applyStreamStatus(); rateSettling flag gates only the combo-sync path in the applet, not the signal itself. |
| 5 | Overlapping restore timers double-create | ✅ m_restorePending flag in DaxIqApplet closes the race; handleDisconnect() in RadioModel::onDisconnected() resets m_streams[i] to IqStream{} (preserving m_desiredRate) so restoreEnabledChannels() correctly sees !exists and recreates on reconnect. |
Additional observations:
- dBFS meter scaling (
20*log10(rms/32768), −70 to −10 dBFS, attack α=0.5 / decay α=0.15) replaces the oldrms * 200that saturated immediately on real signal levels. ✅ boundcheck (stream exists AND pan not 0×0) correctly gates meter zeroing — unbound streams don't show phantom signal. ✅- Removal of the
#if !defined(Q_OS_MAC) && !defined(HAVE_PIPEWIRE)guard inwireDaxIq()is correct: the DAX IQ VITA-49 path is platform-agnostic and the old exclusion was the root cause of IQ never working on those platforms without astartDax()call first.
6/6 CI green. Approved.
Comment-only: the wireDaxIq() call-site comment in the constructor still said '(no-audio-bridge platforms)', stale after #3522 made the helper unconditional/all-platforms. Updated to reflect once-at-construction wiring independent of the audio bridge. Closes #2530 (the extract-helper request was already resolved by #3541 + #3522). No code/behaviour change. Co-authored-by: Ozy311 <ozy311@users.noreply.github.com>
…ethersdr#3522) ## Summary **Stacked on aethersdr#3521** (the core fix that makes DAX-IQ actually deliver samples). Because both PRs target `main`, this one currently shows **all 8 commits** — the first 3 belong to aethersdr#3521; the **5 commits unique to this PR** are the UX/state fixes listed below. Once aethersdr#3521 merges, this diff auto-reduces to just those 5. **Please review/merge aethersdr#3521 first.** With IQ finally flowing, the DAX-IQ applet's UX/state handling — never exercised because the feature never worked — needed fixing: the level meter, sample-rate switching, and enable/disable/startup state. Four focused commits. > Review note for the meter: DAX-IQ is the radio's **native int16-scale `float32`** (raw amplitude, ~32768 full-scale), not normalized `[-1, 1]` — so the meter is scaled accordingly (see commit 1). This is a radio data convention, not a wire-type guarantee. ## Fixes **1. `fix(dax-iq): scale the level meter in dBFS for raw int16 IQ`** — the meter did `rms * 200` assuming normalized `[0, 0.5]` IQ, so it **pegged at 100% on the noise floor**. It now maps RMS to **dBFS over a `[-70, -10]` window** (a level/overload meter: noise sits low, the bar fills toward digital overload) with attack/decay ballistics matching the DAX-audio meter. *Before (pegged `×200`) vs after (responsive dBFS):* | Before — `rms × 200` pegs on the noise floor | After — dBFS, tracking signal across rates | |---|---| |  |  | **2. `fix(dax-iq): apply and persist the selected sample rate across enable`** — picking 24/96/192 k reverted to 48 k: `createStream` sent no rate (radio defaults 48 k) and `setSampleRate` dropped the selection when no stream existed yet. The chosen rate is now remembered and re-applied (`stream set … daxiq_rate=`) once the stream exists; the combo retains the user's choice across disable; and the transient 48k UI flip on enable is suppressed. **3. `fix(dax-iq): zero the level meter when a channel is disabled or unbound`** — a level update queued just before a channel was switched off (or unbound from its panadapter, `pan=0x0`) could re-freeze the bar at its last value. The meter now drops to 0 whenever a channel isn't actively bound and receiving. **4. `fix(dax-iq): restore persisted-enabled IQ channels and rates on startup`** — enabled channels showed "On" at launch but had no stream (dead). On Linux/macOS the IQ↔model wiring is established lazily inside `MainWindow::startDax()`, so the applet's connect-time restore fired before the wiring existed and was dropped. The restore is now triggered from `startDax()` (idempotent), and saved **rates** restore too. **5. `fix(dax-iq): auto-start DAX so a DAX-IQ channel works without first enabling DAX audio`** — on PipeWire/macOS the DAX-IQ `enable → stream create` wiring is established lazily inside `startDax()` (Windows wires it at construction). So enabling a DAX-IQ channel *before* DAX audio was started silently dropped the request — nothing reached the radio and the meter never moved. Enabling a DAX-IQ channel now auto-starts DAX and creates the triggering channel directly; once the bridge is up the guard is inert and `startDax()`'s own connection handles later enables (no double-create). *(Open design note for the maintainer: this auto-starts DAX **audio** as a side effect — the small, additive fix. The alternative is to un-lazy the DAX-IQ connections so they need no DAX-audio bridge at all; happy to take that route if preferred.)* ## Verified Live on a **FLEX-6700**: - All four rates (24/48/96/192 k) switch and persist across enable → disable → enable. - Meter tracks signal across the real dynamic range: noise floor ~9%, strong AM carrier ~19%, super-strong broadcast AM ~74% — no pegging. (Calibration: −66 dBFS noise, −25.6 dBFS broadcast, confirming int16 full-scale.) - Mixed on/off state (e.g. ch1+ch3 on at 96k/24k, ch2+ch4 off) restores **exactly** on boot. ## Scope - 4 commits, `src/gui/DaxIqApplet.{cpp,h}` + one call added to `src/gui/MainWindow.cpp::startDax()`. All portable Qt/STL — no platform guards needed. ## Test plan - [x] Each commit builds standalone; full build clean (Linux). - [x] **Live A/B on a FLEX-6700:** rate switching (24/48/96/192 k persist across enable), dBFS meter response (noise ~9% → strong AM ~19% → super-strong broadcast ~74%, no peg), freeze-on-disable/unbind, startup restore of mixed on/off state. - [x] **CI green on all three platforms:** Linux build · Windows (MSVC) · macOS (clang) · CodeQL · accessibility. - [x] **Runtime-verified on Linux, Windows, and macOS** native builds against the same FLEX-6700: meter, rate switching, and startup state all work. The auto-start fix (commit 5) was confirmed live on macOS — a DAX-IQ channel now brings the meter alive on the first enable with no prior DAX-audio step. --- 73, Ozy **K6OZY** AI compute partnership: [cloaked.agency](https://cloaked.agency) — drafted with Don, our VP of Engineering agent, on Linux dev stack (`nobara-dell`), live-verified against a FLEX-6700.
… (aethersdr#3566) Comment-only: the wireDaxIq() call-site comment in the constructor still said '(no-audio-bridge platforms)', stale after aethersdr#3522 made the helper unconditional/all-platforms. Updated to reflect once-at-construction wiring independent of the audio bridge. Closes aethersdr#2530 (the extract-helper request was already resolved by aethersdr#3541 + aethersdr#3522). No code/behaviour change. Co-authored-by: Ozy311 <ozy311@users.noreply.github.com>
Summary
Stacked on #3521 (the core fix that makes DAX-IQ actually deliver samples). Because both PRs target
main, this one currently shows all 8 commits — the first 3 belong to #3521; the 5 commits unique to this PR are the UX/state fixes listed below. Once #3521 merges, this diff auto-reduces to just those 5. Please review/merge #3521 first.With IQ finally flowing, the DAX-IQ applet's UX/state handling — never exercised because the feature never worked — needed fixing: the level meter, sample-rate switching, and enable/disable/startup state. Four focused commits.
Fixes
1.
fix(dax-iq): scale the level meter in dBFS for raw int16 IQ— the meter didrms * 200assuming normalized[0, 0.5]IQ, so it pegged at 100% on the noise floor. It now maps RMS to dBFS over a[-70, -10]window (a level/overload meter: noise sits low, the bar fills toward digital overload) with attack/decay ballistics matching the DAX-audio meter.Before (pegged
×200) vs after (responsive dBFS):rms × 200pegs on the noise floor2.
fix(dax-iq): apply and persist the selected sample rate across enable— picking 24/96/192 k reverted to 48 k:createStreamsent no rate (radio defaults 48 k) andsetSampleRatedropped the selection when no stream existed yet. The chosen rate is now remembered and re-applied (stream set … daxiq_rate=) once the stream exists; the combo retains the user's choice across disable; and the transient 48k UI flip on enable is suppressed.3.
fix(dax-iq): zero the level meter when a channel is disabled or unbound— a level update queued just before a channel was switched off (or unbound from its panadapter,pan=0x0) could re-freeze the bar at its last value. The meter now drops to 0 whenever a channel isn't actively bound and receiving.4.
fix(dax-iq): restore persisted-enabled IQ channels and rates on startup— enabled channels showed "On" at launch but had no stream (dead). On Linux/macOS the IQ↔model wiring is established lazily insideMainWindow::startDax(), so the applet's connect-time restore fired before the wiring existed and was dropped. The restore is now triggered fromstartDax()(idempotent), and saved rates restore too.5.
fix(dax-iq): auto-start DAX so a DAX-IQ channel works without first enabling DAX audio— on PipeWire/macOS the DAX-IQenable → stream createwiring is established lazily insidestartDax()(Windows wires it at construction). So enabling a DAX-IQ channel before DAX audio was started silently dropped the request — nothing reached the radio and the meter never moved. Enabling a DAX-IQ channel now auto-starts DAX and creates the triggering channel directly; once the bridge is up the guard is inert andstartDax()'s own connection handles later enables (no double-create). (Open design note for the maintainer: this auto-starts DAX audio as a side effect — the small, additive fix. The alternative is to un-lazy the DAX-IQ connections so they need no DAX-audio bridge at all; happy to take that route if preferred.)Verified
Live on a FLEX-6700:
Scope
src/gui/DaxIqApplet.{cpp,h}+ one call added tosrc/gui/MainWindow.cpp::startDax(). All portable Qt/STL — no platform guards needed.Test plan
73, Ozy K6OZY
AI compute partnership: cloaked.agency — drafted with Don, our VP of Engineering agent, on Linux dev stack (
nobara-dell), live-verified against a FLEX-6700.