2026-10-26 22:51:25 +01:00
|
|
|
# Architecture
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
## Decision: Modular Engine First
|
2026-10-26 22:51:25 +01:00
|
|
|
|
|
|
|
|
The processing graph is the common model for audio sources, instruments,
|
|
|
|
|
effects, mixers, and outputs. The sequencer produces time-based content and
|
|
|
|
|
events consumed by graph nodes. The default workspace remains sequencer-first;
|
|
|
|
|
the graph editor is an advanced view of the same project.
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
|
|
|
|
|
2026-10-26 22:51:25 +01:00
|
|
|
## Layers
|
|
|
|
|
|
|
|
|
|
1. **FLTK/C++ GUI:** input, drawing, windows, view state.
|
|
|
|
|
2. **C core:** project model, commands, parameters, persistence, validation.
|
2026-11-06 15:20:20 +01:00
|
|
|
3. **C engine:** prepared processing graph, DSP, transport timing, event
|
|
|
|
|
delivery.
|
2026-10-26 22:51:25 +01:00
|
|
|
4. **Backend:** hardware and system audio/MIDI integration.
|
|
|
|
|
|
|
|
|
|
The GUI must not directly modify engine internals. It calls a stable C API.
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Graph Execution
|
2026-10-26 22:51:25 +01:00
|
|
|
|
|
|
|
|
The first implementation should reject cycles. Compile a graph off the audio
|
|
|
|
|
thread: validate, topologically sort, determine buffer requirements, allocate
|
|
|
|
|
resources, and publish a prepared immutable execution plan. The audio thread
|
|
|
|
|
must not traverse a graph being edited.
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
The offline engine now copies a validated graph into a prepared plan with stable
|
|
|
|
|
node IDs, topological order, precomputed input adjacency, per-node mono buffers
|
|
|
|
|
and oscillator/meter state. Each plan has one processing owner. Graph edits and
|
|
|
|
|
destruction cannot affect an existing plan. All nodes are scheduled, including
|
|
|
|
|
disconnected nodes; only explicit output nodes are rendered (or terminal nodes
|
|
|
|
|
for legacy callers that do not require explicit outputs). See AUDIO_ENGINE.md
|
|
|
|
|
for contracts. `xtc_realtime` now provides a one-pending/one-retired handoff
|
|
|
|
|
around an exclusively audio-owned active plan. `xtc_backend` is the C backend
|
|
|
|
|
boundary; the optional JACK implementation drives that same realtime engine.
|
2026-10-26 22:51:25 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Ownership and Threads
|
2026-10-26 22:51:25 +01:00
|
|
|
|
|
|
|
|
- GUI thread owns FLTK widgets and view state.
|
|
|
|
|
- Project/control thread owns mutable project data and graph compilation.
|
|
|
|
|
- Audio thread owns the active processing plan and DSP state.
|
|
|
|
|
- Backend callbacks follow the backend's documented threading contract.
|
|
|
|
|
|
2026-10-26 23:32:04 +01:00
|
|
|
Only acknowledged retired plans are reclaimed during processing. Backend close
|
2026-11-06 15:20:20 +01:00
|
|
|
synchronizes callbacks before reclaiming remaining plans. See decision 0004 for
|
|
|
|
|
the release/acquire protocol, bounded resource retention and recovery.
|
2026-10-26 23:40:28 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
2026-10-26 23:40:28 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
## GUI Backend Ownership
|
2026-10-26 23:55:35 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
The GUI window owns one C backend handle and serializes all control calls on the
|
|
|
|
|
GUI thread. A GUI timer reads snapshots and collects retired resources; no audio
|
|
|
|
|
callback calls FLTK. Successful window close first closes the JACK client
|
|
|
|
|
(synchronizing callbacks), then removes the GUI timer and hides the window.
|
|
|
|
|
Failed close leaves both handle and window available for retry. View switching
|
|
|
|
|
and node positions are GUI state only. Semantic edits use transactional C
|
|
|
|
|
commands and publish independent compiled snapshots. See GUI.md.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Editable Graph and Canvas
|
2026-10-26 23:55:35 +01:00
|
|
|
|
2026-11-06 14:12:22 +01:00
|
|
|
Read-only C snapshots, monotonic node IDs, and a small set of C edit commands
|
2026-11-06 15:20:20 +01:00
|
|
|
are provided. The GUI owns an opaque graph handle, never private arrays. An edit
|
|
|
|
|
produces a candidate, and the GUI commits it only after backend submit succeeds
|
|
|
|
|
(or locally while offline). A busy pending slot or invalid edit leaves the prior
|
|
|
|
|
model/plan unchanged. Active/submitted revisions distinguish the queued drawing
|
|
|
|
|
from the engine's acknowledged plan. Empty/output-less editor graphs render
|
|
|
|
|
silence; legacy offline terminal fallback remains available.
|
2026-10-26 23:55:35 +01:00
|
|
|
|
|
|
|
|
GraphWidget owns only selection, node positions, pointer gestures and drawing.
|
|
|
|
|
Its fixed canvas is hosted by FLTK scrolling. C owns nodes, ports, edges,
|
|
|
|
|
validation, ID assignment, compilation and rendering. No audio thread accesses
|
|
|
|
|
GUI-owned state. Reconnect republishes the same retained editable graph.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
2026-10-27 00:10:45 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
## Project Ownership and Persistence
|
2026-10-27 00:10:45 +01:00
|
|
|
|
2026-11-06 14:12:22 +01:00
|
|
|
A C-owned project contains the graph, bounded track/clip arrays, stable ID
|
2026-11-06 15:20:20 +01:00
|
|
|
allocators and constant tempo. Read APIs return copies; GUI canvases borrow the
|
|
|
|
|
opaque project/graph on the control thread. A graph edit prepares a candidate
|
|
|
|
|
project before submitting its graph, then commits ownership only after
|
|
|
|
|
acceptance. Arrangement edits currently change silent metadata and require no
|
|
|
|
|
audio publication.
|
2026-10-27 00:10:45 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
Persistence is private C/POSIX code; loading validates an isolated candidate and
|
|
|
|
|
swaps ownership only after a successful close. The GUI similarly prepares a
|
|
|
|
|
loaded document before stopping/submitting audio. File operations never run in
|
|
|
|
|
the callback. See PROJECT_FORMAT.md for schema, bounds and durability.
|
2026-10-27 00:10:45 +01:00
|
|
|
SequencerWidget owns selection, zoom and gestures only; musical range checks,
|
|
|
|
|
source-preserving trims and ID allocation remain C responsibilities.
|
2026-10-27 00:31:03 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
2026-10-27 00:31:03 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
## Assets and Recording
|
2026-10-27 00:31:03 +01:00
|
|
|
|
|
|
|
|
Audio paths are C clip metadata. A stopped preparation copies range/offset
|
|
|
|
|
information, deduplicates paths, validates and preloads bounded WAV assets into
|
|
|
|
|
an independent C audio plan. The backend owns that plan and a preallocated
|
|
|
|
|
timeline buffer until callback quiescence. A graph audio-source node consumes
|
|
|
|
|
the buffer, so effects/output routing remain graph operations. All tracks share
|
2026-11-06 14:12:22 +01:00
|
|
|
the mono source; no audio summation bypasses the graph.
|
2026-10-27 00:31:03 +01:00
|
|
|
|
|
|
|
|
Arrangement edits stop transport; subsequent Play prepares the new snapshot.
|
|
|
|
|
Graph-only edits retain live plan publication. Missing assets preserve metadata
|
|
|
|
|
and render silence, with repair through a C path setter and GUI relinking.
|
|
|
|
|
Version 2 persistence stores references and explicitly migrates version 1 loads.
|
|
|
|
|
|
|
|
|
|
Recording has one callback producer and one control-thread consumer. A bounded
|
2026-11-06 15:20:20 +01:00
|
|
|
SPSC ring publishes complete payload blocks through lock-free indices. Only the
|
|
|
|
|
consumer writes WAV bytes; finalization follows successful callback stop. No
|
|
|
|
|
editable project pointer, GUI object or FILE is accessed by the producer. See
|
|
|
|
|
decision 0005 and AUDIO_FILES.md for format, limits and recovery contracts.
|
2026-10-27 00:53:41 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## MIDI Source
|
2026-10-27 00:53:41 +01:00
|
|
|
|
|
|
|
|
The C MIDI plan prepares bounded source-relative notes into sample spans and
|
|
|
|
|
renders a separate instrument buffer. `xtc_plan_process_sources` and
|
|
|
|
|
`xtc_realtime_process_sources` distinguish external audio, WAV timeline and
|
|
|
|
|
instrument input; older wrappers supply silence for omitted sources. Graph
|
|
|
|
|
instrument nodes consume the shared mono instrument buffer. The backend owns
|
|
|
|
|
prepared MIDI runtime state exclusively while active, with lock-free panic
|
|
|
|
|
requests and no callback allocation/I/O. Stop/seek clear live voices; scheduled
|
2026-11-06 15:20:20 +01:00
|
|
|
notes chase their absolute phase. See MIDI.md for event ordering, panic
|
|
|
|
|
semantics, capacities and test coverage. No new dependency is required.
|
2026-10-27 01:06:57 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Mix Snapshots and History
|
2026-10-27 01:06:57 +01:00
|
|
|
|
|
|
|
|
`src/mixer` owns validated per-track settings and bounded gain/pan automation.
|
|
|
|
|
Audio/MIDI preparation copies and indexes these into source snapshots; paired
|
|
|
|
|
source buffers feed the same graph independently per stereo side. Atomic backend
|
|
|
|
|
output-peak snapshots cross to the GUI without exposing DSP state. Mixer edits
|
|
|
|
|
stop transport before subsequent preparation. `src/project/history.c` provides
|
|
|
|
|
32-edit C project snapshots; checkout clones a candidate and accept moves the
|
|
|
|
|
cursor only after successful GUI/backend installation. No samples or FLTK state
|
2026-11-06 15:20:20 +01:00
|
|
|
are stored in history. `mixer_widget.cpp` is only controls/drawing/gestures. See
|
|
|
|
|
MIXER.md and PROJECT_FORMAT.md for ownership, limits and v4 migration.
|
2026-10-27 01:21:04 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
---
|
2026-10-27 01:21:04 +01:00
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
## Native Lifecycle and Distribution
|
|
|
|
|
|
|
|
|
|
`xtc_native.h` defines copied versioned C descriptors and bounded block
|
|
|
|
|
contexts. Native effect/source nodes prepare independent instances on
|
|
|
|
|
control-thread graph compilation and reclaim them only on compile failure,
|
|
|
|
|
quiescent destruction or retirement collection. Event instruments can render
|
|
|
|
|
into the public graph source buffer. No descriptor/state pointers enter project
|
|
|
|
|
files; unsupported native saves preserve files. See NATIVE_API.md and decision
|
|
|
|
|
0008. Release tooling lives outside the runtime, with source manifests,
|
|
|
|
|
deterministic exports and staged installation/consumer checks. See RELEASING.md
|
|
|
|
|
for reproducibility scope.
|