Feat/spatcore io stage1 - #4
Merged
Merged
Conversation
The shared audio device layer landed on spatcore main (8e0d7e6): DeviceHost, DeviceIoCallback, HardwareIndexMap and a converged TestSignalGenerator. XOA can adopt it now; the Audio Interface window and patch matrix it belongs under are still app-side in WFS-DIY and arrive later. This documents both stages, and in particular the things that would silently corrupt audio if a migration session did not know them: - Under DeviceIoCallback a single buffer row is BOTH hardware input h and hardware output h. XOA's stem gather happens to run before the decoder writes, so it is correct today by accident of ordering; the ordering becomes load-bearing. - The buffer width is max(highest active input, highest active output)+1 clamped to the ceiling, so it is not the speaker count. Taking the speaker count from the buffer would run the decoder over input-only rows. - countNumberOfSetBits() is a count, not an index bound, and XOA indexes its meters and prepare() by that ordinal. It also records what XOA is and is not exposed to: it is NOT affected by the 128-channel AudioSourcePlayer cap (it was never an AudioAppComponent), but it does have the channel-mask defect, since it never clears useDefaultInput/OutputChannels and so can neither honour nor persist a mask. CLAUDE.md registers the document and corrects two claims: the pinned spatcore commit does not contain the io layer, and the DEVPLAN's decision records run past D31. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Updates the handoff now that spatcore/ui/patch/PatchMatrixComponent exists and the boundary has moved. Section 7 was "the window XOA inherits"; it is now a build specification, because the tabs and window are staying in WFS-DIY. What changed and why: the matrix is 2,179 lines of scroll, hit-testing, drag-patching, 1:1 constraint enforcement, keyboard navigation and signal-presence tinting, none of it app-specific once the seams are cut — worth sharing. The shell around it is ~700 lines of layout and widget glue, and sharing it would have pulled the app's long-press button, its slider base (carrying TTSManager and OSC origin-tagging) and 3,199 generated lines of signal-flow artwork into spatcore. So section 7 now carries what a session here actually needs: how to construct the matrix and what every provider is for, the AudioPatch ValueTree section XOA must add, and a behaviour spec for the shell — including the three non-obvious bits (stop the tone on every exit path, the non-linear level and frequency mappings, the 1-based combo vs 0-based enum), a warning not to port WFS-DIY's dead TestSignalControlPanel, and the instruction to make the window stopped-only because patching a live rig destroys speakers. The load-bearing detail is unchanged from stage 1 and now has a second home: feed activeHardwareChannels from DeviceHost::getNumActiveOutputs(), not from the device's channel-name count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… matrix) Link spatcore-io into the app and move the pin prose in CLAUDE.md and XOA-PLAN.md $4. Build-only change: nothing consumes the io layer yet (that lands with the AudioEngine migration). Release build and both ctest lanes verified green on this commit alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AudioEngine now opens/restores its device through spatcore::io::DeviceHost and is driven as a juce::AudioSource by spatcore::io::DeviceIoCallback, replacing the bare AudioDeviceManager::initialise and the raw AudioIODeviceCallback: - DeviceHost writes explicit channel masks with JUCE's useDefault*Channels flags cleared, so the masks the app asks for are the masks that open, the saved device state can round-trip a channel selection, and rigs are no longer silently capped at 256 outputs (handoff $2.2). The addressing ceiling is the new xoa::kMaxHardwareChannels = 512 (D37). - DeviceIoCallback hands getNextAudioBlock a hardware-indexed buffer. The stem gather now runs off that buffer BEFORE anything writes an output -- a row that is an active output aliases the device's own storage with the matching input pre-copied in, so this ordering is load-bearing ($5.1). - Speaker/stem counts come from the device masks in prepareToPlay, never from the buffer width ($5.2), and stay ordinal-indexed under an asserted identity mapping (D36 option A; option B when the stage-2 patch window lands). Downstream (decode, comp, test signal, meters) sees a speaker-width view, unchanged in shape from before. - A device change made through the stock selector is re-asserted to the enable-all policy from the change listener (no-op when already right), and open/policy errors are captured in getLastDeviceError() -- a failing driver now reports a non-empty reason ($5.5). Verified: Release + Debug builds, both ctest lanes, and --gui-smoke in both configs against real hardware (Debug = asserts active). The $6 multi-channel hardware acceptance pass remains a manual step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
spatcore pin bump 00ea5d8 -> ac45f0f (PR #8: SpeakerId in the shared generator). XOA's forked TestSignalGenerator becomes a using-alias shim in the shared-EQ style; SpeakerId and getCurrentSpeakerIndex() now come from spatcore with the ordinals unchanged, so the SpeakersDecoderTab combo mapping and persisted values hold. The deterministic seed is opt-in upstream (the shared prepare() seeds from the wall clock by default), so AudioEngine pins kTestSignalSeed at construction and the determinism test seeds explicitly. The shared prepare() also fixes the silent-Tone defect the fork never had (phase increment recomputed on sample-rate change). Verified: Release build, both ctest lanes, --gui-smoke. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…signal type) The 500 ms hearing-protection ramp now applies to Sweep, DiracPulse and SpeakerId as well as the continuous types (spatcore PR #9) - no signal type may bypass it, so the operator always gets time to stop a too-loud test before it hurts. No XOA code changes: none of the test-signal tests assumed an unramped transient start. Release build, both ctest lanes and --gui-smoke green on this pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…D45) Stage-2 groundwork: an input stem is no longer implicitly mono. - inputFormat (0 = mono point source, 1..10 = AmbiX group of that order, (N+1)^2 stem channels) on each input's Channel node, with span helpers (getInputChannelCount / getStemChannelOffset / getTotalStemChannels) and a kMaxStemChannels = 128 total ceiling enforced by stepping formats down, last HOA input first (D45). - AudioPatch section: InputPatch (one flattened row per stem CHANNEL, D43) and OutputPatch (one row per speaker), property names shared with the spatcore patch matrix (patchData/rows/cols/activeHardware*). Identity-diagonal defaults; reconcileAudioPatch() remaps input rows by per-input block across format/count changes and pads new rows with the identity continuation, so a project that never opens the patch window keeps behaving exactly identity-mapped. recomputePatchCols applies the WFS-DIY column policy; updateHardwareChannelCount feeds the truthful DeviceHost counts. Patch edits are non-undoable, as in WFS-DIY. - Persistence: audiopatch.xml joins the section files (save/load/backup/ merge), with a post-load reconcile. - InputsTab: Format combo; position/conditioning/spread/NFC grey out for HOA groups (inert there, D44). Descriptor + registry entries keep the XoaUiDescriptorTests coverage gate green. Engine consumption of formats and patches lands in the next commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audio path now runs in three explicit domains joined by the patch maps (RtSnapshot'd PatchRtState, composed from the AudioPatch trees on the message thread, identity by default): - GATHER: flattened stem channel k reads hardware input hwForStemChannel[k]; per-input meters take the max across the input's span; new per-hardware-input peaks feed the patch matrix tinting. - BUS: applyEncoder handles both formats per input. Mono keeps the point-source path bit-exactly (SH row broadcast + NFC lanes). An HOA group contributes its own channel c to bus channel c at its liveMatrix factor - now order-adapt x gain composed by AmbiCalculationEngine (zero-padded upmix via weights::orderAdaptGains), with NFC forced off and the same per-coefficient one-block ramps. EncoderRtParams carries the per-input stem offsets/orders. - SCATTER: decode+comp run on a speaker-domain scratch sized by the STORE's speaker count (no longer the device outs), then speaker s is copied to hardware row hwForSpeaker[s]; unfed active outputs are cleared. The test signal is injected post-scatter in the hardware domain (D46) so testing-mode reaches every open output. Per-speaker meters read the speaker's patched hardware row - what actually leaves the device. The $5.1 gather-before-write invariant is now structural (decode never targets io rows), and the identity jassert stays: it guards the DeviceHost contiguous-mask guarantee the hardware indexing rests on. Verified: Release + Debug, ctest (incl. offline-render bit-exactness smoke), --gui-smoke both configs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
XoaPatchMatrix derives spatcore::ui::patch::PatchMatrixComponent and binds the app seams: AudioPatch trees + shared property names, flattened stem-channel rows labelled/coloured by their owning input (D43), palette from ColorScheme, LOC strings, XoaLookAndFeel::uiScale, TTSManager announcements. Distinct basename per the handoff's object-file trap note. The Audio Interface window that hosts it lands next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
XOA's own shell around the shared matrix (the handoff draws the sharing line here deliberately): a device info bar, and Device Settings / Input Patch / Output Patch tabs. - DeviceSettingsPanel routes every mutation through DeviceHost (openNamedDevice / setDeviceAllChannels / enableAllChannels); the sample-rate and buffer-size path re-asserts the mask policy after setAudioDeviceSetup, so the D39 spatcore setters stay optional. - One XoaPatchTab serves both directions: Scroll/Patch modes, Test on the output tab with the non-linear level and log-frequency mappings plus Hold, long-press Unpatch All, and the 20 Hz header repaint that animates the input signal-presence tint. - Transport gate (D41): while the file transport plays, tones stop, modes drop to Scrolling and the tabs are disabled behind a banner. A tone is also stopped on tab change and window close. - SystemConfigTab drops juce::AudioDeviceSelectorComponent for an "Audio Interface…" button plus a live device summary and the last DeviceHost error. - openAudioDevice now seeds the patch trees' active-channel counts. They were only written by the device-change listener, which does not fire at startup, so the matrices' overflow gating was disabled and channels the device never opened looked patchable. Found by opening the window: outputs 7-24 were drawn live on a 6-output device. GUI-verified on Windows: all three tabs render, the matrices show the identity default, gating dims the unopened columns, and switching an input to HOA 1 expands it to four labelled ACN rows in the input matrix, greying its point-source controls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tests (tests/XoaPatchTests.cpp): stem spans and the kMaxStemChannels ceiling stepping formats down; the identity default patch; a patchData round-trip through the routing POD including a NON-CONTIGUOUS speaker assignment and an unpatched row; a format change preserving the other inputs' hardware channels while re-spanning the rows; speaker growth keeping the identity continuation; and the HOA group merge verified against a double reference decode (component c -> bus channel c at the order-adapt gain, silent above the group's order). Also adds rt::makeMonoEncoderParams: EncoderRtParams gained per-input stem offsets, and brace-initialising it left them all zero, which points every input at stem row 0. The factory states the all-mono contract explicitly; the existing encoder tests now use it. Docs: D41-D47 in XOA-DEVPLAN.md (transport gate, both matrices, flattened rows, inputFormat, the stem ceiling, the hardware-domain test signal, clusters deferred), D24 marked superseded, and the handoff's status table updated to ADOPTED. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Architectural correction: program material is played by external apps
(Reaper, QLab, ...) and arrives through the device inputs as stems and
AmbiX HOA groups; there is no cueing mechanism in this app. A WFS-DIY
style interactive sampler may come later - a plain file player will not.
- FilePlayer and the header transport (Browse/Play/Stop/Loop, position
bar, file label) are gone; the header keeps a Test-scene latch, the
rotation dials, master gain and the status line. InputSource is now
{ none, testScene } with none the default: the no-source publish is a
zero-channel BusRtParams whose gather clears busA every block while
the encoder stage accumulates the device stems on top. makeBusParams,
BusRtParams and the gather loop are byte-identical - the offline
bit-exactness smoke passes unchanged.
- The patch window's transport gate is dropped (D49, supersedes D41's
mechanism): an external transport cannot be sensed, so a local gate
would be theatre. Tones still stop on every exit path; the universal
500 ms ramp remains the safety property.
- playbackFilePath/Loop/ContentOrder/Convention leave the schema,
descriptors, registry, constraints, OSC bindings and locale in one
commit (the descriptor-parity gate enforces the lockstep). OSC map
bumped to v1.1 with a documented removal of the three
/xoa/config/playback* leaves (D50); live HOA groups stay AmbiX-only,
per-input convention/order override deferred to the clusters era.
- Tests: XoaFilePlayerTests becomes XoaFileFormatTests, keeping the
WP6 C1 large-channel WAV/FLAC spike (any future file import builds on
it) and dropping the player half. The persistence test now proves a
pre-D48 project file carrying the removed ids loads harmlessly.
- Docs: PRD FR-8 rewritten (external program input), WP6 records
annotated as historical, D48-D50 recorded, kMaxFileChannels
re-documented (value baked into offline baselines, name kept).
Verified: Release + Debug builds, ctest green (offline-render smoke
UNCHANGED), --gui-smoke both configs, app launches and responds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
No description provided.