Docs file normalization.

This commit is contained in:
Johannes Findeisen 2026-11-06 15:20:20 +01:00
commit 619c2d0808
46 changed files with 3587 additions and 2579 deletions

View file

@ -1,39 +1,46 @@
# Architecture
## Decision: modular engine first
## Decision: Modular Engine First
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.
---
## Layers
1. **FLTK/C++ GUI:** input, drawing, windows, view state.
2. **C core:** project model, commands, parameters, persistence, validation.
3. **C engine:** prepared processing graph, DSP, transport timing, event delivery.
3. **C engine:** prepared processing graph, DSP, transport timing, event
delivery.
4. **Backend:** hardware and system audio/MIDI integration.
The GUI must not directly modify engine internals. It calls a stable C API.
## Graph execution
---
## Graph Execution
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.
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.
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.
## Ownership and threads
---
## Ownership and Threads
- GUI thread owns FLTK widgets and view state.
- Project/control thread owns mutable project data and graph compilation.
@ -41,53 +48,59 @@ the optional JACK implementation drives that same realtime engine.
- Backend callbacks follow the backend's documented threading contract.
Only acknowledged retired plans are reclaimed during processing. Backend close
synchronizes callbacks before reclaiming remaining plans. See decision 0004
for the release/acquire protocol, bounded resource retention and recovery.
synchronizes callbacks before reclaiming remaining plans. See decision 0004 for
the release/acquire protocol, bounded resource retention and recovery.
## GUI backend ownership
---
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.
## GUI Backend Ownership
## Editable graph and canvas
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
Read-only C snapshots, monotonic node IDs, and a small set of C edit commands
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.
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.
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.
---
## Project ownership and persistence
## Project Ownership and Persistence
A C-owned project contains the graph, bounded track/clip arrays, stable ID
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.
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.
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.
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.
SequencerWidget owns selection, zoom and gestures only; musical range checks,
source-preserving trims and ID allocation remain C responsibilities.
---
## Assets and recording
## Assets and Recording
Audio paths are C clip metadata. A stopped preparation copies range/offset
information, deduplicates paths, validates and preloads bounded WAV assets into
@ -102,12 +115,14 @@ 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
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.
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.
## MIDI source
---
## MIDI Source
The C MIDI plan prepares bounded source-relative notes into sample spans and
renders a separate instrument buffer. `xtc_plan_process_sources` and
@ -116,10 +131,12 @@ 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
notes chase their absolute phase. See MIDI.md for event ordering, panic semantics,
capacities and test coverage. No new dependency is required.
notes chase their absolute phase. See MIDI.md for event ordering, panic
semantics, capacities and test coverage. No new dependency is required.
## Mix snapshots and history
---
## Mix Snapshots and History
`src/mixer` owns validated per-track settings and bounded gain/pan automation.
Audio/MIDI preparation copies and indexes these into source snapshots; paired
@ -128,16 +145,19 @@ 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
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.
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.
## 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.
## 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.

View file

@ -1,66 +1,146 @@
# Audio source editor workflows
# Audio Source Editor Workflows
The editor uses the existing bounded mono WAV reader. Stereo input is downmixed;
these are offline control-thread tools, not stereo restoration or time stretching.
The project format remains 16. No new dependencies are required.
these are offline control-thread tools, not stereo restoration or time
stretching. The project format remains 16. No new dependencies are required.
## Source inspection
---
## Source Inspection
Select an audio clip and use View / Sample editor or double-click it. The modal
editor loads an independent snapshot of the complete source, up to 4,194,304 frames
(about 87 seconds at 48 kHz). Drag or enter half-open native frame bounds; zoom
1..64 and View start control the visible region. Statistics show raw mono min/max,
RMS and mean/DC before clip gain, fades or mixer processing. Display clamps to
±1; sample statistics retain finite over-range values. Editing project MIDI still
opens the piano roll. This editor does not audition or stream long files.
editor loads an independent snapshot of the complete source, up to 4,194,304
frames (about 87 seconds at 48 kHz). Drag or enter half-open native frame
bounds; zoom 1..64 and View start control the visible region. Statistics show
raw mono min/max, RMS and mean/DC before clip gain, fades or mixer processing.
Display clamps to ±1; sample statistics retain finite over-range values. Editing
project MIDI still opens the piano roll. This editor does not audition or stream
long files.
## Trim clip to selection
---
Trim clip to selection applies source-frame endpoints within the current audible clip. Start rounds up and end rounds down to musical ticks; empty sub-tick ranges reject. Leading removal moves the timeline start by the same amount, preserving alignment. Source content, reference and clip gain/fade settings remain intact; project Undo restores the prior trim.
## Trim Clip to Selection
## Export and relink
Trim clip to selection applies source-frame endpoints within the current audible
clip. Start rounds up and end rounds down to musical ticks; empty sub-tick
ranges reject. Leading removal moves the timeline start by the same amount,
preserving alignment. Source content, reference and clip gain/fade settings
remain intact; project Undo restores the prior trim.
Export selected frames writes a new mono float32 WAV of the half-open selection at native rate. Save full copy and relink writes the complete source and relinks only the selected clip after successful preparation. The original source is never overwritten. Full-copy adoption requires matching source frame count/rate. Project undo restores the reference but retains the new file, which may be referenced by redo/history or other projects. Failed adoption removes its owned new file. Export/copy runs synchronously outside audio callbacks.
---
## Reverse selection
## Export and Relink
Reverse selection changes only the chosen source frames in a new local snapshot. Undo / redo toggles the most recent processing step; selection and view are unchanged. Processing does not affect playback until Save full copy and relink. Close discards unsaved local processing. Sources are bounded to 16 MiB of float samples; preparing a new edit can temporarily retain three snapshots (48 MiB), then two (32 MiB).
Export selected frames writes a new mono float32 WAV of the half-open selection
at native rate. Save full copy and relink writes the complete source and relinks
only the selected clip after successful preparation. The original source is
never overwritten. Full-copy adoption requires matching source frame count/rate.
Project undo restores the reference but retains the new file, which may be
referenced by redo/history or other projects. Failed adoption removes its owned
new file. Export/copy runs synchronously outside audio callbacks.
## Invert polarity
---
Invert polarity negates selected float samples; it is not time reversal. Over-range finite samples remain over-range; no implicit limiting is performed. Outside-selection frames are bit-preserved in the copied snapshot.
## Reverse Selection
## Remove DC offset
Reverse selection changes only the chosen source frames in a new local snapshot.
Undo / redo toggles the most recent processing step; selection and view are
unchanged. Processing does not affect playback until Save full copy and relink.
Close discards unsaved local processing. Sources are bounded to 16 MiB of float
samples; preparing a new edit can temporarily retain three snapshots (48 MiB),
then two (32 MiB).
Remove DC offset subtracts the mean of the selected range from only that range. This is a constant offset correction, not a high-pass filter. Selection boundaries may acquire discontinuities; use fades or zero-crossing tools where appropriate. Finite over-range results are retained; float overflow rejects the edit.
---
## Sample fades
## Invert Polarity
Sample fades offers four offline curves: linear in/out and equal-power in/out (sinusoidal amplitude). The entire selection is the fade duration; both endpoints are included in the curve. Fewer than two frames reject. These curves change copied sample data and stack with existing playback clip fades after adoption; they do not form a linked clip crossfade.
Invert polarity negates selected float samples; it is not time reversal.
Over-range finite samples remain over-range; no implicit limiting is performed.
Outside-selection frames are bit-preserved in the copied snapshot.
## Gain and normalize
---
Gain / normalize processes only selected samples. Mode 0 accepts linear gain 0..16; mode 1 accepts a linear peak target above 0 and at most 1, rejecting silent selections or required gain above 16. Finite values above unity are not clipped; float overflow rejects atomically. This changes sample data in the local snapshot, unlike the existing clip-gain normalization command; it provides neither true-peak nor LUFS processing.
## Remove DC Offset
## Select audible bounds
Remove DC offset subtracts the mean of the selected range from only that range.
This is a constant offset correction, not a high-pass filter. Selection
boundaries may acquire discontinuities; use fades or zero-crossing tools where
appropriate. Finite over-range results are retained; float overflow rejects the
edit.
Select audible bounds finds the first and last sample strictly above the chosen dBFS threshold and pads within the existing selection. It only changes selection; use Trim clip to selection to apply a nondestructive timeline trim, or export the selection. Entirely silent/below-threshold ranges leave selection unchanged. This is boundary detection, not automatic removal of internal gaps or a noise gate.
---
## Snap to zero crossings
## Sample Fades
Snap to zero crossings searches each selected boundary within a source-frame radius up to 65,536. It uses the mono source, chooses earlier positions on equal distance and changes both endpoints only if both searches yield a nonempty range. Source edges qualify only when their adjacent sample is zero. This can reduce boundary discontinuities but does not guarantee click-free output; musical-tick trimming/slicing can move a boundary away from the exact crossing.
Sample fades offers four offline curves: linear in/out and equal-power in/out
(sinusoidal amplitude). The entire selection is the fade duration; both
endpoints are included in the curve. Fewer than two frames reject. These curves
change copied sample data and stack with existing playback clip fades after
adoption; they do not form a linked clip crossfade.
## Detect attacks
---
Detect attacks uses absolute sample amplitude, an initial armed state, rearming below one quarter of the threshold and a minimum frame gap between retained attacks. It marks up to 128 source positions in orange; hovering the statistics shows exact frame indices. This is a threshold detector, not spectral onset detection, beat detection or tempo estimation. Selection/processing changes invalidate the markers. Slice clip at attacks splits the current clip reference at those positions in one project undo step and closes the editor. Edits in the local buffer must be saved/relinked first if the slices should use processed audio. Source positions round to nearest musical ticks; out-of-trim, edge and coalesced cuts are skipped. No usable cuts rejects. Left ID, source references and outside fades remain; inner explicit fades clear. Long explicit fades and existing automatic region fades can sound different after splitting. Timeline/source rounding may differ by one sample, so this is not sample-accurate timeline slicing. The no-explicit-fade aligned regression fixture remains bit-identical across slicing. No media files are written by slicing.
## Gain and Normalize
## Validation and timing limits
Gain / normalize processes only selected samples. Mode 0 accepts linear gain
0..16; mode 1 accepts a linear peak target above 0 and at most 1, rejecting
silent selections or required gain above 16. Finite values above unity are not
clipped; float overflow rejects atomically. This changes sample data in the
local snapshot, unlike the existing clip-gain normalization command; it provides
neither true-peak nor LUFS processing.
Focused model and GUI checks pass for each operation.
The integrated checks add allocation failure/cleanup, oversized sources, overflow,
clip ID rollback, exact selected samples, persistence, no-fade render comparison,
plugin fixtures, software JACK, Clang analysis, ASan/UBSan and install layouts.
The actual source editor and attack markers are shown in the
---
## Select Audible Bounds
Select audible bounds finds the first and last sample strictly above the chosen
dBFS threshold and pads within the existing selection. It only changes
selection; use Trim clip to selection to apply a nondestructive timeline trim,
or export the selection. Entirely silent/below-threshold ranges leave selection
unchanged. This is boundary detection, not automatic removal of internal gaps or
a noise gate.
---
## Snap to Zero Crossings
Snap to zero crossings searches each selected boundary within a source-frame
radius up to 65,536. It uses the mono source, chooses earlier positions on equal
distance and changes both endpoints only if both searches yield a nonempty
range. Source edges qualify only when their adjacent sample is zero. This can
reduce boundary discontinuities but does not guarantee click-free output;
musical-tick trimming/slicing can move a boundary away from the exact crossing.
---
## Detect Attacks
Detect attacks uses absolute sample amplitude, an initial armed state, rearming
below one quarter of the threshold and a minimum frame gap between retained
attacks. It marks up to 128 source positions in orange; hovering the statistics
shows exact frame indices. This is a threshold detector, not spectral onset
detection, beat detection or tempo estimation. Selection/processing changes
invalidate the markers. Slice clip at attacks splits the current clip reference
at those positions in one project undo step and closes the editor. Edits in the
local buffer must be saved/relinked first if the slices should use processed
audio. Source positions round to nearest musical ticks; out-of-trim, edge and
coalesced cuts are skipped. No usable cuts rejects. Left ID, source references
and outside fades remain; inner explicit fades clear. Long explicit fades and
existing automatic region fades can sound different after splitting.
Timeline/source rounding may differ by one sample, so this is not
sample-accurate timeline slicing. The no-explicit-fade aligned regression
fixture remains bit-identical across slicing. No media files are written by
slicing.
---
## Validation and Timing Limits
Focused model and GUI checks pass for each operation. The integrated checks add
allocation failure/cleanup, oversized sources, overflow, clip ID rollback, exact
selected samples, persistence, no-fade render comparison, plugin fixtures,
software JACK, Clang analysis, ASan/UBSan and install layouts. The actual source
editor and attack markers are shown in the
[screenshot gallery](../screenshots/README.md).
Trim placement preserves alignment in musical ticks; independent source/timeline
@ -69,5 +149,5 @@ click-free transitions are promised. Clip source data outside a local processing
selection remains intact; exported selections contain only their chosen frames.
Closing the editor discards unsaved local processing. Successful project Undo
keeps generated files for redo/history; only failed adoption removes its owned
new output. Rejecting sources above the editor's frame limit may temporarily
use the existing WAV reader's larger decode bound before rejecting them.
new output. Rejecting sources above the editor's frame limit may temporarily use
the existing WAV reader's larger decode bound before rejecting them.

View file

@ -1,128 +1,142 @@
# Audio engine requirements
# Audio Engine Requirements
## Real-time callback rules
## Real-Time Callback Rules
The callback must not allocate/free memory, lock blocking mutexes, perform
filesystem/network/console I/O, log, load plugins or samples, call GUI APIs,
or wait for another thread.
filesystem/network/console I/O, log, load plugins or samples, call GUI APIs, or
wait for another thread.
Use preallocated buffers and bounded work. Publish parameter and graph changes
through a documented real-time-safe mechanism. Verify atomics are lock-free
where required on the target platform.
## Processing model
---
## Processing Model
Process a bounded number of frames per block. Do not assume a fixed block size
unless the backend guarantees it. Define maximum frame and event counts and
safe behavior when those limits are reached.
unless the backend guarantees it. Define maximum frame and event counts and safe
behavior when those limits are reached.
The initial milestone is offline processing, then a test oscillator -> gain ->
meter/output graph, then a hardware backend.
## Clock and transport
---
## Clock and Transport
Audio sample position is the authoritative playback clock. Use integer or
rational timeline representations to avoid cumulative floating-point drift.
Define tempo-map conversion and behavior at tempo changes before implementing
tempo automation.
---
## Offline API
`xtc_graph_create` accepts 1..256 nodes; IDs are monotonic stable identifiers, never reused
within a graph's lifetime. Enumeration indexes are dense and distinct from IDs. Supported nodes are gain (any finite multiplier), sine
`xtc_graph_create` accepts 1..256 nodes; IDs are monotonic stable identifiers,
never reused within a graph's lifetime. Enumeration indexes are dense and
distinct from IDs. Supported nodes are gain (any finite multiplier), sine
oscillator (finite nonnegative Hz), pass-through peak meter and audio output.
Ports carry mono float audio: input 0, output 1. Oscillators have no input and
output nodes have no output. Port lookup reports direction, signal type and
channel count. MIDI/control ports and channel conversion are not implemented;
invalid ports/directions, duplicate edges and cycles are rejected before an
edit becomes visible. Fan-out copies a source to consumers; fan-in sums inputs
invalid ports/directions, duplicate edges and cycles are rejected before an edit
becomes visible. Fan-out copies a source to consumers; fan-in sums inputs
without normalization. Failed additions leave IDs and graph contents unchanged.
`xtc_graph_compile(graph, sample_rate, max_frames, &plan)` runs on the control
thread. Empty graphs compile to silence. Sample rate must be finite and positive,
oscillators must be at or below Nyquist, and max_frames is 1..65536. Compilation
allocates and copies topology/parameters, builds a topological schedule and
input adjacency, and allocates paired max_frames float buffers per node for stereo. The
maximum buffer storage is 128 MiB per plan (32 MiB for the GUI 64-node limit). Compilation may fail with an
allocation error; output plan is then NULL. Destroy a plan on the control thread.
thread. Empty graphs compile to silence. Sample rate must be finite and
positive, oscillators must be at or below Nyquist, and max_frames is 1..65536.
Compilation allocates and copies topology/parameters, builds a topological
schedule and input adjacency, and allocates paired max_frames float buffers per
node for stereo. The maximum buffer storage is 128 MiB per plan (32 MiB for the
GUI 64-node limit). Compilation may fail with an allocation error; output plan
is then NULL. Destroy a plan on the control thread.
`xtc_plan_process` uses only prepared state: no allocation/free, sorting,
logging, I/O or locks. Each call accepts up to max_frames; oversized calls
return capacity errors without modifying output or state. Output storage must
hold frame_count floats. NULL input means silence; input/output may alias.
Zero frames leaves all state unchanged. No plan operations may run concurrently
with processing, including reset, meter reads or destruction.
hold frame_count floats. NULL input means silence; input/output may alias. Zero
frames leaves all state unchanged. No plan operations may run concurrently with
processing, including reset, meter reads or destruction.
Unconnected non-oscillator nodes receive external input. Connected inputs
receive only the sum of their upstream buffers. Explicit output nodes are
summed to the caller's output. If none exist, terminal nodes are summed for
receive only the sum of their upstream buffers. Explicit output nodes are summed
to the caller's output. If none exist, terminal nodes are summed for
compatibility with the original gain-chain API, unless
`xtc_graph_require_output_nodes(graph, 1)` is set. The GUI uses that policy;
no output nodes means silence. Disconnected components cannot
contribute when explicit outputs exist, but they are still processed. Floating
point overflow and nonfinite input become silence per sample. No clipping or
normalization is applied to finite samples.
`xtc_graph_require_output_nodes(graph, 1)` is set. The GUI uses that policy; no
output nodes means silence. Disconnected components cannot contribute when
explicit outputs exist, but they are still processed. Floating point overflow
and nonfinite input become silence per sample. No clipping or normalization is
applied to finite samples.
Oscillators start at phase zero and carry phase continuously across blocks;
reset clears phases and meter peaks. Meter reads return the last nonempty
block's absolute peak. These are same-thread reads, not a thread-safe GUI
meter bridge. Rendering is deterministic on the same build/platform regardless
of block partitioning; libm results need not be bit-identical across platforms.
block's absolute peak. These are same-thread reads, not a thread-safe GUI meter
bridge. Rendering is deterministic on the same build/platform regardless of
block partitioning; libm results need not be bit-identical across platforms.
`xtc_graph_process_offline` remains a convenience helper, compiling at 48000 Hz
and destroying the plan after one block. It accepts a finite external-input
gain and requires non-NULL input/output. It now follows routing rather than
and destroying the plan after one block. It accepts a finite external-input gain
and requires non-NULL input/output. It now follows routing rather than
multiplying every gain in insertion order. It allocates and is not suitable for
an audio callback. Use a persistent plan for phase continuity and repeated blocks.
an audio callback. Use a persistent plan for phase continuity and repeated
blocks.
Tests in `tests/unit/test_offline.c` cover known oscillator samples, gain and
meter output, reset and partition equivalence, fan-out/fan-in, reverse insertion
order, disconnected nodes, in-place processing, nonfinite input, invalid ports,
cycles, capacity limits, plan lifetime and repeated compilation/cleanup.
The offline API remains single-owner. The realtime/backend APIs below add
cycles, capacity limits, plan lifetime and repeated compilation/cleanup. The
offline API remains single-owner. The realtime/backend APIs below add
publication and JACK processing; offline tests alone do not qualify hardware.
## Backend and realtime bridge
---
## Backend and Realtime Bridge
Use `xtc_backend.h` on one serialized control thread:
1. Open a config with `XTC_BACKEND_JACK`, a client name, channel_count 1 (or 2 for stereo output), and
max_frames 1..65536. It must accommodate the server's current buffer size.
The actual rate and buffer size are returned through get_status. Open does
not start the server or acquire devices directly. Invalid config returns
invalid argument, missing build support returns unsupported, and JACK setup
failures return I/O error. If cleanup itself fails, a non-NULL output handle
is preserved for close retry; no other operation is allowed on that handle.
2. Submit a graph (copied/compiled at the server rate), then start. Starting
an already running client is an invalid-state error; stopping a stopped
client succeeds. With no submitted graph, callbacks produce silence.
1. Open a config with `XTC_BACKEND_JACK`, a client name, channel_count 1 (or 2
for stereo output), and max_frames 1..65536. It must accommodate the server's
current buffer size. The actual rate and buffer size are returned through
get_status. Open does not start the server or acquire devices directly.
Invalid config returns invalid argument, missing build support returns
unsupported, and JACK setup failures return I/O error. If cleanup itself
fails, a non-NULL output handle is preserved for close retry; no other
operation is allowed on that handle.
2. Submit a graph (copied/compiled at the server rate), then start. Starting an
already running client is an invalid-state error; stopping a stopped client
succeeds. With no submitted graph, callbacks produce silence.
3. Enumerate available audio ports with visit_ports. Names are borrowed only
during the visitor; do not reenter backend operations from the visitor.
Connect the client's input to a remote output or its output to a remote
input explicitly. JACK maps device selection to server/device port routes;
ALSA device identifiers are not accepted here. The server controls rate,
period and hardware configuration outside this API. Names/ports can disappear
between enumeration and connection; route failures are returned as I/O errors.
Connect the client's input to a remote output or its output to a remote input
explicitly. JACK maps device selection to server/device port routes; ALSA
device identifiers are not accepted here. The server controls rate, period
and hardware configuration outside this API. Names/ports can disappear
between enumeration and connection; route failures are returned as I/O
errors.
4. During playback, submit edited graphs and periodically collect retired
resources. Capacity means one pending graph is still awaiting adoption;
keep the current graph and retry later. Stopped submissions instead compile
a replacement engine off-thread and swap only after success, while callbacks
are quiescent. Its block/revision counters restart and oscillator phase resets;
no edit is blocked behind a plan that cannot be adopted until playback. get_status is a nonblocking snapshot
of rate, frames, running/server-loss/rate-change flags, xruns, processed
blocks and last process result. No active DSP memory is exposed.
resources. Capacity means one pending graph is still awaiting adoption; keep
the current graph and retry later. Stopped submissions instead compile a
replacement engine off-thread and swap only after success, while callbacks
are quiescent. Its block/revision counters restart and oscillator phase
resets; no edit is blocked behind a plan that cannot be adopted until
playback. get_status is a nonblocking snapshot of rate, frames,
running/server-loss/rate-change flags, xruns, processed blocks and last
process result. No active DSP memory is exposed.
5. Stop pauses the retained DSP state after deactivation. Restart resumes it.
Close removes the JACK client, synchronizes all callbacks, then destroys
plans. Stop/close failures preserve the handle for recovery/retry. No plan
is freed on the audio thread. Sample-rate change or server loss requires
plans. Stop/close failures preserve the handle for recovery/retry. No plan is
freed on the audio thread. Sample-rate change or server loss requires
close/reopen and a fresh submission; there is no automatic reconnect.
Buffers can change size. In-limit changes process normally; oversized blocks
are silent and report capacity errors until the size is supported again.
Rate changes latch silence and invalid-state processing results. A missing input
Buffers can change size. In-limit changes process normally; oversized blocks are
silent and report capacity errors until the size is supported again. Rate
changes latch silence and invalid-state processing results. A missing input
buffer is treated as unavailable input (silence). A missing output buffer is
reported as an I/O process error. Shutdown callbacks only set the loss flag;
the control thread closes the client. No JACK or GUI types cross the C API.
reported as an I/O process error. Shutdown callbacks only set the loss flag; the
control thread closes the client. No JACK or GUI types cross the C API.
The standalone `xtc_realtime.h` API allows hardware-independent consumers and
stress tests. Submit compiles on the producer; process adopts at a block
@ -134,12 +148,15 @@ JACK notification callbacks were audited for prohibited operations; JACK's own
internal realtime guarantees depend on the installed implementation/server.
Validation commands and results are recorded in BUILD.md and the integration
test README. Current limitations: mono input, no crossfaded graph changes, no automatic reconnect,
no physical-device or long-duration endurance qualification. The FLTK shell is
connected to backend controls and status; see GUI.md. Its editors remain
placeholders except the modular editor described below.
test README. Current limitations: mono input, no crossfaded graph changes, no
automatic reconnect, no physical-device or long-duration endurance
qualification. The FLTK shell is connected to backend controls and status; see
GUI.md. Its editors remain placeholders except the modular editor described
below.
## Graph snapshots and edits
---
## Graph Snapshots and Edits
The control-thread API exposes copies through get_counts, get_node(index) and
get_connection(index), including node IDs/types/values and port endpoints. No
@ -154,17 +171,17 @@ and returns a caller-owned candidate on success. On failure candidate is NULL,
source and added_id are unchanged. added_id is optional and written only for
ADD_NODE. ADD_NODE validates type and the used parameter (finite gain, or finite
nonnegative oscillator Hz). Meter/output do not use the value field. Compile
additionally checks the oscillator against the actual sample rate. Removing
a node removes its incident connections; disconnecting a nonexistent edge
returns not-found. Missing nodes/ports, reversed directions, duplicate edges,
cycles and unknown command/type values are rejected without visible edits.
additionally checks the oscillator against the actual sample rate. Removing a
node removes its incident connections; disconnecting a nonexistent edge returns
not-found. Missing nodes/ports, reversed directions, duplicate edges, cycles and
unknown command/type values are rejected without visible edits.
The GUI keeps a 64-node graph requiring explicit output nodes. It creates a
candidate, submits that exact candidate to the backend, then swaps its owned
graph and refreshes the canvas only if submission succeeds. Failed compile or
pending-slot pressure destroys the candidate and retains both the displayed
graph and previously submitted engine plan. When disconnected at startup,
edits can be stored locally and are submitted on reconnect. Lost/changed active
graph and previously submitted engine plan. When disconnected at startup, edits
can be stored locally and are submitted on reconnect. Lost/changed active
backends reject edits until reconnect. Reconnect submits the retained graph.
Submitted/active revision counters expose adoption without exposing plan
@ -181,20 +198,21 @@ silent under the GUI policy. Terminal fallback remains the default for earlier
offline/standalone callers. Input on unconnected nodes retains the existing
external-input contract; the GUI does not connect external JACK ports.
---
## Transport
Backend status includes a uint64 sample_position. Each successful processing
callback advances it by its exact frame count (including silence); errors do
not advance it. The counter uses compiler atomics, verified always lock-free
before backend setup, with overflow detected rather than wrapped. No extra
allocation, I/O, mutex or GUI work occurs in callbacks. Fields remain individually
coherent snapshots.
callback advances it by its exact frame count (including silence); errors do not
advance it. The counter uses compiler atomics, verified always lock-free before
backend setup, with overflow detected rather than wrapped. No extra allocation,
I/O, mutex or GUI work occurs in callbacks. Fields remain individually coherent
snapshots.
Stop pauses the clock. Start resumes; stopped and running graph submissions
preserve position even when plan/revision counters reset. Reopen starts at zero.
xtc_backend_seek changes position only on a healthy stopped backend; it does
not rewind oscillator phase. Project tick/sample conversions use integer floor
xtc_backend_seek changes position only on a healthy stopped backend; it does not
rewind oscillator phase. Project tick/sample conversions use integer floor
arithmetic at constant tempo, with explicit capacity errors and rates 1..768000.
The GUI observes the counter every 100 ms, so visual updates are block-quantized
and delayed by polling. No external JACK transport synchronization is provided.
@ -203,8 +221,9 @@ Silent clip metadata is control-thread data, never traversed by the callback.
Audio/MIDI scheduling uses compiled event/clip plans rather than direct callback
access to the editable project.
---
## Audio source and recording
## Audio Source and Recording
xtc_audio.h provides control-thread import and immutable preloaded clip plans.
The backend prepares a project only while stopped: build audio candidate,
@ -214,31 +233,33 @@ assets produce explicit issue metadata and silence; OOM/capacity fails.
Graph-only edits still use the existing pending/retired mechanism while running.
Each callback renders the plan into a preallocated mono timeline buffer using
the authoritative sample position. xtc_realtime_process_audio passes that
buffer into the compiled graph; XTC_NODE_AUDIO (output port only) reads it,
the authoritative sample position. xtc_realtime_process_audio passes that buffer
into the compiled graph; XTC_NODE_AUDIO (output port only) reads it,
independently from external JACK input. The old process APIs remain wrappers
with a silent timeline. Import creates a source/output route if necessary.
No samples or GUI/project arrays are shared with editable state. Clip mixing,
rate conversion and graph processing are bounded; see AUDIO_FILES.md for limits.
with a silent timeline. Import creates a source/output route if necessary. No
samples or GUI/project arrays are shared with editable state. Clip mixing, rate
conversion and graph processing are bounded; see AUDIO_FILES.md for limits.
A borrowed recorder can be attached/detached only while stopped. Raw JACK input
is copied to its SPSC ring before graph processing. Producer and consumer index
release/acquire operations protect payload ownership; all atomics are checked
always lock-free. The control thread drains to a temporary float32 WAV, then
finalizes only after successful stop/close has synchronized the callback.
An overflowing ring or unavailable input creates a sticky error; later input is
dropped, reported and never silently joined into a continuous-looking take.
A partial overflow take is finalized for manual recovery. File errors retain
the previous destination and remove unfinished temporaries.
finalizes only after successful stop/close has synchronized the callback. An
overflowing ring or unavailable input creates a sticky error; later input is
dropped, reported and never silently joined into a continuous-looking take. A
partial overflow take is finalized for manual recovery. File errors retain the
previous destination and remove unfinished temporaries.
GUI recording uses a two-second ring and 100 ms control-thread draining, with
no worker thread. A GUI stall can overflow it. No callback allocation/free, file
GUI recording uses a two-second ring and 100 ms control-thread draining, with no
worker thread. A GUI stall can overflow it. No callback allocation/free, file
I/O, logging, locks or FLTK calls were added. Backend stop failure preserves the
borrowed recorder; the caller must not free it before successful quiescence.
Live software source/capture acceptance and concurrent ring tests pass; physical
recording, latency compensation and disk endurance remain unverified.
## MIDI source
---
## MIDI Source
The C MIDI plan prepares bounded source-relative notes into sample spans and
renders a separate instrument buffer. `xtc_plan_process_sources` and
@ -247,10 +268,12 @@ 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
notes chase their absolute phase. See MIDI.md for event ordering, panic semantics,
capacities and test coverage. No new dependency is required.
notes chase their absolute phase. See MIDI.md for event ordering, panic
semantics, capacities and test coverage. No new dependency is required.
## Stereo mixing and automation
---
## Stereo Mixing and Automation
Track settings and automation are copied into immutable source-plan snapshots.
`xtc_audio_plan_render_stereo` and `xtc_midi_plan_render_stereo` pan track audio
@ -260,11 +283,13 @@ retain gain/mute/solo/automation but ignore pan. `xtc_plan_process_stereo` and
with one oscillator phase advance, preserving mono APIs and graph publication.
The backend preallocates paired buffers, registers an optional output_right port
and publishes independently atomic output peak snapshots. Callback work remains
bounded and allocation/I/O/lock free. Meter UI reads no active plan memory.
See MIXER.md for exact pan law, automation interpolation, event/source exceptions,
bounded and allocation/I/O/lock free. Meter UI reads no active plan memory. See
MIXER.md for exact pan law, automation interpolation, event/source exceptions,
memory/history bounds and verification. Input/recording remain mono.
## Native processing
---
## Native Processing
Graph-native effects/sources use copied versioned descriptors, two independently
prepared mono instances and the existing plan ownership/retirement protocol.
@ -274,19 +299,24 @@ without rolling back already advanced DSP. Native event instruments can render
explicitly into graph instrument buffers. See NATIVE_API.md for exact lifecycle
and serialization limits and PERFORMANCE.md for measured workload and budgets.
## Ownership audit
---
## Ownership Audit
[RELIABILITY.md](RELIABILITY.md) records resource owners, publication and
reclamation ordering, callback work bounds and repeatable diagnostic tests.
Graph stereo rendering completes both channels before writing caller outputs;
either output may equal a source, but the output buffers must not overlap.
## Quality policy
---
[AUDIO_QUALITY.md](AUDIO_QUALITY.md) defines absolute musical timing, tempo snapshot
changes, scheduled source envelopes and configurable master transitions. The GUI
enables 5 ms fades; low-level defaults remain raw. `xtc_backend_request_stop` is
asynchronous and does not establish quiescence; control-thread collection after
its callback acknowledgement deactivates JACK. Immediate stop/close remain
available. Sample rate and buffer frames are reported, but end-to-end latency
and native-node delay compensation are not implemented.
## Quality Policy
[AUDIO_QUALITY.md](AUDIO_QUALITY.md) defines absolute musical timing, tempo
snapshot changes, scheduled source envelopes and configurable master
transitions. The GUI enables 5 ms fades; low-level defaults remain raw.
`xtc_backend_request_stop` is asynchronous and does not establish quiescence;
control-thread collection after its callback acknowledgement deactivates JACK.
Immediate stop/close remain available. Sample rate and buffer frames are
reported, but end-to-end latency and native-node delay compensation are not
implemented.

View file

@ -1,12 +1,12 @@
# Audio clips and recording
# Audio Clips and Recording
## Import and playback
## Import and Playback
File/Import WAV (Ctrl+I) loads a WAV into the selected track at the playhead.
With no selected track it creates an Audio track. Add region still creates a
silent placeholder. Imported clips use the existing move/trim controls.
Import stops playback, validates a candidate project, and commits only after
successful backend preparation (or locally when disconnected).
silent placeholder. Imported clips use the existing move/trim controls. Import
stops playback, validates a candidate project, and commits only after successful
backend preparation (or locally when disconnected).
Supported input: little-endian RIFF/WAVE, PCM16/24/32 and IEEE float32, one or
two channels, 1..768000 Hz. Stereo is averaged to mono. Compressed WAV, RF64,
@ -15,23 +15,22 @@ unsupported. Nonfinite float data and malformed/truncated files are rejected.
There is no new dependency: the bounded C decoder/writer is covered by known
sample, truncation and invalid-codec tests. See decision 0005.
Audio is preloaded before playback: maximum 64 distinct paths per plan,
16777216 mono frames per asset (64 MiB), 67108864 total frames (256 MiB).
Import rejects files over 256 MiB or over 4096 chunks. No streaming decoder,
waveform cache, peak drawing or background load worker exists yet. Preloading
can briefly block the GUI. Candidate compilation retains the old plan until
success; temporary build memory can include both old and candidate buffers.
Audio is preloaded before playback: maximum 64 distinct paths per plan, 16777216
mono frames per asset (64 MiB), 67108864 total frames (256 MiB). Import rejects
files over 256 MiB or over 4096 chunks. No streaming decoder, waveform cache,
peak drawing or background load worker exists yet. Preloading can briefly block
the GUI. Candidate compilation retains the old plan until success; temporary
build memory can include both old and candidate buffers.
The callback renders only overlapping clip ranges from memory. Clips on all
tracks are summed through their mix/automation settings into the graph's
**Audio clips** source node.
Import creates that source and connects it to an existing/new output if no
audio source exists. Subsequent routing is explicit: disconnect it or route it
through gain/meter nodes in Modular Graph. Removing the output silences editor
graphs. This source does not monitor JACK input; unconnected ordinary graph
inputs retain their earlier external-input behavior. Track faders/pan/mute
are implemented by the track mixer (see MIXER.md); individual
track graph routing remains deferred.
tracks are summed through their mix/automation settings into the graph's **Audio
clips** source node. Import creates that source and connects it to an
existing/new output if no audio source exists. Subsequent routing is explicit:
disconnect it or route it through gain/meter nodes in Modular Graph. Removing
the output silences editor graphs. This source does not monitor JACK input;
unconnected ordinary graph inputs retain their earlier external-input behavior.
Track faders/pan/mute are implemented by the track mixer (see MIXER.md);
individual track graph routing remains deferred.
Linear interpolation converts source sample rate to backend rate. There is no
antialiasing filter, time stretching, looping or fades. Musical durations round
@ -45,7 +44,9 @@ Stop pauses the sample clock. Graph-only edits can still be submitted during
playback. Graph recompilation can reset oscillator phase as before. Playback
does not stop automatically at the arrangement end.
## Assets and recovery
---
## Assets and Recovery
Version 2 projects store external absolute canonical paths, not sample data.
Save the project AND keep its referenced WAV files. Save As changes only the
@ -56,20 +57,24 @@ removed; the next preparation detects it. Unreadable, malformed and unsupported
assets are also reported, rather than substituted with unrelated files.
Capacity/OOM is a preparation failure and retains the old plans.
Select the affected clip, then File/Relink selected audio to choose a replacement.
Relinking retains its original timeline/source bounds. The replacement is
validated on the next Play; a shorter replacement pads beyond its end with
silence. Saving retains unresolved references for later repair. Missing assets
are also checked on Open without a working backend, using a temporary plan.
Automatic asset search, content hashes and portable project bundles are deferred.
Select the affected clip, then File/Relink selected audio to choose a
replacement. Relinking retains its original timeline/source bounds. The
replacement is validated on the next Play; a shorter replacement pads beyond its
end with silence. Saving retains unresolved references for later repair. Missing
assets are also checked on Open without a working backend, using a temporary
plan. Automatic asset search, content hashes and portable project bundles are
deferred.
---
## Recording
Record (or Audio/Record WAV, Shift+F5) opens a destination chooser, stops playback,
prepares the project, opens a temporary mono float32 WAV and starts JACK.
Connect a source to xtc's JACK input using an external patchbay; no physical or
software route is created automatically. Without a route the take contains
silence. Recording captures raw mono JACK input, not the output/effect mix.
Record (or Audio/Record WAV, Shift+F5) opens a destination chooser, stops
playback, prepares the project, opens a temporary mono float32 WAV and starts
JACK. Connect a source to xtc's JACK input using an external patchbay; no
physical or software route is created automatically. Without a route the take
contains silence. Recording captures raw mono JACK input, not the output/effect
mix.
A preallocated two-second SPSC ring transfers complete callback blocks to the
GUI/control thread, which drains it every 100 ms. The producer uses verified
@ -80,53 +85,59 @@ error rather than hiding gaps or compressing time.
Stop first synchronizes the callback, detaches the recorder, drains remaining
samples, writes the final WAV header, flushes/fsyncs/closes and renames the
same-directory temporary using an atomic no-clobber link; an existing destination is refused. A successful nonempty take is
imported at its capture position on the selected track (or a new Recording
track), then the document becomes unsaved. Save the project after Stop. Seeking
back to the take and pressing Play auditions it. Recording continues existing
arrangement playback, but there is no automatic input monitoring, punch, loop,
latency compensation, multitrack arming or overdub alignment compensation.
same-directory temporary using an atomic no-clobber link; an existing
destination is refused. A successful nonempty take is imported at its capture
position on the selected track (or a new Recording track), then the document
becomes unsaved. Save the project after Stop. Seeking back to the take and
pressing Play auditions it. Recording continues existing arrangement playback,
but there is no automatic input monitoring, punch, loop, latency compensation,
multitrack arming or overdub alignment compensation.
Graph/arrangement edits, project file actions, reconnect and close are blocked
until Stop finishes the take. Deactivation failure retains the recorder for a
safe retry. On overflow a finalized partial WAV is retained for manual recovery
and is not automatically added. On import/preparation failure the completed
take remains at its chosen path with an explicit recovery message. I/O errors
are reported; failed temporary data is retained and its recovery path is reported. Recordings
are RIFF-limited to 1073741814 frames, but the smaller playback/import asset
limit still applies: longer takes may need external splitting before import.
There is no crash-recovery journal. The file is mode 0600; parent-directory
fsync and preservation of previous permissions are not implemented.
and is not automatically added. On import/preparation failure the completed take
remains at its chosen path with an explicit recovery message. I/O errors are
reported; failed temporary data is retained and its recovery path is reported.
Recordings are RIFF-limited to 1073741814 frames, but the smaller
playback/import asset limit still applies: longer takes may need external
splitting before import. There is no crash-recovery journal. The file is mode
0600; parent-directory fsync and preservation of previous permissions are not
implemented.
---
## Verification
make test includes deterministic WAV/graph samples, resampling/overlap/trim,
malformed inputs, missing/relinked assets, SPSC concurrency/overflow/cleanup,
and record -> save/reopen -> exact playback through the backend callback.
make test-gui covers the workflow and rollback with a fake backend.
make test-gui-jack records/reopens a silent take using real JACK callbacks.
make test-jack-audio routes only two software clients: known source -> recorded
WAV -> saved/reopened project -> graph playback -> capture assertion.
Physical input/output, device latency, slow-disk endurance and server loss
during a real recording remain unverified.
and record -> save/reopen -> exact playback through the backend callback. make
test-gui covers the workflow and rollback with a fake backend. make
test-gui-jack records/reopens a silent take using real JACK callbacks. make
test-jack-audio routes only two software clients: known source -> recorded WAV
-> saved/reopened project -> graph playback -> capture assertion. Physical
input/output, device latency, slow-disk endurance and server loss during a real
recording remain unverified.
## Recording protection
---
## Recording Protection
The default recorder and GUI require a new destination, including rejecting
symlinks. Publication also refuses files created during recording. The explicit
`xtc_recorder_create_replace` API retains authorized replacement for callers
that deliberately need it; the GUI never uses it. After a failed finish, copy
`xtc_recorder_recovery_path` before destruction. Disk-write/finalization failures
retain the temporary. Destruction after producer quiescence attempts to drain
and finalize accepted data; normal code should call finish and inspect errors.
The GUI refuses closure while recording and prints the retained/finalized path
on exceptional destruction.
`xtc_recorder_recovery_path` before destruction. Disk-write/finalization
failures retain the temporary. Destruction after producer quiescence attempts to
drain and finalize accepted data; normal code should call finish and inspect
errors. The GUI refuses closure while recording and prints the
retained/finalized path on exceptional destruction.
Use `xtc-check --recover-recording TEMP NEW.wav` to copy complete float32 frames
from an interrupted xtc WAV into a new WAV. Source and existing destinations are
never modified. A trailing partial frame is ignored. This handles xtc's canonical
44-byte mono float32 header only, not arbitrary damaged WAVs. Recovery is explicit
and control-thread only; no orphan scan runs during playback.
never modified. A trailing partial frame is ignored. This handles xtc's
canonical 44-byte mono float32 header only, not arbitrary damaged WAVs. Recovery
is explicit and control-thread only; no orphan scan runs during playback.
`make test-recording` simulates 30 minutes at 8 kHz with repeated ring wrapping
and draining, destination races/symlinks, a child-local disk-size limit, prefix
@ -138,20 +149,22 @@ player still limits each asset to 16M frames. Longer valid takes may require
external splitting before import. No directory fsync/power-loss guarantee is
claimed. WAV destinations are chosen explicitly by the user.
## Non-destructive clip editing
---
## Non-Destructive Clip Editing
Move clips by dragging their body; drag the left/right edge to trim within the
existing source length. Ctrl+D duplicates adjacent to the selected clip. Delete
removes the reference. With an audio clip selected, click the ruler while stopped
to position the cursor strictly inside it, then choose **Edit / Split audio at
cursor**, press S with timeline focus, or use the clip context menu. The right
piece becomes selected. The four-pixel minimum draw width is also selectable for
very short clips; zoom in for precise edge gestures.
removes the reference. With an audio clip selected, click the ruler while
stopped to position the cursor strictly inside it, then choose **Edit / Split
audio at cursor**, press S with timeline focus, or use the clip context menu.
The right piece becomes selected. The four-pixel minimum draw width is also
selectable for very short clips; zoom in for precise edge gestures.
**Edit / Audio clip gain and fades** (A with timeline focus, also in the context
menu) takes three numbers: linear gain 0..4, fade-in ticks, fade-out ticks.
Defaults are `1 0 0`; 0 fade disables that explicit fade. Each duration can be up
to the original source length, even after trimming; overlapping long fades
Defaults are `1 0 0`; 0 fade disables that explicit fade. Each duration can be
up to the original source length, even after trimming; overlapping long fades
attenuate a short clip without extending it. MIDI clips reject these operations;
silent metadata regions support them for later audio relinking.
@ -165,9 +178,9 @@ No destructive audio processing or file deletion command is supplied.
A split retains the original source length/path/name/gain on both pieces. Its
right offset advances by the left length, keeping trims reversible within the
same source. Left keeps its ID and fade-in; right gets a fresh ID and fade-out.
Inner explicit fades are cleared. Gain/fades are retained on duplication.
Moving does not alter offsets; trimming changes only timeline/source bounds and
makes explicit fades follow the new audible boundaries.
Inner explicit fades are cleared. Gain/fades are retained on duplication. Moving
does not alter offsets; trimming changes only timeline/source bounds and makes
explicit fades follow the new audible boundaries.
The renderer maps tick boundaries by the existing absolute floor conversion,
linearly resamples the source and never reads beyond its samples. Its audible
@ -177,27 +190,27 @@ length up to a tick). A fade uses `age / fade_in_frames` at the start and
use the smaller value. The first/last audible samples are zero when their
respective fade has a positive sample duration. Durations rounding to zero
samples are disabled. A one-sample clip with any active fade is silent. Explicit
fade gain multiplies the existing optional safety envelope, then clip gain,
then track gain/pan/automation. No limiter is added; gains/overlaps may clip.
fade gain multiplies the existing optional safety envelope, then clip gain, then
track gain/pan/automation. No limiter is added; gains/overlaps may clip.
The GUI retains its 5 ms boundary safety fades, including new split edges, so
cuts can introduce a brief dip rather than an abrupt discontinuity. The raw C
plan still defaults to zero safety fade for exact-sample/offline consumers;
callers requesting raw cuts accept possible clicks. Tick quantization can place
an edit up to one output sample away from a separately rounded boundary; this
is not sample-resolution audio editing. Manual listening remains pending.
an edit up to one output sample away from a separately rounded boundary; this is
not sample-resolution audio editing. Manual listening remains pending.
Time-stretching, independent pitch shifting and crossfade tools are future work;
no suitable algorithm/quality target or new dependency has been selected.
### Cached waveform display
Timeline audio clips show mono min/max peaks from an immutable C summary,
shared by reference path across split/duplicated clips. Up to 64 paths each
cache at most 1024 bins (fewer for short files). It uses playback's WAV parser,
downmix and file/frame bounds; creation temporarily loads one WAV then frees
its sample storage. Bins cover every source sample, including a final partial
bin. The display follows source offsets and current tempo; it shows **raw source
peaks before clip/track gain and fades**, clamped visually to +/-1. It is an
Timeline audio clips show mono min/max peaks from an immutable C summary, shared
by reference path across split/duplicated clips. Up to 64 paths each cache at
most 1024 bins (fewer for short files). It uses playback's WAV parser, downmix
and file/frame bounds; creation temporarily loads one WAV then frees its sample
storage. Bins cover every source sample, including a final partial bin. The
display follows source offsets and current tempo; it shows **raw source peaks
before clip/track gain and fades**, clamped visually to +/-1. It is an
arrangement aid, not a sample editor or loudness meter.
Loading/rebuilding is synchronous on the GUI/control thread during project
@ -219,4 +232,5 @@ cover short-clip selection, shared previews, split/duplicate/delete and undo.
The modal audio source editor also provides frame selection, reversible
processing, selection export, copy adoption and transient slicing. See
[AUDIO_EDITOR.md](AUDIO_EDITOR.md) for commands, undo behavior and mono/size limits.
[AUDIO_EDITOR.md](AUDIO_EDITOR.md) for commands, undo behavior and mono/size
limits.

View file

@ -1,19 +1,21 @@
# Timing, transitions and audio quality
# Timing, Transitions and Audio Quality
Deterministic transition processing and tests are included. Musical time still
uses a single constant tempo, 960 ticks per quarter note and fixed 4/4 meter;
it does not introduce a tempo map, meter editor or JACK transport following.
uses a single constant tempo, 960 ticks per quarter note and fixed 4/4 meter; it
does not introduce a tempo map, meter editor or JACK transport following.
Project format remains v4 (reads v1–v3).
## Timing policy
---
## Timing Policy
`xtc_project_set_tempo` accepts 20000–300000 milli-BPM and changes the editable
model only. Build new audio/MIDI/mixer plans while stopped to adopt it. Existing
plans keep their prepared sample positions and mix snapshots. The backend retains
its absolute sample counter through preparation; explicitly seek if the caller
wants to preserve a particular musical tick after changing tempo. The GUI has no
tempo-change command yet. Meter is always 4/4, has no setter and does not vary
within a project.
plans keep their prepared sample positions and mix snapshots. The backend
retains its absolute sample counter through preparation; explicitly seek if the
caller wants to preserve a particular musical tick after changing tempo. The GUI
has no tempo-change command yet. Meter is always 4/4, has no setter and does not
vary within a project.
Every musical boundary is converted from its **absolute** tick:
@ -25,10 +27,11 @@ Conversions use bounded unsigned integer arithmetic, not accumulated floating
beat durations. The inverse also floors; a sample-to-tick roundtrip can lose a
tick because of quantization. At very low supported rates several ticks can map
to one sample. Collapsed note spans are silent. Automation uses the last point
in tick order at a shared sample. Clip/note intervals are half-open `[start,end)`;
a boundary exactly at the next block belongs to that next block. The MIDI input
array preserves caller order for equal offsets (for example on/off versus off/on).
A discontinuous MIDI render position resets live voices before rendering.
in tick order at a shared sample. Clip/note intervals are half-open
`[start,end)`; a boundary exactly at the next block belongs to that next block.
The MIDI input array preserves caller order for equal offsets (for example
on/off versus off/on). A discontinuous MIDI render position resets live voices
before rendering.
Changing tempo shifts musical boundaries, including source offsets, on newly
compiled plans. WAV samples keep their original playback speed with linear rate
@ -36,14 +39,17 @@ conversion; this is not audio time stretching. Scheduled note phase comes from
absolute note age, so seeks and block partitions reproduce the same waveform.
The graph oscillator/native graph clock is plan-local, rather than project time.
## Boundary fades and smoothing
---
## Boundary Fades and Smoothing
The GUI configures `xtc_backend_set_transition_frames(rate/200)`: approximately
5 ms at the current rate (integer frames, floor). Configuration is control-thread
only while stopped, before project preparation. The low-level backend and
source-plan APIs default to zero transition frames for source compatibility.
Call `xtc_audio_plan_set_fade_frames` and `xtc_midi_plan_set_fade_frames` before
publication to enable the same behavior in an offline/custom host.
5 ms at the current rate (integer frames, floor). Configuration is
control-thread only while stopped, before project preparation. The low-level
backend and source-plan APIs default to zero transition frames for source
compatibility. Call `xtc_audio_plan_set_fade_frames` and
`xtc_midi_plan_set_fade_frames` before publication to enable the same behavior
in an offline/custom host.
Each audio region and scheduled note gets an independent linear boundary
envelope. Its first and last audible samples are zero. At distance `d` from the
@ -60,33 +66,35 @@ per-clip editing metadata.
`xtc_gain_ramp` in `xtc_dsp.h` is a single-owner C primitive for smooth gain and
parameter changes. Initialize with reset, set a finite target and length, then
process preallocated mono/stereo buffers. It reaches the exact target on the Nth
sample, with the first sample one step toward the target. Retargeting starts from
the current gain; zero frames changes it immediately. Stereo shares one gain and
advances once per frame. Invalid calls leave buffers/state intact. It allocates
nothing. Mixer automation already interpolates gain/pan per sample; the ramp
primitive does not add a second smoothing delay to that timeline. Static mixer
edits stop playback and use the master ramp on restart. Live native parameter
updates and live graph replacements have no automatic crossfade.
sample, with the first sample one step toward the target. Retargeting starts
from the current gain; zero frames changes it immediately. Stereo shares one
gain and advances once per frame. Invalid calls leave buffers/state intact. It
allocates nothing. Mixer automation already interpolates gain/pan per sample;
the ramp primitive does not add a second smoothing delay to that timeline.
Static mixer edits stop playback and use the master ramp on restart. Live native
parameter updates and live graph replacements have no automatic crossfade.
## Graceful and immediate stopping
---
## Graceful and Immediate Stopping
GUI Play fades the master from zero to unity. Ordinary GUI Pause/Stop request a
fade to zero using `xtc_backend_request_stop`; repeated requests do not restart
the fade. The callback publishes completion with a release atomic, then outputs
silence and holds the sample position. `xtc_backend_collect` on the control thread
acquires completion and calls JACK deactivate, which establishes quiescence.
Until then, status reports `running` and `stopping`, and the GUI displays that it
is fading. The 100 ms GUI timer completes deactivation; it does not control fade
timing. If deactivation fails, context/ownership remains intact, silence
continues, the error is reported and later collection retries.
silence and holds the sample position. `xtc_backend_collect` on the control
thread acquires completion and calls JACK deactivate, which establishes
quiescence. Until then, status reports `running` and `stopping`, and the GUI
displays that it is fading. The 100 ms GUI timer completes deactivation; it does
not control fade timing. If deactivation fails, context/ownership remains
intact, silence continues, the error is reported and later collection retries.
The ramp length is sample-counted, but the last callback processes a whole block
and advances the sample position by that block, including its zero-valued tail.
A stop request starts at the next observed block boundary. Therefore Pause/Stop
can consume up to approximately `fade_frames + block_frames` after the request,
plus server scheduling; this is not an immediate sample-exact stop command.
Play resumes at the retained block-end sample position with a fresh start ramp.
No callback waits for control or performs deactivation/allocation/I/O.
plus server scheduling; this is not an immediate sample-exact stop command. Play
resumes at the retained block-end sample position with a fresh start ramp. No
callback waits for control or performs deactivation/allocation/I/O.
`xtc_backend_stop`/close remain synchronous immediate operations. Recording Stop
uses immediate deactivation before draining/finalizing the input take; recording
@ -97,31 +105,37 @@ material preservation take priority. If callbacks cease, use immediate stop or
close rather than waiting for fade acknowledgement. Live note-off/panic remains
immediate with no release tail; scheduled notes use the boundary envelope.
## Numerical and channel policy
---
Core graph, native output, audio and MIDI rendering canonicalize nonfinite values
and float subnormals to zero at output boundaries; graph node buffers also use
this policy. Native callbacks remain trusted and may have their own internal
denormal behavior. No process-wide FTZ/DAZ flags are modified. Normal values as
small as `FLT_MIN` are retained. The raw constant-gain helper is unchanged;
`xtc_gain_ramp_process` applies the canonical output policy.
## Numerical and Channel Policy
Finite values above unity are preserved for mixing headroom. There is no limiter,
soft clipping or dither. The physical device/downstream system may clip; lower
track gains if meters exceed unity. Ports remain mono with paired stereo passes;
external mono input is duplicated. Audio assets are downmixed on import, so
stereo assets do not retain stereo image. Output buffers must not overlap each
other; graph sources may alias outputs because both source passes finish first.
Supported render sizes are 0–65536 frames within the prepared graph maximum.
Core graph, native output, audio and MIDI rendering canonicalize nonfinite
values and float subnormals to zero at output boundaries; graph node buffers
also use this policy. Native callbacks remain trusted and may have their own
internal denormal behavior. No process-wide FTZ/DAZ flags are modified. Normal
values as small as `FLT_MIN` are retained. The raw constant-gain helper is
unchanged; `xtc_gain_ramp_process` applies the canonical output policy.
## Latency and synchronization limits
Finite values above unity are preserved for mixing headroom. There is no
limiter, soft clipping or dither. The physical device/downstream system may
clip; lower track gains if meters exceed unity. Ports remain mono with paired
stereo passes; external mono input is duplicated. Audio assets are downmixed on
import, so stereo assets do not retain stereo image. Output buffers must not
overlap each other; graph sources may alias outputs because both source passes
finish first. Supported render sizes are 0–65536 frames within the prepared
graph maximum.
---
## Latency and Synchronization Limits
Backend status reports sample rate and current buffer frames, not measured
end-to-end device latency. `buffer_frames/sample_rate` is one process quantum,
not capture-to-speaker latency. JACK owns hardware/graph scheduling and external
port latencies; xtc currently does not query/display JACK latency ranges or
publish an explicit per-node latency model. Fades shape amplitude without a
lookahead/delay buffer, so they add no timeline delay; they do attenuate attacks.
lookahead/delay buffer, so they add no timeline delay; they do attenuate
attacks.
Built-in gain/meter/output, preloaded sources and the sine instrument have no
algorithmic delay to compensate between tracks. The graph has no delay inserts,
@ -131,7 +145,9 @@ tail contract and compensation are required before claiming synchronized delayed
inserts or external hosting. JACK transport sync, external MIDI clock, tempo
maps and physical recording-offset calibration remain unsupported.
## Verification and listening
---
## Verification and Listening
```
make all test gui test-reliability test-workflow test-gui test-gui-jack
@ -140,21 +156,23 @@ make test-sanitize analyze
make quality-preview
```
`test_quality` is part of `make test`. It covers a sample-rate/tempo matrix up to
768 kHz and the maximum project tick, conversion errors, old/new tempo snapshots
for both audio and MIDI, exact offset/on/off order, sample boundary behavior,
raw versus faded endpoints, media ends shorter than rounded clip ends, split/
whole equivalence, seeks, ramps, stereo parity, overload, NaN/infinity/subnormals
and block sizes 1, 2, 3, 17, 257, 4097, 65535 and 65536. Backend fault tests check
exact master ramps, repeated stop requests, held position/silence after completion,
deactivation failure/retry, immediate escape and recording guards. The workflow
render comparison now enables the GUI source fade policy before/after reopening.
`test_quality` is part of `make test`. It covers a sample-rate/tempo matrix up
to 768 kHz and the maximum project tick, conversion errors, old/new tempo
snapshots for both audio and MIDI, exact offset/on/off order, sample boundary
behavior, raw versus faded endpoints, media ends shorter than rounded clip ends,
split/ whole equivalence, seeks, ramps, stereo parity, overload,
NaN/infinity/subnormals and block sizes 1, 2, 3, 17, 257, 4097, 65535 and 65536.
Backend fault tests check exact master ramps, repeated stop requests, held
position/silence after completion, deactivation failure/retry, immediate escape
and recording guards. The workflow render comparison now enables the GUI source
fade policy before/after reopening.
Automated checks pass warning-enabled core/GUI and no-JACK builds,
ASan/UBSan/leak checks, static analysis, pthread TSan tests, mock and live GUI,
software JACK audio/MIDI tests and staged installation. GUI leak checks retain
the existing fontconfig-only suppression; core leak checks are unsuppressed.
Physical-device listening and human listening acceptance are **not yet verified**.
Physical-device listening and human listening acceptance are **not yet
verified**.
`make quality-preview` writes ignored build artifacts `quality-raw.wav` and
`quality-smooth.wav` in BUILD_DIR using `examples/quality_preview.c`. Compare at
@ -162,7 +180,7 @@ moderate playback level: first listen to the three-note phrase endings, then the
constant clip at two seconds, then a mid-period tone stop near three seconds.
Check that fades reduce edge clicks, do not add noise and do not unexpectedly
change pitch/timing. Also listen to the saved MIDI/audio workflow project with
repeated Play/Pause/Stop, Home/seek and representative gain/pan automation on the
actual target device. Record device, rate, block size and observations before
marking human listening acceptance complete. Generated previews are reproducible
test material, not a claim that anyone has listened to them.
repeated Play/Pause/Stop, Home/seek and representative gain/pan automation on
the actual target device. Record device, rate, block size and observations
before marking human listening acceptance complete. Generated previews are
reproducible test material, not a claim that anyone has listened to them.

View file

@ -1,9 +1,11 @@
# Build baseline
# Build Baseline
The existing layout is retained: public C headers in `include/xtc`,
C implementation modules under `src`, optional C++ GUI under `src/gui`, and
tests under `tests`. `src/main.c` is a minimal headless startup/shutdown check;
it does not initialize audio devices or open a GUI.
The existing layout is retained: public C headers in `include/xtc`, C
implementation modules under `src`, optional C++ GUI under `src/gui`, and tests
under `tests`. `src/main.c` is a minimal headless startup/shutdown check; it
does not initialize audio devices or open a GUI.
---
## Commands
@ -15,14 +17,17 @@ make gui
```
`all` builds the static C library and startup check. `test` runs the existing
core/offline tests, concurrent plan-handoff stress, JACK fault injection
(when JACK headers are available), and the startup check. `debug` builds and tests separately in
`build/debug`. `gui` builds the FLTK shell with backend controls and status.
The shell starts with a silent graph and never connects physical ports. Generated files remain under `build` and header changes trigger rebuilds.
core/offline tests, concurrent plan-handoff stress, JACK fault injection (when
JACK headers are available), and the startup check. `debug` builds and tests
separately in `build/debug`. `gui` builds the FLTK shell with backend controls
and status. The shell starts with a silent graph and never connects physical
ports. Generated files remain under `build` and header changes trigger rebuilds.
Compiler, flags, `BUILD_DIR`, `PKG_CONFIG`, `FLTK_CONFIG`, and installation
`PREFIX` can be overridden through Make variables. C uses C99; GUI uses C++17.
## Inspected Linux toolchain (2026-10-26)
---
## Inspected Linux Toolchain (2026-10-26)
| Component | Available version | Role |
| --- | --- | --- |
@ -35,24 +40,29 @@ Compiler, flags, `BUILD_DIR`, `PKG_CONFIG`, `FLTK_CONFIG`, and installation
| Clang | 23.1.3 | Alternative compiler / diagnostics |
Valgrind is also available. The core tests use a small in-tree C test harness
and require no external test framework. FLTK has no `fltk.pc` on this host;
the Makefile tries pkg-config first, then `fltk-config`.
and require no external test framework. FLTK has no `fltk.pc` on this host; the
Makefile tries pkg-config first, then `fltk-config`.
## Dependencies and licensing
---
## Dependencies and Licensing
The offline core needs the C runtime and math library. Optional JACK support is
selected with pkg-config; missing JACK does not prevent core/tests
from building. Realtime publication uses GCC/Clang lock-free atomic builtins in
C99. Tests also use POSIX threads. Backend consumers must link JACK explicitly;
backend objects are pulled from the static library only when used. The repository is MIT licensed. The optional GUI uses maintained
FLTK 1.4 (LGPL with FLTK's static-linking exception); its platform link flags
come from the installed package. Distribution still requires review of all
linked dependencies and notices. The installed PipeWire JACK library is MIT;
traditional JACK client libraries use LGPL licensing. See decision 0004 for
the dependency choice and caveats. ALSA remains a future MIDI candidate. No packages or system settings
selected with pkg-config; missing JACK does not prevent core/tests from
building. Realtime publication uses GCC/Clang lock-free atomic builtins in C99.
Tests also use POSIX threads. Backend consumers must link JACK explicitly;
backend objects are pulled from the static library only when used. The
repository is MIT licensed. The optional GUI uses maintained FLTK 1.4 (LGPL with
FLTK's static-linking exception); its platform link flags come from the
installed package. Distribution still requires review of all linked dependencies
and notices. The installed PipeWire JACK library is MIT; traditional JACK client
libraries use LGPL licensing. See decision 0004 for the dependency choice and
caveats. ALSA remains a future MIDI candidate. No packages or system settings
are changed by these build commands.
## Backend verification
---
## Backend Verification
```sh
make all test gui debug
@ -72,11 +82,13 @@ Normal, sanitizer and no-JACK tests pass. ThreadSanitizer reports no race in
2,000 concurrent submissions. The 30-second live loopback test passed at 48 kHz
with zero xruns and ten restart cycles; see tests/integration/README.md. Live
server termination/format changes were not performed on the shared server;
fault-injection tests cover those notification paths. No packages installed
or system/device configuration changed. Sanitizers are not enabled in the live
fault-injection tests cover those notification paths. No packages installed or
system/device configuration changed. Sanitizers are not enabled in the live
callback qualification run.
## GUI shell verification
---
## GUI Shell Verification
```sh
make clean all test gui debug test-gui test-gui-jack
@ -92,7 +104,9 @@ exit-time font allocations; see tests/integration/README.md for the distinction
and command. No compiler warnings remain with GCC or Clang. Core/backend tests
pass with unsuppressed ASan/UBSan. No packages or system configuration changed.
## Graph editor verification
---
## Graph Editor Verification
```sh
make clean all test gui debug test-gui test-gui-jack
@ -106,10 +120,12 @@ ASan/UBSan; ThreadSanitizer passes the concurrent handoff test with revision-to-
gain checks. GUI ASan/UBSan tests pass with the existing fontconfig-only leak
suppression. Live GUI tests build a connected graph, adopt accepted edits during
playback, reject cycles, reconnect and close active clients. Mouse/visual smoke
checks cover node creation, wiring, cycle rejection, dragging and deletion.
No physical routes, new dependencies or system changes are introduced.
checks cover node creation, wiring, cycle rejection, dragging and deletion. No
physical routes, new dependencies or system changes are introduced.
## Project verification
---
## Project Verification
```sh
make clean all test gui debug test-gui test-gui-jack
@ -118,10 +134,10 @@ make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui
The suite adds test_project: track/clip limits, stable IDs, trims, integer
transport conversion, exact file roundtrip with graph rendering, exhaustive
fixture truncations, byte mutations, failed saves and temporary cleanup.
GUI tests cover region gestures, zoom, save/load and backend rollback.
Live JACK tests verify playhead advance, pause and resume without creating routes.
The updated core and GUI tests pass Clang ASan/UBSan (the GUI uses the existing
fixture truncations, byte mutations, failed saves and temporary cleanup. GUI
tests cover region gestures, zoom, save/load and backend rollback. Live JACK
tests verify playhead advance, pause and resume without creating routes. The
updated core and GUI tests pass Clang ASan/UBSan (the GUI uses the existing
fontconfig-only LSan suppression). An Xvfb/xdotool visual check created a track
and region, moved it one bar and zoomed the canvas. No dependencies were added.
@ -129,8 +145,9 @@ Audio assets and recording are supported. Silent arrangement regions, fixed
tempo/4/4, no undo, no persisted GUI layout, and unverified physical-device
playback/endurance remain explicit limits.
---
## Audio verification
## Audio Verification
```sh
make all test gui debug test-gui test-gui-jack test-jack-audio
@ -150,12 +167,15 @@ migration and 1024 maximum-length asset paths in a valid v2 file.
The live test-jack-audio target connects only its software clients and checks
record -> WAV -> saved/reopened project -> graph playback. GUI/JACK tests also
record, finalize, save and reopen a take without hardware routing. Clang
ASan/UBSan passes the core and GUI tests (GUI retains the existing fontconfig-only
LSan suppression). ThreadSanitizer passes the concurrent recorder and existing
realtime handoff tests. Hardware capture/playback, disk endurance and real server
loss during recording remain unverified. See AUDIO_FILES.md.
ASan/UBSan passes the core and GUI tests (GUI retains the existing
fontconfig-only LSan suppression). ThreadSanitizer passes the concurrent
recorder and existing realtime handoff tests. Hardware capture/playback, disk
endurance and real server loss during recording remain unverified. See
AUDIO_FILES.md.
## MIDI verification
---
## MIDI Verification
```sh
make all test gui debug test-gui test-gui-jack test-jack-audio test-jack-midi
@ -166,17 +186,20 @@ make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui
voice capacities, stable notes and v3 malformed-file/roundtrip/v2 migration
coverage. Backend fault injection checks event offsets, dropped/malformed input,
disconnection, xruns, missing output buffers and stop/restart cleanup. GUI tests
draw/move/resize notes, save/reopen and render them. The live MIDI target connects
only software clients and checks on/off, panic, disconnect, and saved-note
playback/seek. It needs an existing JACK-compatible server. No hardware routes
or system changes are made. MIDI recording/SMF and physical devices are deferred.
draw/move/resize notes, save/reopen and render them. The live MIDI target
connects only software clients and checks on/off, panic, disconnect, and
saved-note playback/seek. It needs an existing JACK-compatible server. No
hardware routes or system changes are made. MIDI recording/SMF and physical
devices are deferred.
A fresh `build/verify` build passed all targets above with no compiler warnings.
The no-JACK core/GUI suite and Clang ASan/UBSan core/GUI suite also passed; GUI
leak checking uses only the existing fontconfig suppression documented in the
integration README. Physical MIDI and audio devices remain unverified.
## Mixer verification
---
## Mixer Verification
```sh
make all test gui debug test-gui test-gui-jack test-jack-audio test-jack-midi
@ -184,20 +207,23 @@ make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui
```
`test_mixer` covers stereo gain/pan, mute/solo, sample automation, block
independence, WAV and graph routing, v4 persistence failures and bounded history.
GUI tests add automation mouse edits, mixer keys, history rollback/branching and
save/reopen. The existing live MIDI test now uses two software capture ports and
checks hard-right saved-note playback and output meter snapshots; mono audio
record/play acceptance remains covered. A native Xvfb mouse smoke check confirms
sliders and automation drawing. See MIXER.md for capacities and deferred features.
independence, WAV and graph routing, v4 persistence failures and bounded
history. GUI tests add automation mouse edits, mixer keys, history
rollback/branching and save/reopen. The existing live MIDI test now uses two
software capture ports and checks hard-right saved-note playback and output
meter snapshots; mono audio record/play acceptance remains covered. A native
Xvfb mouse smoke check confirms sliders and automation drawing. See MIXER.md for
capacities and deferred features.
The fresh verification build, no-JACK core/GUI suite and Clang
ASan/UBSan core/GUI suite passed without compiler warnings. GUI leak checking
retains only the existing fontconfig suppression. ThreadSanitizer also passed
the recorder concurrency and realtime publication tests. Physical devices and
long-running stereo performance remain unverified.
The fresh verification build, no-JACK core/GUI suite and Clang ASan/UBSan
core/GUI suite passed without compiler warnings. GUI leak checking retains only
the existing fontconfig suppression. ThreadSanitizer also passed the recorder
concurrency and realtime publication tests. Physical devices and long-running
stereo performance remain unverified.
## Release preparation
---
## Release Preparation
```sh
make all test gui debug test-gui test-gui-jack test-jack test-jack-audio test-jack-midi
@ -205,54 +231,63 @@ make test-install test-recovery test-performance
make test-release
```
Core test now includes native ABI/lifecycle/retirement and the native-gain example.
Optional release tests use Python 3.12+ standard library, Git for indexed source
exports, GNU/LLVM deterministic ar and temporary directories. No live prefix is
installed. test-install checks core/GUI files, a prefix containing spaces,
headless CLI, pkg-config C/C++ consumers, exact/idempotent uninstall and preservation
of unrelated files. test-release compares two clean exported optimized builds and
repacked archives byte-for-byte. test-recovery kills private child writers and
forces a child-local write limit, checking complete finalized destinations and
explicit orphan temporaries. Performance timings are reported, not used as a
universal realtime guarantee. See RELEASING.md, RECOVERY.md and PERFORMANCE.md.
Core test now includes native ABI/lifecycle/retirement and the native-gain
example. Optional release tests use Python 3.12+ standard library, Git for
indexed source exports, GNU/LLVM deterministic ar and temporary directories. No
live prefix is installed. test-install checks core/GUI files, a prefix
containing spaces, headless CLI, pkg-config C/C++ consumers, exact/idempotent
uninstall and preservation of unrelated files. test-release compares two clean
exported optimized builds and repacked archives byte-for-byte. test-recovery
kills private child writers and forces a child-local write limit, checking
complete finalized destinations and explicit orphan temporaries. Performance
timings are reported, not used as a universal realtime guarantee. See
RELEASING.md, RECOVERY.md and PERFORMANCE.md.
Verification passed a fresh warning-enabled core/debug/GUI build, all
software JACK targets, staged install/uninstall, recovery and performance tests.
No-JACK core/GUI/install checks and Clang ASan/UBSan core/GUI checks also passed
(the GUI retains only its existing fontconfig leak suppression). TSan recorder
and realtime-handoff checks passed. Two independent optimized source exports
and their repacked source archives were byte-identical on this toolchain.
Verification passed a fresh warning-enabled core/debug/GUI build, all software
JACK targets, staged install/uninstall, recovery and performance tests. No-JACK
core/GUI/install checks and Clang ASan/UBSan core/GUI checks also passed (the
GUI retains only its existing fontconfig leak suppression). TSan recorder and
realtime-handoff checks passed. Two independent optimized source exports and
their repacked source archives were byte-identical on this toolchain.
## Reliability audit
---
## Reliability Audit
Run `make test-reliability` for the core suites, allocation-failure sweep,
callback allocation/free guard and native reclamation checks. Run
`make test-sanitize` for Clang ASan/UBSan/leak checks and `make analyze` for
Clang static analysis of all core sources. These need installed development
tools; the allocation guard uses a GNU-compatible linker. See
[RELIABILITY.md](RELIABILITY.md) for ownership, audit findings,
full verification commands and remaining limits.
[RELIABILITY.md](RELIABILITY.md) for ownership, audit findings, full
verification commands and remaining limits.
## Workflow acceptance
---
## Workflow Acceptance
`make test-workflow` requires FLTK/JACK development files, Xvfb and an existing
JACK-compatible server. It does not start/configure the server or connect
physical devices. See [WORKFLOW.md](WORKFLOW.md) for the full GUI composition
acceptance sequence and transport semantics.
## Audio quality
---
## Audio Quality
`make test-quality` runs exact timing, source-boundary, gain-ramp, event-order
and numeric/layout tests (also included in `make test`). `make quality-preview`
builds the C example and writes raw/smoothed listening WAVs into BUILD_DIR without
playing them or configuring devices. See [AUDIO_QUALITY.md](AUDIO_QUALITY.md).
builds the C example and writes raw/smoothed listening WAVs into BUILD_DIR
without playing them or configuring devices. See
[AUDIO_QUALITY.md](AUDIO_QUALITY.md).
Parameter-processor and plugin checks: `make test-processor test-plugins test-gui-plugins`.
The latter two need the system LADSPA development header; real-plugin acceptance
is separate (`make test-plugins-real`, paths listed in PLUGIN_HOSTING.md). With no
Parameter-processor and plugin checks:
`make test-processor test-plugins test-gui-plugins`. The latter two need the
system LADSPA development header; real-plugin acceptance is separate
(`make test-plugins-real`, paths listed in PLUGIN_HOSTING.md). With no
`ladspa.h`, the ordinary core/GUI still builds and plugin operations return
UNSUPPORTED; existing plugin metadata can still be loaded/saved. To exercise that
configuration explicitly, use a fresh BUILD_DIR with
UNSUPPORTED; existing plugin metadata can still be loaded/saved. To exercise
that configuration explicitly, use a fresh BUILD_DIR with
`CPPFLAGS=-DXTC_DISABLE_LADSPA JACK_AVAILABLE=`. No dependency is downloaded.
Link static consumers with `-lm -ldl` plus the backend dependencies when used.
See LICENSES.md for header/plugin licensing and decision 0009 for rationale.
@ -264,23 +299,27 @@ ASan/UBSan. Run `make benchmark benchmark-project` separately without contention
or sanitizers for timing reports. Optional `make test-stress-plugins` requires
installed LADSPA headers; `make test-stress` supports minimal builds too.
## Installation layouts
---
## Installation Layouts
`make test-install test-install-layout` verifies default and custom layouts in
throwaway staging directories, C/C++ installed consumers, examples and exact
removal. Override `PREFIX`, `exec_prefix`, `bindir`, `libdir`, `includedir`,
`datarootdir`, `datadir`, `docdir`, `pkgconfigdir`, `licensedir`, `examplesdir` or
`desktopdir` as needed. `DESTDIR` adds staging separately; pass the same layout
to uninstall. See RELEASING.md for commands and RELEASE_NOTES.md for upgrades.
`datarootdir`, `datadir`, `docdir`, `pkgconfigdir`, `licensedir`, `examplesdir`
or `desktopdir` as needed. `DESTDIR` adds staging separately; pass the same
layout to uninstall. See RELEASING.md for commands and RELEASE_NOTES.md for
upgrades.
Modern plugin adapters build in C99 using the pinned header-only CLAP
and VST3 C APIs under third_party; there is no network build or SDK runtime.
VST2 compatibility uses original ABI declarations. `make test-modern-plugins`
runs original shared-library fixtures without a server; `make test-gui-modern-plugins`
adds GUI/browser/undo/reopen tests under Xvfb. The fixtures also run in `make test`
and therefore in sanitizer/release builds. Notices are installed with LICENSE.
Modern plugin adapters build in C99 using the pinned header-only CLAP and VST3 C
APIs under third_party; there is no network build or SDK runtime. VST2
compatibility uses original ABI declarations. `make test-modern-plugins` runs
original shared-library fixtures without a server;
`make test-gui-modern-plugins` adds GUI/browser/undo/reopen tests under Xvfb.
The fixtures also run in `make test` and therefore in sanitizer/release builds.
Notices are installed with LICENSE.
The GUI uses a header-only FLTK theme and existing drawing primitives; no new
GUI dependency or generated asset is required. `make test-gui-instruments` also
exercises sidebar track creation, rollback, undo/reopen and minimum/large layouts.
See WORKSPACE.md for the default size and manual visual verification.
exercises sidebar track creation, rollback, undo/reopen and minimum/large
layouts. See WORKSPACE.md for the default size and manual visual verification.

View file

@ -1,21 +1,23 @@
# Internal instruments and effects
# Internal Instruments and Effects
No plugin downloads, search paths or external libraries are needed. The original
built-in sine remains available for existing projects.
Choose **Internal: Soft Keys** or **Internal: Sub Bass** in a MIDI track's
instrument dropdown. Soft Keys blends a sine with a third harmonic; Sub Bass
uses a pure sine. Both have 32 voices, velocity sensitivity,
short attack/release envelopes, sustain pedal, note-off and panic handling.
High harmonics are omitted near Nyquist. The default sounds remain compatible with fixed presets from v13. See the
instrument controls below. A sample library is not provided by these oscillators.
uses a pure sine. Both have 32 voices, velocity sensitivity, short
attack/release envelopes, sustain pedal, note-off and panic handling. High
harmonics are omitted near Nyquist. The default sounds remain compatible with
fixed presets from v13. See the instrument controls below. A sample library is
not provided by these oscillators.
In **Modular Graph**, select a wire and choose **Edit → Insert internal delay on
wire** or **Insert internal reverb on wire**. This preserves the route and inserts
the effect into the signal path. **File → Add internal delay/reverb** creates an
unconnected device for manual wiring. Select its node and use **Edit → Selected
plugin control** to edit it, or **Selected plugin automation** to automate it.
Changes use the existing stopped-engine preparation and undo workflow.
wire** or **Insert internal reverb on wire**. This preserves the route and
inserts the effect into the signal path. **File → Add internal delay/reverb**
creates an unconnected device for manual wiring. Select its node and use **Edit
→ Selected plugin control** to edit it, or **Selected plugin automation** to
automate it. Changes use the existing stopped-engine preparation and undo
workflow.
- **Delay:** time (1–2000 ms), feedback (0–0.95), dry/wet mix (0–1).
- **Reverb:** four parallel damped feedback combs, with decay (0–0.95), damping
@ -28,37 +30,43 @@ Internal effect history resets on transport discontinuities. Configurable export
tails and optional playback stopping after tails are described below.
Project format 13 stores internal devices as format 4 with reserved module path
`/xtc/internal`, stable numeric identity and index (0 keys, 1 bass, 2 delay,
3 reverb). This is a virtual identifier, never an external binary. Effect controls
and automation are saved using the existing plugin records; instruments are fixed
presets. Readers still accept formats 1–12. Older xtc builds reject format 13.
All buffers are allocated during preparation; processing performs no allocation,
file access, library loading, locking or GUI calls.
`/xtc/internal`, stable numeric identity and index (0 keys, 1 bass, 2 delay, 3
reverb). This is a virtual identifier, never an external binary. Effect controls
and automation are saved using the existing plugin records; instruments are
fixed presets. Readers still accept formats 1–12. Older xtc builds reject format
13. All buffers are allocated during preparation; processing performs no
allocation, file access, library loading, locking or GUI calls.
Validation: `make BUILD_DIR=build/builtins all gui test test-gui-instruments
test-gui-modern-plugins test-gui-plugins analyze` (join onto one line).
`test-builtins` checks impulse tails, dry mix, feedback bounds, block partition
consistency, note release/panic and instrument/effect project round trips.
Validation:
`make BUILD_DIR=build/builtins all gui test test-gui-instruments test-gui-modern-plugins test-gui-plugins analyze`
(join onto one line). `test-builtins` checks impulse tails, dry mix, feedback
bounds, block partition consistency, note release/panic and instrument/effect
project round trips.
## Acceptance checks
---
## Acceptance Checks
`make BUILD_DIR=build/builtin-acceptance all gui test test-reliability test-gui builtin-preview`
checks all four device identities and GUI paths without external plugin binaries.
The reliability runner fails each internal preparation allocation in turn and
forbids allocation/free, direct file I/O and blocking locks during processing.
Core tests compare persisted rendering for both instruments and both automated
effects, corrupt an internal module identity transactionally, and cover voice
exhaustion, sustain, repeated notes, partitioning and transport reset/seek/panic.
The existing portability suite verifies older project migrations.
checks all four device identities and GUI paths without external plugin
binaries. The reliability runner fails each internal preparation allocation in
turn and forbids allocation/free, direct file I/O and blocking locks during
processing. Core tests compare persisted rendering for both instruments and both
automated effects, corrupt an internal module identity transactionally, and
cover voice exhaustion, sustain, repeated notes, partitioning and transport
reset/seek/panic. The existing portability suite verifies older project
migrations.
For repeatable manual listening, run `make builtin-preview` and audition
`build/builtin-{0,1,2,3}.wav` at a conservative level: 0 is Soft Keys, 1 Sub Bass,
2 Delay and 3 Reverb. Each six-second file includes held tones or two impulses
followed by silence for tails. Check onset/release, unwanted discontinuities,
finite decay and the documented mono character. Generation is verified;
human listening and physical-device qualification remain pending.
`build/builtin-{0,1,2,3}.wav` at a conservative level: 0 is Soft Keys, 1 Sub
Bass, 2 Delay and 3 Reverb. Each six-second file includes held tones or two
impulses followed by silence for tails. Check onset/release, unwanted
discontinuities, finite decay and the documented mono character. Generation is
verified; human listening and physical-device qualification remain pending.
## Instrument controls and presets
---
## Instrument Controls and Presets
**Edit → Selected instrument controls** edits an assigned internal instrument.
The five ports are harmonic amount (0..1), attack (0.1..2000 ms), release
@ -77,8 +85,8 @@ with no parameters upgrade to defaults when opened in the editor.
are versioned single-instrument `.xtc` containers with no clips. Public C APIs
`xtc_plugin_preset_save/load` provide atomic saving and transactional metadata
loading; they never execute plugin code. Loading into a track is an undoable,
prepared edit. Parameter automation is included. Preset paths are ordinary files;
saving uses the existing project replacement semantics.
prepared edit. Parameter automation is included. Preset paths are ordinary
files; saving uses the existing project replacement semantics.
Live MIDI pitch bend uses standard 14-bit messages (8192 center) and is applied
at the event's sample offset per channel. Panic clears the affected channel's
@ -91,7 +99,9 @@ old fixed internal assignments use their original defaults. Older builds reject
v14. Tests cover legacy-default sound equality, v13 migration, parameter/preset
round trips, pitch-bend offsets, automated block partitions, undo and GUI edits.
## Sample instrument
---
## Sample Instrument
**File → Load sampler WAV** assigns a Sample Player to the selected track or
relinks its existing sample while retaining its controls. Use the instrument
@ -99,8 +109,8 @@ controls editor for root MIDI key (0..127), whole-sample loop (0/1), attack,
release and gain (0..4). One root mapping covers every MIDI key using linear
rate conversion, including source/output sample-rate differences. Loop and root
are integer controls and require hold automation. Live bend spans ±2 semitones.
This initial sampler has no multisample zones, loop-point editor or time stretch.
Extreme transposition can alias; use appropriate source material.
This initial sampler has no multisample zones, loop-point editor or time
stretch. Extreme transposition can alias; use appropriate source material.
The existing WAV reader supports PCM16/24/32 and float32, mono/stereo downmixed
to mono. Each sample is at most 16M mono frames; the combined instrument plan
@ -122,15 +132,17 @@ Tests cover source-rate conversion, one-shot endpoint, looping, release,
partitioning, preset references, missing assets, GUI relinking/undo/reload and
preparation-allocation/callback guards.
## Stereo effects
---
## Stereo Effects
**File → Add stereo delay/reverb** and **Edit → Insert stereo delay/reverb on
wire** add new devices (indices 5/6). Original Delay/Reverb (indices 2/3) retain
their original algorithms, values and saved identities. Existing control and
automation editors, undo and persistence support both generations.
Stereo Delay adds integer **Sync**: 0 milliseconds, 1 quarter, 2 eighth,
3 dotted eighth, 4 eighth triplet. Nonzero sync follows project tempo; changing
Stereo Delay adds integer **Sync**: 0 milliseconds, 1 quarter, 2 eighth, 3
dotted eighth, 4 eighth triplet. Nonzero sync follows project tempo; changing
tempo uses ordinary stopped project preparation. Time changes crossfade two
fractional read heads for 5 ms. A crossfade finishes before adopting the latest
automated target, bounding transition work and avoiding read-head jumps.
@ -139,29 +151,33 @@ Delay storage covers four seconds, sufficient for all divisions at 20..300 BPM.
Stereo Reverb adds **Room** (0..1, scales comb spacing) and **Diffusion** (0..1,
cross-couples damped feedback). The right comb timings differ from the left.
Decay/damping retain 0..0.95 bounds. This is a simple algorithmic reverb, without
physical room simulation or a measured RT60. Room automation can alter coloration.
Decay/damping retain 0..0.95 bounds. This is a simple algorithmic reverb,
without physical room simulation or a measured RT60. Room automation can alter
coloration.
The graph evaluates both channels' inputs before calling a coupled internal
processor. Each stereo device has one prepared state, with no callback allocation.
Mono processing excites both sides and folds the wet output to mono. External
plugins and the original effects retain independent mono instances.
processor. Each stereo device has one prepared state, with no callback
allocation. Mono processing excites both sides and folds the wet output to mono.
External plugins and the original effects retain independent mono instances.
Tests compare whole/split blocks, tempo-synced impulse placement, left-to-right
energy, bounded extreme feedback/diffusion and graph/direct stereo output
(including the engine's subnormal-to-zero policy). GUI tests save/reopen both
identities; callback guards and ASan/UBSan cover processing. `make builtin-preview`
also writes files 5/6 for repeatable listening; human listening remains pending.
identities; callback guards and ASan/UBSan cover processing.
`make builtin-preview` also writes files 5/6 for repeatable listening; human
listening remains pending.
## History and tails
---
## History and Tails
Internal effects reset on a transport discontinuity (seek/loop wrap), stopped
backend, or newly prepared plan. Pause uses backend Stop and therefore also
resets history. Continuous playback retains tails. Reset invalidates delay
history with a valid-frame counter and clears only fixed-size filter/voice state;
it neither allocates nor clears a large delay buffer in the callback. External
plugins retain their existing history contract. MIDI discontinuities continue
to chase notes with fresh envelopes.
history with a valid-frame counter and clears only fixed-size filter/voice
state; it neither allocates nor clears a large delay buffer in the callback.
External plugins retain their existing history contract. MIDI discontinuities
continue to chase notes with fresh envelopes.
**File → Export WAV** renders the song plus the Preferences / transport
`tail_ms` duration (0..60000, default 3000). Output is stereo float32 WAV at the
@ -169,9 +185,9 @@ offline sample rate. The destination must be a new absolute path. Rendering
prepares an independent snapshot, then advances in 256-frame control-thread
steps with a Cancel button; the project and playback instance stay independent.
Only a complete synced file is published, without replacing an existing file.
Cancellation/errors remove the temporary. Missing media rejects export.
RIFF's 32-bit size limit is enforced before rendering. Rendering does not apply
loop preferences, normalize, automatically detect silence or stop infinite
Cancellation/errors remove the temporary. Missing media rejects export. RIFF's
32-bit size limit is enforced before rendering. Rendering does not apply loop
preferences, normalize, automatically detect silence or stop infinite
oscillator/feedback sources early: the configured endpoint always bounds work.
The public `xtc_export_create/step/progress/destroy` API supports incremental
@ -181,8 +197,9 @@ destruction. `xtc_project_render_length` returns the exact sample endpoint.
Set `stop_after_tail=1` in Preferences to stop ordinary playback after the song
and configured tail. Looping and recording disable this automatic stop. Playback
stop is serviced by the GUI refresh timer and normal stop fade, so it can exceed
the endpoint by a refresh interval plus fade; export has the exact sample length.
Uncheck the setting to retain indefinite playback. Stop always remains available.
the endpoint by a refresh interval plus fade; export has the exact sample
length. Uncheck the setting to retain indefinite playback. Stop always remains
available.
Tests compare stereo export against direct rendering in 31-frame blocks, verify
nonzero tails, exact length, cancellation, existing-file retention, tail bounds,

View file

@ -1,48 +1,124 @@
# Arrangement and composition workflows
# Arrangement and Composition Workflows
All mutations run on the control thread through the existing stop, history and
prepared-plan workflow. No new dependencies are required.
---
## Persistent Arrangement Markers
Edit/Markers/Set at cursor adds or renames a marker; Remove at cursor removes the exact tick key. Markers draw names and vertical guides in the arrangement and survive project save/load and undo. Up to 128 markers are supported. Names may overlap visually when markers are close; markers do not change playback by themselves.
Edit/Markers/Set at cursor adds or renames a marker; Remove at cursor removes
the exact tick key. Markers draw names and vertical guides in the arrangement
and survive project save/load and undo. Up to 128 markers are supported. Names
may overlap visually when markers are close; markers do not change playback by
themselves.
---
## Section Navigation and Looping
View/Previous marker and Next marker navigate without wrapping and stop playback before seeking. Loop current section uses the last marker at or before the cursor through its successor; the final section ends at the final clip endpoint. Empty final sections reject. Loop ranges remain existing UI preferences rather than marker metadata.
View/Previous marker and Next marker navigate without wrapping and stop playback
before seeking. Loop current section uses the last marker at or before the
cursor through its successor; the final section ends at the final clip endpoint.
Empty final sections reject. Loop ranges remain existing UI preferences rather
than marker metadata.
---
## Ripple Time Editing
Edit/Time inserts a gap or removes a tick range beginning at the cursor. Clips crossing either edit boundary reject the entire operation; split them first. Contained clips and their notes are removed; later clips, markers and absolute automation points shift together. Source notes within retained clips do not move. No interpolation guard points are generated, so automation ramps across the boundary may change slope. Loop preferences are unchanged.
Edit/Time inserts a gap or removes a tick range beginning at the cursor. Clips
crossing either edit boundary reject the entire operation; split them first.
Contained clips and their notes are removed; later clips, markers and absolute
automation points shift together. Source notes within retained clips do not
move. No interpolation guard points are generated, so automation ramps across
the boundary may change slope. Loop preferences are unchanged.
---
## Complete Track Duplication
Edit/Duplicate complete track copies the selected non-bus track adjacent to its source. Clips, notes, instrument settings, static mix, bus routing and mixer automation are independent model copies with fresh IDs; WAV references remain shared. The modular graph and live-input target are unchanged. Bus duplication is rejected. Capacity errors leave the original document unchanged.
Edit/Duplicate complete track copies the selected non-bus track adjacent to its
source. Clips, notes, instrument settings, static mix, bus routing and mixer
automation are independent model copies with fresh IDs; WAV references remain
shared. The modular graph and live-input target are unchanged. Bus duplication
is rejected. Capacity errors leave the original document unchanged.
---
## Nondestructive MIDI Clip Splitting
The existing Split clip at cursor command (S) now handles MIDI as well as audio. Left retains its ID; right gets fresh IDs, the same source content and an adjusted source offset. Hidden notes remain editable. Split is nondestructive, but instrument note envelopes may retrigger at the new boundary; original sine rendering retains source phase. Capacity failures preserve the project.
The existing Split clip at cursor command (S) now handles MIDI as well as audio.
Left retains its ID; right gets fresh IDs, the same source content and an
adjusted source offset. Hidden notes remain editable. Split is nondestructive,
but instrument note envelopes may retrigger at the new boundary; original sine
rendering retains source phase. Capacity failures preserve the project.
---
## MIDI Clip Consolidation
Edit/Consolidate MIDI clips merges two or more selected MIDI clips on one track. The new clip spans their full bounding range; gaps and overlapping notes remain. Notes outside visible trims are discarded and crossing notes are cropped to audible boundaries. IDs are fresh. Cropped notes start anew, so envelope/phase behavior can change. Audio or cross-track selections reject atomically.
Edit/Consolidate MIDI clips merges two or more selected MIDI clips on one track.
The new clip spans their full bounding range; gaps and overlapping notes remain.
Notes outside visible trims are discarded and crossing notes are cropped to
audible boundaries. IDs are fresh. Cropped notes start anew, so envelope/phase
behavior can change. Audio or cross-track selections reject atomically.
---
## Deterministic MIDI Humanization
Edit/MIDI tools/Humanize applies timing variation up to 960 ticks and velocity variation up to 127, using a 32-bit seed and stable note IDs. Repeating from the same original state reproduces results; applying repeatedly accumulates edits. All source notes are affected, including hidden trimmed notes. Timing outside source bounds rejects the entire edit; velocities clamp to 1..127. This is offline editing, not live randomness.
Edit/MIDI tools/Humanize applies timing variation up to 960 ticks and velocity
variation up to 127, using a 32-bit seed and stable note IDs. Repeating from the
same original state reproduces results; applying repeatedly accumulates edits.
All source notes are affected, including hidden trimmed notes. Timing outside
source bounds rejects the entire edit; velocities clamp to 1..127. This is
offline editing, not live randomness.
---
## Chord Construction
Edit/MIDI tools/Build chords accepts 1..8 unique nonzero semitone intervals, such as 4 7 for a major triad or 3 7 for minor. Originals remain; added notes copy timing, velocity and channel. Negative intervals support lower voicings. All source notes are processed, including hidden trimmed notes. Pitch overflow or capacity rejects the whole operation; existing unisons are not deduplicated.
Edit/MIDI tools/Build chords accepts 1..8 unique nonzero semitone intervals,
such as 4 7 for a major triad or 3 7 for minor. Originals remain; added notes
copy timing, velocity and channel. Negative intervals support lower voicings.
All source notes are processed, including hidden trimmed notes. Pitch overflow
or capacity rejects the whole operation; existing unisons are not deduplicated.
---
## Offline Arpeggio Generation
Edit/MIDI tools/Generate arpeggio replaces source notes with a written pattern. Equal-onset notes on the same channel form a chord lasting until its longest note end. The pattern cycles sorted pitches upward or downward, with the selected step and gate. Each step inherits its chosen note velocity/channel. All source notes, including hidden trimmed notes, participate. This is offline generation with undo, not a live arpeggiator or tempo-synced MIDI effect.
Edit/MIDI tools/Generate arpeggio replaces source notes with a written pattern.
Equal-onset notes on the same channel form a chord lasting until its longest
note end. The pattern cycles sorted pitches upward or downward, with the
selected step and gate. Each step inherits its chosen note velocity/channel. All
source notes, including hidden trimmed notes, participate. This is offline
generation with undo, not a live arpeggiator or tempo-synced MIDI effect.
---
## Audio Peak Normalization
Edit/Audio tools/Normalize source peak accepts a target from -60 to 0 dBFS. The C API accepts a linear target above 0 and at most 1. Analysis uses the audible native-rate source slice and the same mono downmix as playback, before fades or mixer controls. It replaces clip gain without writing WAV data. Silence, missing/invalid media, MIDI selections or required gain above 4 reject the entire batch. This is sample-peak normalization, not true-peak limiting or LUFS loudness normalization. Analysis runs synchronously on the control thread.
Edit/Audio tools/Normalize source peak accepts a target from -60 to 0 dBFS. The
C API accepts a linear target above 0 and at most 1. Analysis uses the audible
native-rate source slice and the same mono downmix as playback, before fades or
mixer controls. It replaces clip gain without writing WAV data. Silence,
missing/invalid media, MIDI selections or required gain above 4 reject the
entire batch. This is sample-peak normalization, not true-peak limiting or LUFS
loudness normalization. Analysis runs synchronously on the control thread.
---
## Portable Project Collection
File/Collect project media... requires a new absolute folder. The original document and files remain unchanged; open project.xtc inside the new folder to use the copy. Referenced audio and track/graph sampler WAVs are deduplicated and copied byte-for-byte, with relative references. Limits are 128 distinct assets, 256 MiB per file, 1 GiB total and a canonical folder path shorter than 480 bytes. Existing destinations, missing media, nonregular files and native graph nodes reject. External plugin binaries/state are not collected; plugins must remain installed. Invalid WAV data is copied rather than repaired. The folder is visible during synchronous collection; failures remove owned outputs, and the project file is saved last.
File/Collect project media... requires a new absolute folder. The original
document and files remain unchanged; open project.xtc inside the new folder to
use the copy. Referenced audio and track/graph sampler WAVs are deduplicated and
copied byte-for-byte, with relative references. Limits are 128 distinct assets,
256 MiB per file, 1 GiB total and a canonical folder path shorter than 480
bytes. Existing destinations, missing media, nonregular files and native graph
nodes reject. External plugin binaries/state are not collected; plugins must
remain installed. Invalid WAV data is copied rather than repaired. The folder is
visible during synchronous collection; failures remove owned outputs, and the
project file is saved last.

View file

@ -1,41 +1,45 @@
# Automation and expressive control
# Automation and Expressive Control
Track Gain/Pan lanes in the Mixer apply to audio and scheduled instrument voices.
They use absolute project ticks, linear or hold interpolation, hold-first/last outside
points and the constant project tempo snapshot. A clip trim/move does not move
track or effect automation: lanes belong to the project timeline. Instrument
volume is controlled by its track Gain lane; the current sine has no timbre
parameter. Track gain ramps and boundary fades are described in AUDIO_QUALITY.md.
Track Gain/Pan lanes in the Mixer apply to audio and scheduled instrument
voices. They use absolute project ticks, linear or hold interpolation,
hold-first/last outside points and the constant project tempo snapshot. A clip
trim/move does not move track or effect automation: lanes belong to the project
timeline. Instrument volume is controlled by its track Gain lane; the current
sine has no timbre parameter. Track gain ramps and boundary fades are described
in AUDIO_QUALITY.md.
Select a LADSPA node in Modular Graph and use Edit/Selected plugin automation.
Enter `port tick mode value`: mode 0 interpolates linearly, 1 holds until the next
point, and -1 removes the point at that port/tick (value ignored). Port names,
ranges and current tick are displayed. There are at most 32 points per plugin;
setting an existing port/tick replaces it. Integer/toggle controls require step
mode. Invalid values, ticks, ports or duplicate persisted points reject the edit
or file. Edits are undoable and rebuild on the control thread while stopped.
Enter `port tick mode value`: mode 0 interpolates linearly, 1 holds until the
next point, and -1 removes the point at that port/tick (value ignored). Port
names, ranges and current tick are displayed. There are at most 32 points per
plugin; setting an existing port/tick replaces it. Integer/toggle controls
require step mode. Invalid values, ticks, ports or duplicate persisted points
reject the edit or file. Edits are undoable and rebuild on the control thread
while stopped.
Effect lanes use the project tempo, hold first/last outside their range and
apply before run at each exact sample. Distinct ticks rounding to one sample
resolve to the later tick. Linear curves avoid stepping continuous controls;
intentional step curves may click. The initial implementation calls run(1) while
any lane exists, with connected preallocated buffers. CPU cost can be significant;
only the documented LADSPA set is qualified. No worker, allocator, loader or
clock is called from the host audio path. Native ABI 2 callers continue to use
sample-offset parameter events, applied before equal-offset MIDI events.
any lane exists, with connected preallocated buffers. CPU cost can be
significant; only the documented LADSPA set is qualified. No worker, allocator,
loader or clock is called from the host audio path. Native ABI 2 callers
continue to use sample-offset parameter events, applied before equal-offset MIDI
events.
Live MIDI input maps CC7 to per-channel volume (0..1), CC10 to pan (64 center),
and CC64 to sustain (>=64 down). Volume/pan ramp over 5 ms. These default mappings
apply to live sine voices, not track faders or prerecorded clips; controller
capture, automatic learn and plugin MIDI are not supported. CC120/123 and
Panic release all live voices and sustain and suppress already-started scheduled
notes. Note-on velocity zero decodes to note-off. Repeated live note-on on one
channel/pitch retriggers one voice; note-off releases it or marks it pending while
sustain is held. Sustain-up releases pending voices, never newer retriggers.
Seek, stop, teardown and disconnect clear live voices; plan reset returns controller
values to unity/center. Equal-offset live events execute in input order before
the sample; arrange note ends precede starts, and scheduled mixer values are
evaluated at that sample. Overlapping arranged notes are independent.
and CC64 to sustain (>=64 down). Volume/pan ramp over 5 ms. These default
mappings apply to live sine voices, not track faders or prerecorded clips;
controller capture, automatic learn and plugin MIDI are not supported. CC120/123
and Panic release all live voices and sustain and suppress already-started
scheduled notes. Note-on velocity zero decodes to note-off. Repeated live
note-on on one channel/pitch retriggers one voice; note-off releases it or marks
it pending while sustain is held. Sustain-up releases pending voices, never
newer retriggers. Seek, stop, teardown and disconnect clear live voices; plan
reset returns controller values to unity/center. Equal-offset live events
execute in input order before the sample; arrange note ends precede starts, and
scheduled mixer values are evaluated at that sample. Overlapping arranged notes
are independent.
Format v6 preserves v5 data plus effect lanes. A v5 plugin migrates with no lane
and the project tempo. Repeated render/save/reopen, odd block partition, exact
@ -46,12 +50,16 @@ make test test-control test-plugins test-gui-plugins test-plugins-real
```
Physical controller devices and listening remain unqualified; no tempo map,
latency compensation, MIDI capture, pitch bend or plugin crash isolation is added.
latency compensation, MIDI capture, pitch bend or plugin crash isolation is
added.
## Common parameter targets
---
`xtc_control.h` identifies a parameter by `(kind, stable owner ID, parameter ID)`.
IDs never mean a dense list index and are not DSP pointers:
## Common Parameter Targets
`xtc_control.h` identifies a parameter by
`(kind, stable owner ID, parameter ID)`. IDs never mean a dense list index and
are not DSP pointers:
| Kind | Owner | Parameter IDs | Metadata/reset defaults |
| --- | --- | --- | --- |
@ -59,39 +67,44 @@ IDs never mean a dense list index and are not DSP pointers:
| PLUGIN | LADSPA graph node ID | Actual input-control port | Descriptor name, bounds, default and discrete/logarithmic hints; units unspecified |
| LIVE | MIDI channel 0..15 | 0 volume, 1 pan, 2 sustain | Volume 0..1 default 1; pan -1..1 default 0; sustain boolean default 0 |
`xtc_project_parameter` returns copied metadata, static configuration and whether
a lane exists. LIVE values are reset defaults, not callback-owned controller
readbacks. `xtc_project_set_parameter` changes TRACK/PLUGIN static configuration
without deleting automation. `xtc_project_automate` sets/replaces/removes points
for TRACK/PLUGIN through one C contract. LIVE static setters and timeline lanes
are unsupported: these controls belong to incoming MIDI events, not the arranged
instrument. Plugin metadata/control edits execute trusted discovery on the
control thread; they never run inside audio. Model-only clients must compile and
publish accepted snapshots before rendering; GUI edits do this transactionally.
`xtc_project_parameter` returns copied metadata, static configuration and
whether a lane exists. LIVE values are reset defaults, not callback-owned
controller readbacks. `xtc_project_set_parameter` changes TRACK/PLUGIN static
configuration without deleting automation. `xtc_project_automate`
sets/replaces/removes points for TRACK/PLUGIN through one C contract. LIVE
static setters and timeline lanes are unsupported: these controls belong to
incoming MIDI events, not the arranged instrument. Plugin metadata/control edits
execute trusted discovery on the control thread; they never run inside audio.
Model-only clients must compile and publish accepted snapshots before rendering;
GUI edits do this transactionally.
Unknown owners/ports fail without touching the destination model/output. Missing
plugin modules fail discovery rather than guessing ranges or installing a dummy.
Track deletion removes its lanes; plugin deletion removes its embedded lanes.
Compatible plugin replacement resets controls/automation as described in
PLUGIN_HOSTING.md. Persisted dangling references reject the complete load;
the current project remains intact. The GUI reports rejected edits/Open failures.
PLUGIN_HOSTING.md. Persisted dangling references reject the complete load; the
current project remains intact. The GUI reports rejected edits/Open failures.
## Interpolation and precedence
---
## Interpolation and Precedence
The Mixer Interpolation chooser selects Linear or Hold (step) for the point
being added/replaced. A point owns the segment beginning there. At a hold point,
its value remains until the next point's exact sample. Linear segments interpolate
between endpoint values without an extra smoothing stage. Hold first/last before/
after the lane. Gain/pan for tracks and buses support both; integer/toggle LADSPA
controls require hold. Existing files migrate mixer points to linear.
its value remains until the next point's exact sample. Linear segments
interpolate between endpoint values without an extra smoothing stage. Hold
first/last before/ after the lane. Gain/pan for tracks and buses support both;
integer/toggle LADSPA controls require hold. Existing files migrate mixer points
to linear.
Automation is absolute project time, independent of clip starts, ends, trims,
splits and moves; it neither restarts nor resets at a clip boundary. Tick-to-sample
rounding uses the prepared constant tempo/rate; distinct ticks landing on the
same sample choose the greatest tick. Empty lanes restore the static value.
Static edits while a lane exists change the fallback value, not the audible lane.
GUI changes stop/rebuild snapshots; live mutation of faders/plugin instances is
not introduced. Plugin reset removes lanes only when explicitly applied.
splits and moves; it neither restarts nor resets at a clip boundary.
Tick-to-sample rounding uses the prepared constant tempo/rate; distinct ticks
landing on the same sample choose the greatest tick. Empty lanes restore the
static value. Static edits while a lane exists change the fallback value, not
the audible lane. GUI changes stop/rebuild snapshots; live mutation of
faders/plugin instances is not introduced. Plugin reset removes lanes only when
explicitly applied.
At each sample, arranged track/bus and plugin lanes apply to their own targets.
Incoming MIDI volume/pan/sustain affect only live sine voices on that channel,
@ -107,41 +120,47 @@ the new position. Backward jumps evaluate the earlier timeline again; tests
exercise explicit wraps. There is no loop transport feature or promise to
restore opaque LADSPA delay history across jumps (transport reset retains it).
## Configurable live MIDI bindings
---
## Configurable Live MIDI Bindings
Audio → MIDI controller mapping lists the current 16 channels. Enter
`channel volume_CC pan_CC sustain_CC`, for example `2 1 74 11`. Channels are
zero-based; CCs are 0..127, or 128 to disable a target. Enabled CCs must be distinct
within a channel; CC120 and CC123 cannot be rebound. All channels default to
7/10/64. Rebinding replaces the old binding; it is not an additional route. An
unmapped CC does nothing. Other channels remain independent. The same volume,
pan and sustain transfer functions apply regardless of the chosen CC number.
Disabling a target leaves it at its runtime reset value until plan replacement.
Changing a map stops/rebuilds playback and is one undoable edit; maps survive
save/Open and undo/redo. Cancel and invalid/duplicate/reserved bindings retain
the prior configuration. There is no automatic learn, arbitrary plugin/track CC
mapping, MIDI capture or write/touch/latch automation mode.
zero-based; CCs are 0..127, or 128 to disable a target. Enabled CCs must be
distinct within a channel; CC120 and CC123 cannot be rebound. All channels
default to 7/10/64. Rebinding replaces the old binding; it is not an additional
route. An unmapped CC does nothing. Other channels remain independent. The same
volume, pan and sustain transfer functions apply regardless of the chosen CC
number. Disabling a target leaves it at its runtime reset value until plan
replacement. Changing a map stops/rebuilds playback and is one undoable edit;
maps survive save/Open and undo/redo. Cancel and invalid/duplicate/reserved
bindings retain the prior configuration. There is no automatic learn, arbitrary
plugin/track CC mapping, MIDI capture or write/touch/latch automation mode.
Plans copy only 48 binding bytes on the control thread. The callback compares
at most three CC numbers per control event, uses existing preallocated ramps/
Plans copy only 48 binding bytes on the control thread. The callback compares at
most three CC numbers per control event, uses existing preallocated ramps/
voices and the existing 256-event block budget. Decode now accepts all valid
three-byte CC messages; even unmapped CCs count against that event budget.
Mixer points compile to bounded sorted arrays with binary lane lookup; processing
does no allocation, sort, file/module I/O, locking or GUI work. LADSPA automated
three-byte CC messages; even unmapped CCs count against that event budget. Mixer
points compile to bounded sorted arrays with binary lane lookup; processing does
no allocation, sort, file/module I/O, locking or GUI work. LADSPA automated
run(1) cost and native plugin trust limits remain unchanged.
## Persistence and verification
---
## Persistence and Verification
Project v10 appends a mixer interpolation field and 16 controller-map records;
v1–v9 migrate to linear mixer lanes and default 7/10/64 maps. LADSPA point encoding
is unchanged. Public `xtc_automation_point` gains `step`: rebuild clients and
initialize it to 0 (linear) or 1 (hold). See PROJECT_FORMAT.md for exact grammar.
v1–v9 migrate to linear mixer lanes and default 7/10/64 maps. LADSPA point
encoding is unchanged. Public `xtc_automation_point` gains `step`: rebuild
clients and initialize it to 0 (linear) or 1 (hold). See PROJECT_FORMAT.md for
exact grammar.
`test_parameters` covers metadata, missing targets, static/automation precedence,
exact hold/linear boundaries, save/reopen, malformed maps, low-rate sample
collisions, odd blocks, seeks and backward jumps. `test_control` compares default
and remapped event streams, stereo pan, disabled/unmapped controls and immutable
plan maps across resets. Plugin tests cover common-target edits and persistence;
GUI tests cover interpolation, map rollback, undo/redo and save/Open. Existing
callback guards and sanitizers verify host ownership constraints. Physical
controller devices, listening and extended high-density endurance are pending.
`test_parameters` covers metadata, missing targets, static/automation
precedence, exact hold/linear boundaries, save/reopen, malformed maps, low-rate
sample collisions, odd blocks, seeks and backward jumps. `test_control` compares
default and remapped event streams, stereo pan, disabled/unmapped controls and
immutable plan maps across resets. Plugin tests cover common-target edits and
persistence; GUI tests cover interpolation, map rollback, undo/redo and
save/Open. Existing callback guards and sanitizers verify host ownership
constraints. Physical controller devices, listening and extended high-density
endurance are pending.

View file

@ -1,42 +1,53 @@
# Creative workflow additions
# Creative Workflow Additions
These features prioritize fast musical experiments and retain the existing
control-thread preparation and project undo model. They introduce no dependency
or project-format change.
## Selected track inserts
---
## Selected Track Inserts
**Track → Show selected track inserts** (Ctrl+5) opens the sequencer or active
mixer selection directly. Bus inserts are unsupported; no selection leaves the
workspace unchanged. **View → Track inserts** still opens the rack independently.
Ctrl+4 remains the piano-roll command, resolving the earlier shortcut collision.
workspace unchanged. **View → Track inserts** still opens the rack
independently. Ctrl+4 remains the piano-roll command, resolving the earlier
shortcut collision.
## Duplicate an effect
---
Select a slot, then **Rack actions → Duplicate effect**. The copy appears directly
after it, retaining controls, automation, wet amount and bypass. It is an
independent instance with fresh processing history. Eight-slot capacity and
## Duplicate an Effect
Select a slot, then **Rack actions → Duplicate effect**. The copy appears
directly after it, retaining controls, automation, wet amount and bypass. It is
an independent instance with fresh processing history. Eight-slot capacity and
plugin preparation errors preserve the old chain; Undo removes the copy.
## Copy a chain between tracks
---
## Copy A Chain Between Tracks
**Rack actions → Copy chain** stores an independent metadata snapshot. Select
another track and use **Paste chain (replace)** to replace its complete chain,
including output gain. Copying neither loads modules nor changes undo history;
pasting prepares the candidate and is undoable. The clipboard is internal to
this session and clears on successful New/Open. Preparation errors keep both
the destination and clipboard intact.
this session and clears on successful New/Open. Preparation errors keep both the
destination and clipboard intact.
## Compare effect configurations
---
**Rack actions → Compare** captures or recalls A and B. Each slot is bound to the
track where it was captured; Recall is available only on that track. Capturing
copies metadata without affecting undo or loading modules. Recall prepares a
fresh chain and is undoable; histories and opaque plugin states are excluded.
The two comparison slots clear on successful New/Open. Comparisons stop
transport when recalled, following the existing safe insert-edit workflow.
## Compare Effect Configurations
## Creative starting points
**Rack actions → Compare** captures or recalls A and B. Each slot is bound to
the track where it was captured; Recall is available only on that track.
Capturing copies metadata without affecting undo or loading modules. Recall
prepares a fresh chain and is undoable; histories and opaque plugin states are
excluded. The two comparison slots clear on successful New/Open. Comparisons
stop transport when recalled, following the existing safe insert-edit workflow.
---
## Creative Starting Points
**Add effect → Templates (replace)** offers Slapback (80 ms delay), Dub Echo
(375 ms feedback delay into a short reverb), and Ambient Space (long reverb into
@ -45,15 +56,19 @@ no external plugins. All controls remain editable. Times are milliseconds;
feedback and wet levels are deliberate starting points, without automatic
loudness matching or a claim that the presets suit every source.
## Bulk rack actions
---
## Bulk Rack Actions
**Enable all effects**, **Bypass all effects**, and **Clear chain** each prepare
one undoable edit. Enabling all intentionally enables every slot, including
previously bypassed effects; Undo restores the previous mixed bypass state.
Clear removes slots while preserving chain output gain. No plugin resources
are released in the audio callback. Empty racks return a no-result error.
Clear removes slots while preserving chain output gain. No plugin resources are
released in the audio callback. Empty racks return a no-result error.
## Meter while editing effects
---
## Meter While Editing Effects
The insert workspace shows the selected track's stereo sample peaks after its
fader and before its bus, including the effects' output. Green bars release at
@ -62,34 +77,41 @@ it. Stopped, offline or missing-track readings show the -90 dBFS floor. Changing
tracks clears the indicator. These are sample peaks, not true-peak or loudness
measurements. The display shares the mixer's existing backend snapshot read.
## Tap tempo
---
## Tap Tempo
Tap the transport **Tap** button at a quarter-note pulse. The estimator averages
the last four valid intervals and accepts 20–300 BPM. The first tap primes it;
a pause longer than three seconds restarts it. Too-fast or backward timestamps
are ignored. Each accepted tempo is an undoable edit and stops playback before
preparation; tapping is unavailable while recording. New/Open resets tap history.
The estimator uses monotonic control-thread time, with no audio-callback clock
access or allocation. Tap timing is not a beat detector or MIDI clock sync.
the last four valid intervals and accepts 20–300 BPM. The first tap primes it; a
pause longer than three seconds restarts it. Too-fast or backward timestamps are
ignored. Each accepted tempo is an undoable edit and stops playback before
preparation; tapping is unavailable while recording. New/Open resets tap
history. The estimator uses monotonic control-thread time, with no
audio-callback clock access or allocation. Tap timing is not a beat detector or
MIDI clock sync.
## Audio crossfades
---
Select two partially overlapping audio clips on the same track and choose
**Edit → Audio tools → Crossfade two selected clips**. The earlier clip fades
out across the overlap while the later clip fades in. Gains, positions, source
files and the opposite fades are preserved. Equal boundaries, containment,
adjacency, cross-track selections and MIDI clips reject without partial edits.
This is a linear tick-based crossfade using existing clip envelopes; it does
not move clips, create overlaps or offer equal-power curves. Discrete sample
endpoints retain the engine's existing one-sample boundary convention.
## Audio Crossfades
## Chord strumming
Select two partially overlapping audio clips on the same track and choose **Edit
→ Audio tools → Crossfade two selected clips**. The earlier clip fades out
across the overlap while the later clip fades in. Gains, positions, source files
and the opposite fades are preserved. Equal boundaries, containment, adjacency,
cross-track selections and MIDI clips reject without partial edits. This is a
linear tick-based crossfade using existing clip envelopes; it does not move
clips, create overlaps or offer equal-power curves. Discrete sample endpoints
retain the engine's existing one-sample boundary convention.
---
## Chord Strumming
Select MIDI clips and use **Edit → MIDI tools → Strum chords...**. Enter spacing
in ticks (1–960) and direction (0 ascending, 1 descending). Notes sharing a source
onset and MIDI channel within each clip form a chord. Equal pitches use stable
note-ID order. Each successive note starts one spacing later, while its end,
pitch, velocity and channel remain unchanged. Single notes remain unchanged.
Any resulting zero/negative duration rejects the entire edit; Undo restores the
whole selection. This edits source notes, including notes hidden by clip trims.
It does not generate an arpeggiator or schedule live input.
in ticks (1–960) and direction (0 ascending, 1 descending). Notes sharing a
source onset and MIDI channel within each clip form a chord. Equal pitches use
stable note-ID order. Each successive note starts one spacing later, while its
end, pitch, velocity and channel remain unchanged. Single notes remain
unchanged. Any resulting zero/negative duration rejects the entire edit; Undo
restores the whole selection. This edits source notes, including notes hidden by
clip trims. It does not generate an arpeggiator or schedule live input.

View file

@ -1,11 +1,13 @@
# Editing commands and undo audit
# Editing Commands and Undo Audit
The control thread owns project edits and history. Audio plans are immutable
snapshots; widgets never pass editable project pointers into the callback.
Graph commands and batch note commands use the existing public C command APIs.
The GUI groups a completed gesture or accepted dialog as one history step.
snapshots; widgets never pass editable project pointers into the callback. Graph
commands and batch note commands use the existing public C command APIs. The GUI
groups a completed gesture or accepted dialog as one history step.
## Operation audit
---
## Operation Audit
| Operation | History / grouping | Engine and external effects |
| --- | --- | --- |
@ -30,7 +32,9 @@ plus a baseline; new accepted edits discard redo, and eviction drops the oldest
baseline. Monotonic ID high-water marks survive undo and branches. History is
session-only; restart/New/Open discards it.
## History ownership and failure boundary
---
## History Ownership and Failure Boundary
`xtc_history_reserve` allocates one empty fixed-capacity project/graph snapshot
before an edit mutates the document or a candidate is submitted to the engine.
@ -47,8 +51,8 @@ The snapshot contains metadata/graph configuration only, never sample buffers,
DSP instances, module handles or GUI pointers. An unused reservation is freed
with history. This adds at most one pending metadata snapshot to the 33 retained
states. C clients that call record without reserving retain the original
clone-and-report-allocation-failure behavior; they must handle that result.
All history work is serialized on the control thread, never the audio callback.
clone-and-report-allocation-failure behavior; they must handle that result. All
history work is serialized on the control thread, never the audio callback.
Undo/redo checks out an owned candidate without moving the cursor, stops/resets
transport and prepares its assets/plans, then accepts the adjacent history step
@ -57,33 +61,38 @@ cursor/document, so retry is possible. Preparation failure may leave transport
stopped at zero; it does not resume playback automatically. Snapshot restoration
marks the document dirty conservatively, even if it matches a saved file.
## Selection, focus and menus
---
Ctrl+Z undoes; Ctrl+Shift+Z and Ctrl+Y redo. The main Edit menu disables unavailable
steps and disables history while recording. Piano roll also has Edit/Undo/Redo
shortcuts. Undo preserves currently selected track/clip, mixer strip, graph node
and piano-roll clip/note when their stable IDs still exist (notes must remain
visible under the current filter). Clip selection follows its restored track. A removed selection
clears, with track fallback for a removed clip. A visible piano roll stays open
when its clip survives; missing clips hide it. Historical selections/layouts are
not saved in snapshots; undo does not resurrect a selection already cleared by
a deletion. Open/New retain their reset behavior.
## Selection, Focus and Menus
Editor-local shortcuts act on focused editors. Text inputs retain Space and
text undo/editing keys; numeric mixer inputs do not invoke fader shortcuts.
Timeline Ctrl+D duplicates, F2 renames, Alt+Up/Down reorders tracks, S splits
audio at the cursor, A opens audio properties and Delete removes the selection.
Graph Left/Right chooses a node, L arranges by flow, I inserts unity gain in the
selected wire, Delete removes the selected node/wire and Escape cancels gestures.
Piano-roll commands are listed in its Notes menu. Escape cancels gestures;
ordinary deletion is reversible and does not add a confirmation dialog.
Ctrl+Z undoes; Ctrl+Shift+Z and Ctrl+Y redo. The main Edit menu disables
unavailable steps and disables history while recording. Piano roll also has
Edit/Undo/Redo shortcuts. Undo preserves currently selected track/clip, mixer
strip, graph node and piano-roll clip/note when their stable IDs still exist
(notes must remain visible under the current filter). Clip selection follows its
restored track. A removed selection clears, with track fallback for a removed
clip. A visible piano roll stays open when its clip survives; missing clips hide
it. Historical selections/layouts are not saved in snapshots; undo does not
resurrect a selection already cleared by a deletion. Open/New retain their reset
behavior.
Editor-local shortcuts act on focused editors. Text inputs retain Space and text
undo/editing keys; numeric mixer inputs do not invoke fader shortcuts. Timeline
Ctrl+D duplicates, F2 renames, Alt+Up/Down reorders tracks, S splits audio at
the cursor, A opens audio properties and Delete removes the selection. Graph
Left/Right chooses a node, L arranges by flow, I inserts unity gain in the
selected wire, Delete removes the selected node/wire and Escape cancels
gestures. Piano-roll commands are listed in its Notes menu. Escape cancels
gestures; ordinary deletion is reversible and does not add a confirmation
dialog.
Graph right-click now selects the node/wire under the pointer before showing
Inspect/Arrange/Insert/Delete; empty space clears a stale selection. Piano-roll
right-click selects the clicked note and offers Properties/Quantize/Repeat/Delete.
It no longer immediately deletes the note. Timeline context menus retain their
selection and duplicate/rename/delete/audio commands. Dismissing a context menu
does not edit the document. Context menus and widgets remain C++/FLTK UI code.
right-click selects the clicked note and offers
Properties/Quantize/Repeat/Delete. It no longer immediately deletes the note.
Timeline context menus retain their selection and duplicate/rename/delete/audio
commands. Dismissing a context menu does not edit the document. Context menus
and widgets remain C++/FLTK UI code.
Open/import/plugin load/replacement paint a busy cursor and operation status
before synchronous work, flushing drawing without dispatching new input or
@ -93,13 +102,15 @@ optional-support and audio errors. Unsaved New/Open/Quit and Save As overwrite
keep their existing confirmations; undoable deletes and external-media reference
edits do not request extra approval. No operation here deletes source media.
## Evidence and manual procedure
---
`make all test gui test-gui-plugins test-reliability analyze test-workflow
test-gui-jack` covers core commands, grouped gestures/batches, backend rollback,
selection retention, take undo/redo and real software JACK state restoration.
The GUI fixture injects history-reservation failure before graph/map/import
publication and verifies unchanged document, submission count and redo. The
## Evidence and Manual Procedure
`make all test gui test-gui-plugins test-reliability analyze test-workflow test-gui-jack`
covers core commands, grouped gestures/batches, backend rollback, selection
retention, take undo/redo and real software JACK state restoration. The GUI
fixture injects history-reservation failure before graph/map/import publication
and verifies unchanged document, submission count and redo. The
allocation-interposed reliability suite fails each reservation allocation, then
fails all allocations while capturing a reserved edit; redo branching and
snapshot restoration still succeed. ASan/UBSan cover the same ownership paths.

View file

@ -1,44 +1,51 @@
# GUI guidelines
# GUI Guidelines
Use FLTK/C++ for windows, menus, dialogs, and the event loop. Keep all audio
engine and project-domain logic in C.
Main views:
- Sequencer: tracks, clips, ruler, playhead, selection, zoom and scrolling.
- Mixer: channels, faders, pan, mute/solo and meters.
- Modular graph: nodes, ports, connections, validation feedback.
- Transport: play/stop/record, tempo, meter, loop and position.
Use custom-drawn widgets where ordinary controls are not a good fit: timeline,
waveforms, piano roll, mixer strips and graph canvas. Keep drawing separate
from project mutation. Never call FLTK from the audio thread.
waveforms, piano roll, mixer strips and graph canvas. Keep drawing separate from
project mutation. Never call FLTK from the audio thread.
Visual direction: compact, high-contrast, restrained dark interface. Avoid
excessive animation and modal configuration. Recreate broad usability ideas,
not energyXT's exact artwork or proprietary resources.
excessive animation and modal configuration. Recreate broad usability ideas, not
energyXT's exact artwork or proprietary resources.
---
## Shell
The shell opens JACK on startup through `xtc_backend.h`. It never accesses
backend structures, processes samples, or calls JACK itself. Graph domain
data is owned by opaque C objects, manipulated only through the C API. All backend control calls run on the GUI thread; FLTK is never called
from an audio callback. No server is started and no audio ports are connected
by the GUI. The initially empty graph produces silence.
backend structures, processes samples, or calls JACK itself. Graph domain data
is owned by opaque C objects, manipulated only through the C API. All backend
control calls run on the GUI thread; FLTK is never called from an audio
callback. No server is started and no audio ports are connected by the GUI. The
initially empty graph produces silence.
- **Play / F5:** start the engine. **Stop / F8:** deactivate it. **Pause / F7:** hold playback. **Space:** toggle Play/Stop.
**To start / Home:** stop and seek to zero. Pause/Home are disabled while recording.
These advance/pause the sample-counted playhead; imported audio clips play through their graph source.
- **Reconnect / F6:** close the old client and open a fresh stopped client.
Use this after server loss or sample-rate changes. A failed close preserves
the handle and reports the failure instead of opening another client.
- **Play / F5:** start the engine. **Stop / F8:** deactivate it. **Pause / F7:**
hold playback. **Space:** toggle Play/Stop. **To start / Home:** stop and seek
to zero. Pause/Home are disabled while recording. These advance/pause the
sample-counted playhead; imported audio clips play through their graph source.
- **Reconnect / F6:** close the old client and open a fresh stopped client. Use
this after server loss or sample-rate changes. A failed close preserves the
handle and reports the failure instead of opening another client.
- **View menu / Ctrl+1, Ctrl+2, Ctrl+3:** switch between sequencer, mixer and
modular graph. Tabs provide the same view switching. Mixer, graph and sequencer editors are described below.
modular graph. Tabs provide the same view switching. Mixer, graph and
sequencer editors are described below.
- **File / Quit / Ctrl+Q** or the window close button: close the backend and
synchronize audio callbacks before hiding/destroying widgets. A failed close
leaves the window open for a retry. New/Open/Save actions are described below. About identifies the development milestone and version.
leaves the window open for a retry. New/Open/Save actions are described below.
About identifies the development milestone and version.
The indicator shows disconnected, ready, running, server lost, format changed
or process-error states. The status line shows rate, actual block size, mono
The indicator shows disconnected, ready, running, server lost, format changed or
process-error states. The status line shows rate, actual block size, mono
layout, processed block count, xruns and process result. Command errors remain
visible until a successful control action; periodic refresh does not erase them.
A GUI-owned 100 ms timer reads atomic snapshots and collects retired plans.
@ -54,41 +61,44 @@ process exit. Normal close/reconnect paths permit retry and release ownership
only after successful close. The entry point catches C++ exceptions before
process exit. Backend operations return C error codes throughout.
Tests: `make test-gui` runs real widgets/callbacks/timers against a fake C backend
under Xvfb, including control errors, startup without JACK, partially initialized
handles, server/format notifications, view switching, retrying close, and timer
removal. `make test-gui-jack` additionally requires a running JACK-compatible
server, tests repeated start/stop, reconnect and active-client close, and creates
no routes. Physical playback is outside this shell. See BUILD.md and the
integration test README for sanitizer limitations and keyboard smoke results.
Tests: `make test-gui` runs real widgets/callbacks/timers against a fake C
backend under Xvfb, including control errors, startup without JACK, partially
initialized handles, server/format notifications, view switching, retrying
close, and timer removal. `make test-gui-jack` additionally requires a running
JACK-compatible server, tests repeated start/stop, reconnect and active-client
close, and creates no routes. Physical playback is outside this shell. See
BUILD.md and the integration test README for sanitizer limitations and keyboard
smoke results.
## Modular editor
---
Open **Modular Graph** or press **Ctrl+3**. Add oscillator (440 Hz), gain (0.10),
pass-through meter or output nodes with the toolbar. Their parameters are fixed
presets in this milestone and are drawn on each node; parameter editing and
live meter display are not implemented. Connect oscillator → gain → meter →
output for a simple test chain. No sound reaches hardware until ports are
explicitly connected outside the GUI. Output nodes are required to render;
## Modular Editor
Open **Modular Graph** or press **Ctrl+3**. Add oscillator (440 Hz), gain
(0.10), pass-through meter or output nodes with the toolbar. Their parameters
are fixed presets in this milestone and are drawn on each node; parameter
editing and live meter display are not implemented. Connect oscillator → gain →
meter → output for a simple test chain. No sound reaches hardware until ports
are explicitly connected outside the GUI. Output nodes are required to render;
isolated sources and an empty graph produce silence.
Drag a node body/title to move it. Drag a circular output port to an input port
to connect. Click a node or wire to select it (yellow outline/wire), then press
Delete/Backspace or **Delete selection**. Deleting a node deletes its incident
wires. Escape cancels a drag preview. Canvas scrollbars pan the view; positions
are retained across edits/reconnect but not across application restart. There
is no graph zoom, multiselection or external hardware routing UI. Semantic
graph edits participate in project undo/redo; layout does not. The default
project supports 64 nodes; auto-layout can grow the scrollable canvas.
are retained across edits/reconnect but not across application restart. There is
no graph zoom, multiselection or external hardware routing UI. Semantic graph
edits participate in project undo/redo; layout does not. The default project
supports 64 nodes; auto-layout can grow the scrollable canvas.
Every semantic action becomes a C graph command. The window owns a C project containing the graph
handle and publishes the C-created candidate through the backend. Cycle,
invalid-direction, duplicate connection, missing-port, capacity and backend
failures are shown below the canvas; failed edits leave the graph unchanged.
Capacity feedback distinguishes the possible node limit/pending edit condition
and allows retry. Editing while stopped replaces the prepared plan without
starting playback. Editing with a disconnected startup backend stays local
until reconnect. A lost or changed live backend requires reconnect before
Every semantic action becomes a C graph command. The window owns a C project
containing the graph handle and publishes the C-created candidate through the
backend. Cycle, invalid-direction, duplicate connection, missing-port, capacity
and backend failures are shown below the canvas; failed edits leave the graph
unchanged. Capacity feedback distinguishes the possible node limit/pending edit
condition and allows retry. Editing while stopped replaces the prepared plan
without starting playback. Editing with a disconnected startup backend stays
local until reconnect. A lost or changed live backend requires reconnect before
further semantic edits. Moving/selecting nodes does not touch the engine.
The graph state line distinguishes offline, prepared, queued and active states.
@ -100,54 +110,59 @@ rejected capacity edit. Plan replacement can reset oscillator phase and is not
crossfaded. Render validation and active replacement tests are described in
tests/integration/README.md.
---
## Projects and sequencer
## Projects and Sequencer
File/New (Ctrl+N), Open (Ctrl+O), Save (Ctrl+S), and Save As
(Ctrl+Shift+S) use native file dialogs and the C persistence API. New/Open/Quit
offer Cancel/Discard when semantic edits are unsaved; save first to retain them.
Save As confirms overwrites. The title marks unsaved changes. Failed loads and
File/New (Ctrl+N), Open (Ctrl+O), Save (Ctrl+S), and Save As (Ctrl+Shift+S) use
native file dialogs and the C persistence API. New/Open/Quit offer
Cancel/Discard when semantic edits are unsaved; save first to retain them. Save
As confirms overwrites. The title marks unsaved changes. Failed loads and
backend submissions retain the old document. Loading parses before stopping
audio; installing a valid candidate stops and resets transport before compiling
and submitting. A later failure can leave the retained document stopped at zero.
Opening/New resets graph layout; graph positions are not saved. See PROJECT_FORMAT.md.
Opening/New resets graph layout; graph positions are not saved. See
PROJECT_FORMAT.md.
Sequencer offers Add track, Add region, Delete region/track, Zoom +/-. Select a
track header to add a one-bar region there at the cursor. Select a region to
delete it; deleting a selected track cascades to its regions. Delete also works
with canvas focus. Drag a region body to move it in time or between tracks;
drag either edge to trim within its original source bounds. Edits commit on
release with the selected snap grid; Escape cancels. There is no drag preview.
Undo, rename, tempo editing and additive clip selection are supported. Group
commands, clipboard and musical transforms are described in
with canvas focus. Drag a region body to move it in time or between tracks; drag
either edge to trim within its original source bounds. Edits commit on release
with the selected snap grid; Escape cancels. There is no drag preview. Undo,
rename, tempo editing and additive clip selection are supported. Group commands,
clipboard and musical transforms are described in
[MODERN_EDITING.md](MODERN_EDITING.md). Overlapping regions are permitted.
The scrollable canvas expands to cover the arrangement plus a margin; zoom
ranges from 24 to 384 pixels/quarter. Names, tempo and 4/4 meter are drawn.
A ruler click seeks while stopped (or positions the cursor when disconnected).
The 100 ms GUI refresh converts the backend's actual sample counter to ticks;
it never integrates wall-clock time. Play resumes and Stop pauses. Reconnect,
New and Open reset position. Add region creates silent metadata; imported WAV clips play through the
audio source node (see below). MIDI clips and notes are described below. The C model
supports a constant tempo; a tempo map and scheduler are future work.
ranges from 24 to 384 pixels/quarter. Names, tempo and 4/4 meter are drawn. A
ruler click seeks while stopped (or positions the cursor when disconnected). The
100 ms GUI refresh converts the backend's actual sample counter to ticks; it
never integrates wall-clock time. Play resumes and Stop pauses. Reconnect, New
and Open reset position. Add region creates silent metadata; imported WAV clips
play through the audio source node (see below). MIDI clips and notes are
described below. The C model supports a constant tempo; a tempo map and
scheduler are future work.
Tests cover C-model/persistence boundaries, GUI move/trim gestures and zoom,
save/reopen, failed load/submission rollback, sample-derived cursor updates,
and live JACK playhead advance/pause.
save/reopen, failed load/submission rollback, sample-derived cursor updates, and
live JACK playhead advance/pause.
---
## Audio workflow
## Audio Workflow
Use File/Import WAV (Ctrl+I) to add an audio clip at the playhead on the selected
track, or on a new Audio track. Record (Shift+F5) chooses a WAV destination and
captures JACK input until Stop finalizes and adds the take. No input routes are
created automatically. Save after Stop; the project references the external WAV.
Use File/Import WAV (Ctrl+I) to add an audio clip at the playhead on the
selected track, or on a new Audio track. Record (Shift+F5) chooses a WAV
destination and captures JACK input until Stop finalizes and adds the take. No
input routes are created automatically. Save after Stop; the project references
the external WAV.
Import creates an Audio clips graph source and output route if the source is
absent. Modular Graph offers Add audio source for explicit routing through gain
or meter nodes. All tracks currently share one mono timeline mix. Arrangement
edits stop playback; graph edits remain live outside recording. During recording,
edits, file actions, reconnect and close require Stop first.
edits stop playback; graph edits remain live outside recording. During
recording, edits, file actions, reconnect and close require Stop first.
Open/Play reports missing, unreadable or unsupported assets, preserving their
regions as silence. The backend status shows the unique issue count. Select a
@ -158,21 +173,26 @@ can block the GUI while reading, but never the audio callback.
See AUDIO_FILES.md for supported WAV formats, memory limits, musical placement,
recording overflow/partial-take recovery, external asset portability and the
software-only acceptance tests. Waveform drawing, input monitoring, recording
latency compensation and per-track graph inserts are not implemented.
Per-track gain/pan/mute/solo is available in the mixer below.
latency compensation and per-track graph inserts are not implemented. Per-track
gain/pan/mute/solo is available in the mixer below.
## Piano roll
---
## Piano Roll
Add MIDI clip creates a one-bar clip on the selected/first existing track at the
cursor. Select it and choose Piano roll (View/Piano roll, Ctrl+4). Click to draw,
drag a body to move pitch/time, drag its right edge to resize, and right-click
or Delete to remove. Notes use source ticks with 1/16 snapping; trimmed source
content is retained. Edits stop transport and are blocked during audio recording.
Panic/F9 silences held notes; Record continues to mean WAV audio capture.
Modular Graph offers Add instrument for explicit routing. Status includes MIDI
drops. See MIDI.md for voice limits, unsupported messages and live input routing.
cursor. Select it and choose Piano roll (View/Piano roll, Ctrl+4). Click to
draw, drag a body to move pitch/time, drag its right edge to resize, and
right-click or Delete to remove. Notes use source ticks with 1/16 snapping;
trimmed source content is retained. Edits stop transport and are blocked during
audio recording. Panic/F9 silences held notes; Record continues to mean WAV
audio capture. Modular Graph offers Add instrument for explicit routing. Status
includes MIDI drops. See MIDI.md for voice limits, unsupported messages and live
input routing.
## Mixer and editing
---
## Mixer and Editing
Mixer (Ctrl+2) now has a selectable track strip, gain/pan controls, mute/solo,
Output L/R peaks and editable gain/pan automation lanes. Mixer edits stop
@ -182,35 +202,42 @@ and redo up to 32 project edits; New/Open resets history. M/S and arrow keys
provide mixer shortcuts. See MIXER.md for exact controls, history rollback,
stereo port routing, automation semantics and prototype limits.
## Minimal composition workflow
---
## Minimal Composition Workflow
[WORKFLOW.md](WORKFLOW.md) walks through creating, arranging, saving, closing,
reopening and playing MIDI/audio with the GUI. New/Open select Sequencer;
Ctrl+3 still opens the modular graph. Stop retains its established position-hold
reopening and playing MIDI/audio with the GUI. New/Open select Sequencer; Ctrl+3
still opens the modular graph. Stop retains its established position-hold
behavior and finalizes recording; explicit Pause retains position without
finalizing a take (and is refused during recording). Home stops and returns to
zero. Failed candidate installation now refreshes the actual backend state
even when the previous document is retained.
zero. Failed candidate installation now refreshes the actual backend state even
when the previous document is retained.
`make test-workflow` uses a live JACK-compatible backend and Xvfb, with exact
stereo render comparisons across destruction/recreation of the GUI and live
playhead/meter/transport checks. `test-gui` covers stop/seek failure and recording
guards. No project files need manual editing for this workflow.
playhead/meter/transport checks. `test-gui` covers stop/seek failure and
recording guards. No project files need manual editing for this workflow.
## Timing and transitions
---
## Timing and Transitions
The GUI enables 5 ms source boundaries and master start/stop ramps. Ordinary
Pause/Stop now request a fade and show “stopping” until the timer collects its
acknowledgement and deactivates JACK. Recording Stop, Home, fault handling and
project installation retain immediate semantics. See
[AUDIO_QUALITY.md](AUDIO_QUALITY.md) for timing, overload/denormal behavior, latency
limits, test commands and the pending human-listening checklist.
[AUDIO_QUALITY.md](AUDIO_QUALITY.md) for timing, overload/denormal behavior,
latency limits, test commands and the pending human-listening checklist.
## Routing refinement
---
## Routing Refinement
Graph selection now reports edge counts, output reachability and external-input
fallback. Direction arrows, role colors, incident-wire highlighting and no-output
badges help inspect routes. View / Arrange graph by signal flow (L) is a layout
command; Edit / Insert unity gain in selected wire (I) is one undoable graph edit.
Left/Right selects nodes; Tab retains ordinary focus navigation. See [ROUTING.md](ROUTING.md) for summing, stereo-lane,
cycle, live publication and deferred grouping/template semantics.
fallback. Direction arrows, role colors, incident-wire highlighting and
no-output badges help inspect routes. View / Arrange graph by signal flow (L) is
a layout command; Edit / Insert unity gain in selected wire (I) is one undoable
graph edit. Left/Right selects nodes; Tab retains ordinary focus navigation. See
[ROUTING.md](ROUTING.md) for summing, stereo-lane, cycle, live publication and
deferred grouping/template semantics.

View file

@ -1,48 +1,78 @@
# Project Brief: A Modern, Lightweight, energyXT-Inspired DAW
**Document purpose:** This file is the authoritative project brief and implementation guide for an AI coding agent working inside an IDE.
**Document purpose:** This file is the authoritative project brief and
implementation guide for an AI coding agent working inside an IDE.
**Working project name:** `xtc` (temporary; keep the project name configurable and do not hard-code it into public APIs).
**Working project name:** `xtc` (temporary; keep the project name configurable
and do not hard-code it into public APIs).
**Primary goal:** Build a modern, reliable, lightweight digital audio workstation inspired by the workflow and modular philosophy of energyXT. This is a new implementation, not a clone of energyXT's proprietary code or assets.
**Primary goal:** Build a modern, reliable, lightweight digital audio
workstation inspired by the workflow and modular philosophy of energyXT. This is
a new implementation, not a clone of energyXT's proprietary code or assets.
**Architecture decision:** **B — Modular engine first.** The sequencer, instruments, effects, mixers, audio inputs, and outputs are components in one unified processing graph. The default user experience should still be a simple sequencer-first workspace; users can open the modular view when they need to inspect or edit signal flow.
**Architecture decision:** **B — Modular engine first.** The sequencer,
instruments, effects, mixers, audio inputs, and outputs are components in one
unified processing graph. The default user experience should still be a simple
sequencer-first workspace; users can open the modular view when they need to
inspect or edit signal flow.
**Language decision:**
- **100% C for the audio engine and core domain logic.**
- **FLTK with C++ for the graphical user interface only.**
- The GUI talks to the core through a small, documented C API.
- Do not introduce C++ into the audio engine or core library.
- Keep dependencies small, prefer open-source components, and avoid unnecessary frameworks.
- Keep dependencies small, prefer open-source components, and avoid unnecessary
frameworks.
---
## 1. Instructions to the AI coding agent
## 1. Instructions to the AI Coding Agent
Treat this document as the project contract. Follow it in every implementation task.
Treat this document as the project contract. Follow it in every implementation
task.
1. Read this file and the existing project before making changes.
2. Inspect the current build system, source tree, and tests. Do not assume the repository is empty.
3. Implement work in small, buildable increments. Each increment must compile and have relevant tests before proceeding.
4. Prefer complete, coherent file updates over partial snippets. Do not silently remove existing functionality.
5. Before editing, state briefly what you intend to change and which files are affected. Then make the changes.
6. Do not invent APIs, FLTK features, audio backend capabilities, or dependency behavior. Check installed headers and official documentation when uncertain.
7. Keep C source compatible with **C99** initially. Use C11 features only when explicitly justified and supported by the selected compiler/toolchain.
8. Use C snake_case identifiers. Use clear names; avoid cryptic one-letter names except conventional loop counters.
9. Validate external input, lengths, indexes, counts, enum values, and allocation sizes. Handle errors explicitly.
10. Do not hide failures, silently clamp invalid data, or continue with partially initialized objects.
11. Avoid unnecessary dependencies, generated-code systems, global mutable state, and elaborate abstractions.
12. Never perform audio-thread-unsafe operations from the real-time callback. See the real-time rules below.
13. Keep the project usable on Linux first. Do not make Linux-specific details leak into the public core API.
2. Inspect the current build system, source tree, and tests. Do not assume the
repository is empty.
3. Implement work in small, buildable increments. Each increment must compile
and have relevant tests before proceeding.
4. Prefer complete, coherent file updates over partial snippets. Do not silently
remove existing functionality.
5. Before editing, state briefly what you intend to change and which files are
affected. Then make the changes.
6. Do not invent APIs, FLTK features, audio backend capabilities, or dependency
behavior. Check installed headers and official documentation when uncertain.
7. Keep C source compatible with **C99** initially. Use C11 features only when
explicitly justified and supported by the selected compiler/toolchain.
8. Use C snake_case identifiers. Use clear names; avoid cryptic one-letter names
except conventional loop counters.
9. Validate external input, lengths, indexes, counts, enum values, and
allocation sizes. Handle errors explicitly.
10. Do not hide failures, silently clamp invalid data, or continue with
partially initialized objects.
11. Avoid unnecessary dependencies, generated-code systems, global mutable
state, and elaborate abstractions.
12. Never perform audio-thread-unsafe operations from the real-time callback.
See the real-time rules below.
13. Keep the project usable on Linux first. Do not make Linux-specific details
leak into the public core API.
14. Add or update tests and documentation with each feature.
15. At the end of a task, report: files changed, behavior added, build/test commands run, results, known limitations, and the next sensible step.
16. If a requirement is ambiguous, choose the smallest safe implementation consistent with this document and explain the assumption. Ask a question only if the ambiguity blocks safe progress.
17. Do not claim that a feature works unless it has been implemented and tested. Clearly distinguish a prototype, stub, and finished feature.
18. Never copy energyXT source code, proprietary resources, UI artwork, or undocumented internal implementation. Use energyXT only as inspiration for high-level workflow ideas.
15. At the end of a task, report: files changed, behavior added, build/test
commands run, results, known limitations, and the next sensible step.
16. If a requirement is ambiguous, choose the smallest safe implementation
consistent with this document and explain the assumption. Ask a question
only if the ambiguity blocks safe progress.
17. Do not claim that a feature works unless it has been implemented and tested.
Clearly distinguish a prototype, stub, and finished feature.
18. Never copy energyXT source code, proprietary resources, UI artwork, or
undocumented internal implementation. Use energyXT only as inspiration for
high-level workflow ideas.
### Definition of done for every task
A task is complete only when:
- the code builds with warnings enabled;
- relevant tests pass;
- error paths are considered;
@ -53,16 +83,19 @@ A task is complete only when:
---
## 2. Product principles
## 2. Product Principles
The application should feel like a small, responsive music instrument rather than a general-purpose content-creation suite.
The application should feel like a small, responsive music instrument rather
than a general-purpose content-creation suite.
### Core principles
- **Simple by default, powerful when opened up.**
- A single unified component graph underlies the application.
- The sequencer, mixer, instruments, effects, audio I/O, and utility processors use a common node model.
- Common operations should require few clicks and avoid modal dialogs when practical.
- The sequencer, mixer, instruments, effects, audio I/O, and utility processors
use a common node model.
- Common operations should require few clicks and avoid modal dialogs when
practical.
- The modular graph should expose real signal flow, not be a decorative diagram.
- The project must remain understandable to a human developer.
- Audio reliability is more important than visual effects or feature count.
@ -75,6 +108,7 @@ The application should feel like a small, responsive music instrument rather tha
### Explicit non-goals for the first release
Do not initially implement:
- video, notation, collaboration, cloud accounts, or online services;
- an integrated marketplace;
- scripting as a requirement for basic operation;
@ -86,11 +120,12 @@ Do not initially implement:
- automatic online update services;
- a proprietary project format that cannot be inspected or migrated.
These may be considered later, but they must not delay the first reliable recording and playback workflow.
These may be considered later, but they must not delay the first reliable
recording and playback workflow.
---
## 3. High-level architecture
## 3. High-Level Architecture
Use distinct layers with narrow interfaces.
@ -125,7 +160,9 @@ Use distinct layers with narrow interfaces.
+------------------------------------------------------------+
```
The C core and audio engine may be separate libraries or clearly separated modules within one C library. Keep their interfaces explicit even if they initially compile into one library.
The C core and audio engine may be separate libraries or clearly separated
modules within one C library. Keep their interfaces explicit even if they
initially compile into one library.
### Suggested repository layout
@ -179,13 +216,14 @@ xtc/
└── build/ # generated; do not commit
```
Keep C and C++ files clearly separated. C headers exposed to C++ must use `extern "C"` guards.
Keep C and C++ files clearly separated. C headers exposed to C++ must use
`extern "C"` guards.
---
## 4. Language and API boundary
## 4. Language and API Boundary
### C code owns
### C Code owns
- project and track data;
- graph nodes, ports, and connections;
@ -198,7 +236,7 @@ Keep C and C++ files clearly separated. C headers exposed to C++ must use `exter
- backend-independent engine state;
- error codes and diagnostic messages.
### C++/FLTK code owns
### C++/FLTK Code owns
- windows and widgets;
- layout and drawing;
@ -211,15 +249,19 @@ Keep C and C++ files clearly separated. C headers exposed to C++ must use `exter
### Boundary rules
- Public C headers must compile from both C and C++.
- Do not expose C++ classes, STL containers, exceptions, or FLTK types through the C API.
- Do not expose C++ classes, STL containers, exceptions, or FLTK types through
the C API.
- Prefer opaque handles for objects whose internal layout must remain private.
- Use explicit integer error codes and documented output parameters.
- Define ownership for every pointer: borrowed, caller-owned, or transferred.
- Do not return pointers into mutable engine memory unless their lifetime is guaranteed.
- Do not return pointers into mutable engine memory unless their lifetime is
guaranteed.
- Do not let GUI widgets mutate internal engine structures directly.
- Avoid a public API that mirrors every internal struct field. Provide stable operations instead.
- Avoid a public API that mirrors every internal struct field. Provide stable
operations instead.
Example API shape (illustrative only; refine after reviewing the actual codebase):
Example API shape (illustrative only; refine after reviewing the actual
codebase):
```c
#ifndef XTC_H
@ -254,17 +296,21 @@ xtc_result xtc_transport_stop(xtc_app *app);
#endif
```
Do not implement this exact API blindly if the existing design calls for a better interface. Keep it small and consistent.
Do not implement this exact API blindly if the existing design calls for a
better interface. Keep it small and consistent.
---
## 5. Modular processing graph — primary design decision
## 5. Modular Processing Graph — Primary Design Decision
**Decision B: Modular engine first.** The graph is the common representation of signal processing and routing. The sequencer is a source of time-based events and clips, not a separate audio system disconnected from the graph.
**Decision B: Modular engine first.** The graph is the common representation of
signal processing and routing. The sequencer is a source of time-based events
and clips, not a separate audio system disconnected from the graph.
### Node categories
Start with a small set:
- audio input;
- audio output;
- audio clip player;
@ -277,11 +323,13 @@ Start with a small set:
- metering/analyzer;
- utility nodes such as channel mapping.
Do not build every node at once. First prove the graph with a test tone, gain node, and output node.
Do not build every node at once. First prove the graph with a test tone, gain
node, and output node.
### Ports and connections
Each node has a stable identifier, a node type, and declared ports. A port has:
- a unique identifier within its node;
- a direction (input or output);
- a signal/data type (audio, MIDI/event, or control);
@ -289,6 +337,7 @@ Each node has a stable identifier, a node type, and declared ports. A port has:
- a documented sample/block processing contract.
Validate connections before accepting them:
- source and destination exist;
- source port is an output and destination is an input;
- signal types are compatible;
@ -298,13 +347,18 @@ Validate connections before accepting them:
### Cycles and feedback
For the first working engine, reject cycles in the audio-processing graph. A directed acyclic graph is easier to schedule safely and predictably. Report a useful error to the user. Do not silently break a cycle.
For the first working engine, reject cycles in the audio-processing graph. A
directed acyclic graph is easier to schedule safely and predictably. Report a
useful error to the user. Do not silently break a cycle.
Feedback support can be considered later using explicit delay/feedback nodes and a defined block-boundary model. Never introduce arbitrary graph cycles without a correct scheduling design.
Feedback support can be considered later using explicit delay/feedback nodes and
a defined block-boundary model. Never introduce arbitrary graph cycles without a
correct scheduling design.
### Graph compilation
Compile the editable graph into an execution plan outside the real-time thread:
1. validate nodes, ports, and edges;
2. identify source nodes and reachable output nodes;
3. detect cycles;
@ -313,21 +367,31 @@ Compile the editable graph into an execution plan outside the real-time thread:
6. allocate buffers and prepare node state;
7. publish an immutable, ready-to-run plan.
The audio callback processes the prepared plan. It must not perform graph discovery, sorting, dynamic allocation, file loading, or plugin creation.
The audio callback processes the prepared plan. It must not perform graph
discovery, sorting, dynamic allocation, file loading, or plugin creation.
### Graph replacement during playback
The editor must not modify the graph currently being traversed by the audio thread. Prepare a new plan off-thread and publish it through a bounded real-time-safe mechanism. Define how the old plan is retired only after the audio thread can no longer access it. Do not use a naive pointer swap followed by immediate free.
The editor must not modify the graph currently being traversed by the audio
thread. Prepare a new plan off-thread and publish it through a bounded
real-time-safe mechanism. Define how the old plan is retired only after the
audio thread can no longer access it. Do not use a naive pointer swap followed
by immediate free.
Possible approaches include a carefully designed single-producer/single-consumer handoff, epochs, or another proven reclamation scheme. Choose one, document it, and test it under stress. Keep the initial design as simple as safety allows.
Possible approaches include a carefully designed single-producer/single-consumer
handoff, epochs, or another proven reclamation scheme. Choose one, document it,
and test it under stress. Keep the initial design as simple as safety allows.
### Sequencer integration
The sequencer stores timeline content and emits events to the graph at the correct sample positions. A clip player or instrument node consumes those events. The GUI timeline is a view/editor for project data; it is not the audio scheduler.
The sequencer stores timeline content and emits events to the graph at the
correct sample positions. A clip player or instrument node consumes those
events. The GUI timeline is a view/editor for project data; it is not the audio
scheduler.
---
## 6. Real-time audio requirements
## 6. Real-Time Audio Requirements
These rules are mandatory.
@ -354,54 +418,75 @@ These rules are mandatory.
- process silence safely when a source is unavailable;
- handle backend buffer sizes and channel counts within documented limits.
Do not assume all backends use a fixed block size unless the backend contract guarantees it. Establish maximum supported frame/event counts and define what happens when limits are exceeded.
Do not assume all backends use a fixed block size unless the backend contract
guarantees it. Establish maximum supported frame/event counts and define what
happens when limits are exceeded.
### Thread ownership
Document which thread owns each mutable resource:
- GUI thread: widgets and GUI state;
- control/project thread: editable project state, file operations, graph compilation;
- control/project thread: editable project state, file operations, graph
compilation;
- audio thread: active processing plan and real-time DSP state;
- backend callbacks: follow the backend's documented threading model.
Avoid sharing mutable structs across threads without an explicit synchronization strategy. Atomics are not automatically safe for every use: verify that the selected atomic operations are lock-free where required and use correct memory ordering.
Avoid sharing mutable structs across threads without an explicit synchronization
strategy. Atomics are not automatically safe for every use: verify that the
selected atomic operations are lock-free where required and use correct memory
ordering.
### Timing
Use audio sample position as the authoritative playback clock. Represent timeline positions with an integer or rational scheme that avoids accumulated floating-point drift. Convert musical time (bars/beats/ticks) to sample positions using tempo and meter maps. Define behavior at tempo changes before implementing advanced tempo automation.
Use audio sample position as the authoritative playback clock. Represent
timeline positions with an integer or rational scheme that avoids accumulated
floating-point drift. Convert musical time (bars/beats/ticks) to sample
positions using tempo and meter maps. Define behavior at tempo changes before
implementing advanced tempo automation.
---
## 7. Audio backend strategy
## 7. Audio Backend Strategy
Start on Linux and keep the backend replaceable.
1. Define a small backend interface in C.
2. Implement one backend first, selected after checking the target system and installed development packages. JACK-compatible audio is a reasonable first target on Linux.
3. Test device enumeration, start/stop, sample rate, buffer size, channel count, disconnects, and shutdown.
2. Implement one backend first, selected after checking the target system and
installed development packages. JACK-compatible audio is a reasonable first
target on Linux.
3. Test device enumeration, start/stop, sample rate, buffer size, channel count,
disconnects, and shutdown.
4. Keep backend-specific headers out of the public API.
5. Add another backend only after the engine works and tests pass.
Do not assume PipeWire-native, JACK compatibility, ALSA PCM, and desktop audio routing are interchangeable. Document the chosen backend's setup and limitations.
Do not assume PipeWire-native, JACK compatibility, ALSA PCM, and desktop audio
routing are interchangeable. Document the chosen backend's setup and
limitations.
The engine should also have an offline/test mode that processes buffers without physical audio hardware. This makes unit and integration testing repeatable.
The engine should also have an offline/test mode that processes buffers without
physical audio hardware. This makes unit and integration testing repeatable.
---
## 8. MIDI, parameters, automation, and DSP
## 8. MIDI, Parameters, Automation, and DSP
### MIDI/events
### MIDI/Events
- Use a compact, well-defined event representation.
- Store event time relative to a project timeline and convert it to sample offsets for a processing block.
- Store event time relative to a project timeline and convert it to sample
offsets for a processing block.
- Validate event sizes and event counts.
- Handle note-on, note-off, velocity, channel, controller, pitch bend, and tempo/transport events as needed.
- Ensure all held notes can be released on stop, panic, seek, device loss, or project teardown.
- Handle note-on, note-off, velocity, channel, controller, pitch bend, and
tempo/transport events as needed.
- Ensure all held notes can be released on stop, panic, seek, device loss, or
project teardown.
- Avoid unbounded event queues.
### Parameters
Every automatable control should have:
- a stable parameter ID;
- a type and valid range;
- a default value;
@ -409,85 +494,115 @@ Every automatable control should have:
- a safe update mechanism;
- optional automation and MIDI mapping.
Do not silently clamp invalid serialized values. Validate project data and return an error or apply a clearly documented migration rule. UI controls may constrain user input, but core validation must remain authoritative.
Do not silently clamp invalid serialized values. Validate project data and
return an error or apply a clearly documented migration rule. UI controls may
constrain user input, but core validation must remain authoritative.
### Automation
Implement automation only after transport, graph processing, and basic parameter updates work. Define interpolation types explicitly (initially step and linear are enough). Automation evaluation must be deterministic and bounded.
Implement automation only after transport, graph processing, and basic parameter
updates work. Define interpolation types explicitly (initially step and linear
are enough). Automation evaluation must be deterministic and bounded.
### DSP
Start with a few well-tested processors:
- gain;
- pan;
- simple meter/peak detector;
- test oscillator;
- basic delay only after buffer/lifetime handling is sound.
Keep DSP algorithms in C and independently testable. Use appropriate numerical safeguards, including finite-value checks where needed. Avoid premature SIMD optimization until profiling shows a real bottleneck.
Keep DSP algorithms in C and independently testable. Use appropriate numerical
safeguards, including finite-value checks where needed. Avoid premature SIMD
optimization until profiling shows a real bottleneck.
---
## 9. FLTK/C++ GUI guidelines
## 9. FLTK/C++ GUI Guidelines
The GUI is a thin client of the C API, not the owner of the engine.
### Main views
1. **Sequencer:** tracks, audio/MIDI clips, timeline ruler, playhead, selection, zoom and horizontal scrolling.
2. **Mixer:** channels, faders, pan, mute/solo, inserts/sends when supported, and meters.
3. **Modular graph:** nodes, ports, connections, zoom/pan, selection, and connection validation feedback.
4. **Transport:** play, stop, record, tempo, meter, loop range, and current position.
5. **Project/file dialogs:** create, open, save, import audio, and manage missing assets.
1. **Sequencer:** tracks, audio/MIDI clips, timeline ruler, playhead, selection,
zoom and horizontal scrolling.
2. **Mixer:** channels, faders, pan, mute/solo, inserts/sends when supported,
and meters.
3. **Modular graph:** nodes, ports, connections, zoom/pan, selection, and
connection validation feedback.
4. **Transport:** play, stop, record, tempo, meter, loop range, and current
position.
5. **Project/file dialogs:** create, open, save, import audio, and manage
missing assets.
The default workspace should emphasize the sequencer and transport. The graph view is an advanced but first-class view of the same underlying project.
The default workspace should emphasize the sequencer and transport. The graph
view is an advanced but first-class view of the same underlying project.
### GUI implementation
### GUI Implementation
- Use FLTK for conventional widgets and application windows.
- Use custom-drawn widgets for the timeline, piano roll, mixer strips, waveform display, and graph canvas where needed.
- Use custom-drawn widgets for the timeline, piano roll, mixer strips, waveform
display, and graph canvas where needed.
- Keep drawing code separate from project mutation.
- Use GUI timers or the toolkit's appropriate event mechanism for refreshing meters; never poll from the audio callback or block the GUI waiting for audio.
- Use GUI timers or the toolkit's appropriate event mechanism for refreshing
meters; never poll from the audio callback or block the GUI waiting for audio.
- Avoid redrawing the entire application when only a small region changes.
- Support keyboard shortcuts and predictable focus behavior.
- Make selection, dragging, snapping, zooming, and scrolling consistent across editors.
- Make selection, dragging, snapping, zooming, and scrolling consistent across
editors.
- Never call FLTK APIs from the audio thread.
- Do not hold GUI locks while calling potentially blocking engine operations.
- Keep C++ simple: use RAII for GUI resources, clear ownership, and small classes. Do not force the C core into C++ abstractions.
- Keep C++ simple: use RAII for GUI resources, clear ownership, and small
classes. Do not force the C core into C++ abstractions.
### Visual direction
Aim for a compact, dark, high-contrast interface with restrained decoration. Prioritize legible waveforms, clip boundaries, transport state, and meter levels. Avoid excessive gradients, animations, nested panels, and modal configuration windows.
Aim for a compact, dark, high-contrast interface with restrained decoration.
Prioritize legible waveforms, clip boundaries, transport state, and meter
levels. Avoid excessive gradients, animations, nested panels, and modal
configuration windows.
Do not copy energyXT's artwork, exact visual assets, or proprietary UI resources. Recreate the general principles of compactness, direct manipulation, and modular routing in an original design.
Do not copy energyXT's artwork, exact visual assets, or proprietary UI
resources. Recreate the general principles of compactness, direct manipulation,
and modular routing in an original design.
---
## 10. Project format and persistence
## 10. Project Format and Persistence
The project format must be versioned from its first release.
### Requirements
- Include a format version and application version.
- Store stable IDs for tracks, nodes, ports where necessary, clips, parameters, and connections.
- Store stable IDs for tracks, nodes, ports where necessary, clips, parameters,
and connections.
- Save relative asset paths when possible.
- Define behavior for missing audio files.
- Validate every field before allocating large amounts of memory.
- Handle truncated, malformed, unsupported, or corrupted files gracefully.
- Never execute code while loading a project.
- Save atomically: write a temporary file, flush as appropriate, and rename it into place when safe.
- Preserve unknown optional fields when practical, or explicitly document when they are discarded.
- Save atomically: write a temporary file, flush as appropriate, and rename it
into place when safe.
- Preserve unknown optional fields when practical, or explicitly document when
they are discarded.
- Include migration functions when the format changes.
- Keep project serialization outside the real-time thread.
Choose a simple, well-supported format after considering the dependency and robustness trade-offs. A small, documented text format can help inspect projects, but large binary audio data should generally live in separate assets rather than be embedded in a text document. Do not add a serialization dependency without a clear reason.
Choose a simple, well-supported format after considering the dependency and
robustness trade-offs. A small, documented text format can help inspect
projects, but large binary audio data should generally live in separate assets
rather than be embedded in a text document. Do not add a serialization
dependency without a clear reason.
---
## 11. Undo/redo and editing commands
## 11. Undo/Redo and Editing Commands
Use explicit commands for user-visible edits, such as:
- create/delete/move/resize clip;
- create/delete/rename track;
- change parameter;
@ -495,21 +610,28 @@ Use explicit commands for user-visible edits, such as:
- connect/disconnect ports;
- change tempo or meter.
A command should validate its inputs and have a well-defined inverse or stored previous state. Avoid designing a generic event-sourcing framework before it is needed.
A command should validate its inputs and have a well-defined inverse or stored
previous state. Avoid designing a generic event-sourcing framework before it is
needed.
The initial implementation may begin without a complete undo stack, but design edit operations so undo/redo can be added without rewriting all editors. Never make an edit partially visible if it fails validation.
The initial implementation may begin without a complete undo stack, but design
edit operations so undo/redo can be added without rewriting all editors. Never
make an edit partially visible if it fails validation.
---
## 12. Build, coding standards, and dependencies
## 12. Build, Coding Standards, and Dependencies
### Build system
Use a straightforward Makefile unless the existing repository has a sound reason to use another build system.
Use a straightforward Makefile unless the existing repository has a sound reason
to use another build system.
The build must:
- compile C with the selected C standard and warnings enabled;
- compile C++ GUI sources with the standard supported by the chosen FLTK version;
- compile C++ GUI sources with the standard supported by the chosen FLTK
version;
- link the GUI with FLTK and the C core;
- link the required audio backend;
- provide `all`, `clean`, `test`, and a debug build target;
@ -517,9 +639,11 @@ The build must:
- avoid embedding machine-specific paths;
- separate generated objects and binaries into a build directory.
Use `pkg-config` where supported by the installed dependencies. Check actual package names on the target distribution rather than hard-coding unverified names.
Use `pkg-config` where supported by the installed dependencies. Check actual
package names on the target distribution rather than hard-coding unverified
names.
### C style
### C Style
- Use C99 initially.
- Use `snake_case`.
@ -532,7 +656,7 @@ Use `pkg-config` where supported by the installed dependencies. Check actual pac
- Use `const` where appropriate.
- Do not suppress warnings merely to get a clean build.
### C++ GUI style
### C++ GUI Style
- Keep C++ confined to the GUI and any unavoidable C++-only third-party wrapper.
- Prefer small classes with explicit ownership.
@ -544,21 +668,26 @@ Use `pkg-config` where supported by the installed dependencies. Check actual pac
### Dependencies
Before adding a dependency:
1. identify the exact feature it provides;
2. check license and maintenance status;
3. determine whether the existing code can provide the functionality simply and safely;
3. determine whether the existing code can provide the functionality simply and
safely;
4. document the dependency and its version constraints;
5. keep it optional if the feature is optional.
Prefer open-source dependencies with clear licensing. Do not claim the whole application is license-compatible until all direct and bundled dependencies have been reviewed.
Prefer open-source dependencies with clear licensing. Do not claim the whole
application is license-compatible until all direct and bundled dependencies have
been reviewed.
---
## 13. Testing and diagnostics
## 13. Testing and Diagnostics
### Unit tests
Test at least:
- graph validation and topological sorting;
- invalid ports and incompatible connections;
- cycle detection;
@ -573,6 +702,7 @@ Test at least:
### Integration tests
Test:
- test oscillator → gain → output processing;
- offline rendering with deterministic output;
- graph replacement during active processing;
@ -585,6 +715,7 @@ Test:
### Tools
Where available, use:
- compiler warnings;
- AddressSanitizer and UndefinedBehaviorSanitizer in non-real-time test builds;
- static analysis;
@ -592,25 +723,29 @@ Where available, use:
- stress tests for graph edits and shutdown;
- audio loopback or offline render comparisons.
Sanitizers and diagnostic logging should not be enabled in production real-time callbacks unless their real-time implications have been specifically evaluated.
Sanitizers and diagnostic logging should not be enabled in production real-time
callbacks unless their real-time implications have been specifically evaluated.
---
## 14. Development roadmap and acceptance criteria
## 14. Development Roadmap and Acceptance Criteria
Implement these milestones in order. Do not jump ahead merely because a later feature is exciting.
Implement these milestones in order. Do not jump ahead merely because a later
feature is exciting.
### Repository and design baseline
- Inspect the repository and available toolchain.
- Create or update architecture documentation and the Makefile.
- Verify C compiler, C++ compiler, FLTK, audio development headers, and test tools.
- Verify C compiler, C++ compiler, FLTK, audio development headers, and test
tools.
- Record dependency and licensing decisions.
- Add a minimal application that starts and exits cleanly.
**Acceptance:** clean build on the target Linux environment; no audio engine assumptions hidden in GUI code.
**Acceptance:** clean build on the target Linux environment; no audio engine
assumptions hidden in GUI code.
### C core and offline processing
### C Core and offline processing
- Create the C library/API.
- Define node, port, connection, and graph data structures.
@ -619,7 +754,8 @@ Implement these milestones in order. Do not jump ahead merely because a later fe
- Add oscillator, gain, meter, and output test nodes.
- Implement deterministic offline block processing.
**Acceptance:** automated test proves oscillator → gain → meter/output produces expected samples without hardware.
**Acceptance:** automated test proves oscillator → gain → meter/output produces
expected samples without hardware.
### Real-time backend
@ -629,16 +765,18 @@ Implement these milestones in order. Do not jump ahead merely because a later fe
- Add safe control-to-audio communication.
- Stress-test shutdown and graph replacement.
**Acceptance:** stable playback for a sustained test; no known callback allocations or blocking calls; failures are reported clearly.
**Acceptance:** stable playback for a sustained test; no known callback
allocations or blocking calls; failures are reported clearly.
### FLTK shell
### FLTK Shell
- Create main window, menus, transport, status area, and view switching.
- Call the C API through the public header.
- Add an audio engine status indicator.
- Implement clean startup and shutdown.
**Acceptance:** GUI can start/stop the engine, report backend errors, and close without leaks or hangs.
**Acceptance:** GUI can start/stop the engine, report backend errors, and close
without leaks or hangs.
### Modular graph editor
@ -648,16 +786,19 @@ Implement these milestones in order. Do not jump ahead merely because a later fe
- Display invalid connection feedback.
- Submit graph edits through validated C API commands.
**Acceptance:** graph shown by the GUI matches the graph used by the engine; invalid connections are rejected without corrupting active playback.
**Acceptance:** graph shown by the GUI matches the graph used by the engine;
invalid connections are rejected without corrupting active playback.
### Project and sequencer foundation
- Add project persistence and stable IDs.
- Add tracks and clip objects.
- Implement timeline rendering, selection, moving, trimming, zoom, and scrolling.
- Implement timeline rendering, selection, moving, trimming, zoom, and
scrolling.
- Add transport/sample-position synchronization.
**Acceptance:** save/load a project with clips and reproduce the same arrangement.
**Acceptance:** save/load a project with clips and reproduce the same
arrangement.
### Audio clips and recording
@ -666,16 +807,18 @@ Implement these milestones in order. Do not jump ahead merely because a later fe
- Keep file I/O outside the audio callback.
- Add missing-asset reporting.
**Acceptance:** record, save, reopen, and play a basic audio project without callback file I/O.
**Acceptance:** record, save, reopen, and play a basic audio project without
callback file I/O.
### MIDI and a simple instrument
### MIDI And a simple instrument
- Add MIDI input and clip events.
- Add a basic internal synthesizer or test instrument.
- Handle note-off, panic, seeking, and stop correctly.
- Add a minimal piano-roll editor only after event scheduling works.
**Acceptance:** record or draw MIDI notes, play them in time, save the project, and reopen it.
**Acceptance:** record or draw MIDI notes, play them in time, save the project,
and reopen it.
### Mixer, automation, and editing polish
@ -684,7 +827,8 @@ Implement these milestones in order. Do not jump ahead merely because a later fe
- Add a bounded undo/redo system for common edits.
- Improve keyboard interaction and workflow.
**Acceptance:** basic composition and mixing can be completed without editing project files manually.
**Acceptance:** basic composition and mixing can be completed without editing
project files manually.
### Extensibility and release preparation
@ -693,31 +837,40 @@ Implement these milestones in order. Do not jump ahead merely because a later fe
- Review project format migration, crash behavior, performance, and licensing.
- Package the application and document Linux setup.
**Acceptance:** reproducible release build, tested install/uninstall, clear limitations, and complete user/developer documentation.
**Acceptance:** reproducible release build, tested install/uninstall, clear
limitations, and complete user/developer documentation.
---
## 15. Initial implementation task for the agent
## 15. Initial Implementation Task for the Agent
Start with the **repository and design baseline** only.
1. Inspect the current repository and report its state.
2. Identify the available C/C++ compilers, FLTK version, audio backend development packages, `pkg-config`, and test tools.
2. Identify the available C/C++ compilers, FLTK version, audio backend
development packages, `pkg-config`, and test tools.
3. Do not install packages or modify system configuration without permission.
4. Propose the smallest initial source tree compatible with this brief.
5. Add or update the Makefile and documentation only as needed.
6. Create a minimal program that builds and exits cleanly; do not yet implement a full audio engine or GUI.
6. Create a minimal program that builds and exits cleanly; do not yet implement
a full audio engine or GUI.
7. Build it and run its tests.
8. Report exact commands, results, assumptions, and any missing dependencies.
After the baseline is reviewed, continue with the C core and offline processing. Keep each milestone independently buildable and testable.
After the baseline is reviewed, continue with the C core and offline processing.
Keep each milestone independently buildable and testable.
---
## 16. Long-term quality bar
## 16. Long-Term Quality Bar
The project succeeds if a musician can open it quickly, connect components without fighting the interface, record and arrange audio/MIDI, and make music without needing to understand every detail of the engine.
The project succeeds if a musician can open it quickly, connect components
without fighting the interface, record and arrange audio/MIDI, and make music
without needing to understand every detail of the engine.
The implementation succeeds if a developer can read the C code, understand ownership and thread boundaries, test components independently, and add functionality without turning the application into an unmaintainable framework.
The implementation succeeds if a developer can read the C code, understand
ownership and thread boundaries, test components independently, and add
functionality without turning the application into an unmaintainable framework.
**Guiding rule:** build a small, coherent system whose components compose well. Do not build a large DAW and then attempt to simplify it afterward.
**Guiding rule:** build a small, coherent system whose components compose well.
Do not build a large DAW and then attempt to simplify it afterward.

View file

@ -1,36 +1,42 @@
# Known limitations and qualification boundaries
# Known Limitations and Qualification Boundaries
- Development version 0.1.0, Linux C99/C++17/FLTK, optional JACK. Physical-device
playback/capture, human transition listening, independent user acceptance and
long-duration device qualification are pending.
- Development version 0.1.0, Linux C99/C++17/FLTK, optional JACK.
Physical-device playback/capture, human transition listening, independent user
acceptance and long-duration device qualification are pending.
- Fixed 4/4 and constant tempo snapshots; no tempo map, loop transport, plugin
latency compensation, limiter, background streaming or multichannel mixer buses.
latency compensation, limiter, background streaming or multichannel mixer
buses.
- Mono capture/import downmix; stereo track output. Graph ports are mono with
independent paired processing. Maximum 64 project graph nodes (256 standalone),
64 tracks, 1024 clips/automation points, 4096 scheduled notes, 256 live MIDI
events/block, 32 voices, 65536 frames/block. See API headers for exact bounds.
independent paired processing. Maximum 64 project graph nodes (256
standalone), 64 tracks, 1024 clips/automation points, 4096 scheduled notes,
256 live MIDI events/block, 32 voices, 65536 frames/block. See API headers for
exact bounds.
- WAV samples preload with the memory/format limits in AUDIO_FILES.md. Callback
overflow reports/silences; there is no CPU preemption or expensive-plugin rescue.
- Live MIDI configurable per-channel volume/pan/sustain CC mapping; no controller learn, capture, SMF
import, pitch bend, custom-instrument chooser or instrument timbre controls.
- Native ABI 1 graph nodes are session-only. ABI 2 state is parameter-only. LADSPA
supports the documented mono amp/inverter/sine set; no opaque state, native
editor, MIDI, true multichannel layout, latency compensation or crash isolation.
Automated LADSPA controls use run(1) and can be expensive.
overflow reports/silences; there is no CPU preemption or expensive-plugin
rescue.
- Live MIDI configurable per-channel volume/pan/sustain CC mapping; no
controller learn, capture, SMF import, pitch bend, custom-instrument chooser
or instrument timbre controls.
- Native ABI 1 graph nodes are session-only. ABI 2 state is parameter-only.
LADSPA supports the documented mono amp/inverter/sine set; no opaque state,
native editor, MIDI, true multichannel layout, latency compensation or crash
isolation. Automated LADSPA controls use run(1) and can be expensive.
- GUI loading/compilation is synchronous on the control thread. Timeline grid/
zoom/selection and graph positions are session UI state. Undo retains 32 edits
plus baseline and at most one reserved metadata snapshot; GUI edits reserve
history before mutation/publication. Media is
never removed by undo/delete. Broad screen-reader/accessibility testing is pending.
- Format v12 reads v1–v11 and uses relative local media references. External absolute
media/module paths need manual repair after relocation. Saving does not bundle
media; missing media stays referenced, missing plugins reject GUI installation.
Unknown data is rejected rather than stripped. Post-rename directory-sync errors
can report failure even though the complete new document was published.
- No autosave/session crash restoration, RF64, system service, automatic dependency
installation or public signed release. Source/binary archives are local artifacts;
binary packages require compatible system libraries. No third-party plugin or
dependency binary is bundled. Power-cut durability depends on the filesystem.
history before mutation/publication. Media is never removed by undo/delete.
Broad screen-reader/accessibility testing is pending.
- Format v12 reads v1–v11 and uses relative local media references. External
absolute media/module paths need manual repair after relocation. Saving does
not bundle media; missing media stays referenced, missing plugins reject GUI
installation. Unknown data is rejected rather than stripped. Post-rename
directory-sync errors can report failure even though the complete new document
was published.
- No autosave/session crash restoration, RF64, system service, automatic
dependency installation or public signed release. Source/binary archives are
local artifacts; binary packages require compatible system libraries. No
third-party plugin or dependency binary is bundled. Power-cut durability
depends on the filesystem.
See ROADMAP.md, AUDIO_QUALITY.md, CONTROL.md, WORKFLOW.md, PROJECT_FORMAT.md,
PERFORMANCE.md, PLUGIN_HOSTING.md and RECOVERY.md for tested contracts/evidence.
@ -46,43 +52,49 @@ PERFORMANCE.md, PLUGIN_HOSTING.md and RECOVERY.md for tested contracts/evidence.
- Stereo one-level mix buses share the 64-track limit. Nested buses, independent
bus plugin inserts, per-track aux sends and sidechains remain deferred. Track
meters are pre-bus; bus meters are pre-graph; master is post-graph. Sample peaks
and sticky clipping do not provide loudness, true-peak analysis or limiting.
meters are pre-bus; bus meters are pre-graph; master is post-graph. Sample
peaks and sticky clipping do not provide loudness, true-peak analysis or
limiting.
- Plugin discovery is an explicit-file descriptor browser, not a directory scan.
Generic parameter dialogs own copied metadata; no native plugin editors or
custom MIDI instrument chooser. LADSPA units are unspecified. Replacement
requires the same mono audio layout and resets controls/automation. Missing
modules reject Open while retaining the current project; GUI module relink for
unopened documents is deferred. In-process plugin crashes/hangs are uncontained.
unopened documents is deferred. In-process plugin crashes/hangs are
uncontained.
- Common parameter targets cover track/bus gain/pan, LADSPA controls and metadata
for live sine volume/pan/sustain. Live mappings do not drive arranged lanes,
faders or plugins. No automation capture, touch/latch/write modes or loop
transport. Backward jumps are deterministic for lanes, not opaque plugin tails.
- Common parameter targets cover track/bus gain/pan, LADSPA controls and
metadata for live sine volume/pan/sustain. Live mappings do not drive arranged
lanes, faders or plugins. No automation capture, touch/latch/write modes or
loop transport. Backward jumps are deterministic for lanes, not opaque plugin
tails.
- Editing feedback remains synchronous; busy cursors are not background loading
or cancellable progress. Surviving selections are retained across undo but
historical selection/layout is not stored. Native context-menu/focus manual
qualification and broad accessibility testing remain pending. See EDITING.md.
Project diagnostics use syntax-line locations; semantic cross-reference
failures identify the MODEL stage without a source line. `xtc-check --inspect`
checks dependency file availability only, without loading code or WAV contents.
The project format is Linux/POSIX oriented; absolute plugin paths, architecture,
ABI and JACK/device configuration must be supplied on another system. Unknown
fields are rejected, not preserved. See PROJECT_FORMAT.md for versions 1–10 and
Project diagnostics use syntax-line locations; semantic cross-reference failures
identify the MODEL stage without a source line. `xtc-check --inspect` checks
dependency file availability only, without loading code or WAV contents. The
project format is Linux/POSIX oriented; absolute plugin paths, architecture, ABI
and JACK/device configuration must be supplied on another system. Unknown fields
are rejected, not preserved. See PROJECT_FORMAT.md for versions 1–10 and
RECOVERY.md for save failures after rename.
CLAP/VST2/VST3 loading supports Linux binary modules, one-input mono
float32 zero-latency effects at default configuration. No modern parameter edit,
CLAP/VST2/VST3 loading supports Linux binary modules, one-input mono float32
zero-latency effects at default configuration. No modern parameter edit,
automation, programs, opaque state, MIDI/instruments, transport, custom editor,
workers, sidechains or true stereo/multichannel buses are implemented. Plugin
bundles must be opened at their actual Linux shared-library binary. Only original
ABI fixtures are qualified; arbitrary third-party modern plugins remain untested.
See PLUGIN_HOSTING.md and decision 0010 for lifecycle and licensing boundaries.
bundles must be opened at their actual Linux shared-library binary. Only
original ABI fixtures are qualified; arbitrary third-party modern plugins remain
untested. See PLUGIN_HOSTING.md and decision 0010 for lifecycle and licensing
boundaries.
## Instrument support
---
## Instrument Support
MIDI tracks can bind CLAP, VST2 and VST3 instruments. In the Track menu, select
**Load instrument plugin** for the selected sequencer track; **Use built-in
@ -93,9 +105,9 @@ their own track's instrument. Independent instances feed track gain/pan,
mute/solo, bus routing and automation. Bindings/live target save in v12.
Supply the actual trusted Linux binary (including the inner .so of a VST3
bundle). Supported instruments have zero audio inputs, one mono output,
float32 processing, zero latency and one note/event input. Default configuration
only: no custom editor, opaque state/preset restoration, modern parameter
bundle). Supported instruments have zero audio inputs, one mono output, float32
processing, zero latency and one note/event input. Default configuration only:
no custom editor, opaque state/preset restoration, modern parameter
editing/automation, stereo/multichannel instruments or MIDI output. No arbitrary
third-party instrument is qualified by the original ABI fixture tests.
@ -113,19 +125,18 @@ the callback and old instances are reclaimed on the control thread. Missing
references remain inspectable without loading; failed GUI preparation retains
the current project. Save a backup for applications limited to v11 or earlier.
### Track insert chains
Track chains contain at most eight mono effects. Modern adapters require zero
reported latency, use default configuration and do not provide native plugin
windows, parameter editing or opaque state persistence. A bounded VST3 output
parameter sink retains the final notification per parameter per block (64
parameters); these notifications are not recorded or exposed as controls.
Track edits stop transport before candidate preparation. Bus inserts and tracks
with both audio assets and MIDI reject preparation. Unassigned live sine
monitoring stays on the master input. Stereo insert layouts, latency
compensation, sandboxed scanning and gapless live insert edits remain future
work. Internal history resets on discontinuities; third-party history follows
the existing host contract. Presets retain absolute plugin references, so
compatible binaries must be installed at the same paths on another machine.
The smaller optional inspector's visibility is session-local.
parameters); these notifications are not recorded or exposed as controls. Track
edits stop transport before candidate preparation. Bus inserts and tracks with
both audio assets and MIDI reject preparation. Unassigned live sine monitoring
stays on the master input. Stereo insert layouts, latency compensation,
sandboxed scanning and gapless live insert edits remain future work. Internal
history resets on discontinuities; third-party history follows the existing host
contract. Presets retain absolute plugin references, so compatible binaries must
be installed at the same paths on another machine. The smaller optional
inspector's visibility is session-local.

View file

@ -1,13 +1,14 @@
# Licensing and dependency review
# Licensing and Dependency Review
The repository's original code, example and documentation are under the MIT
license in LICENSE (copyright Johannes Findeisen). No energyXT source, artwork,
proprietary SDK, vendored DSP or plugin binary is included. Permissively licensed
ABI headers are vendored with their own notices. The source archive
proprietary SDK, vendored DSP or plugin binary is included. Permissively
licensed ABI headers are vendored with their own notices. The source archive
contains repository files and an export manifest; it does not bundle system
libraries. Installation includes LICENSE and these documents.
The host review inspected installed package metadata and local license files. Package-level license sets can include utilities/docs that differ from the
The host review inspected installed package metadata and local license files.
Package-level license sets can include utilities/docs that differ from the
specific library being linked; retain the relevant upstream notices when
redistributing dependencies. The default install uses system shared libraries.
@ -27,10 +28,10 @@ redistributing dependencies. The default install uses system shared libraries.
| D-Bus | AFL-2.1 OR GPL-2.0-or-later. |
| Python 3 | PSF-2.0; optional build/test/package scripts use only the standard library. Not an application runtime dependency. |
GNU Make, GCC/Clang, binutils, pkg-config and optional Xvfb/xdotool/sanitizers are
build/test tools, not shipped dependencies. New Python tooling needs Python 3.12+
for safe tar extraction in release tests; this host has Python 3.14.8. No packages
were installed and no application dependency was added.
GNU Make, GCC/Clang, binutils, pkg-config and optional Xvfb/xdotool/sanitizers
are build/test tools, not shipped dependencies. New Python tooling needs Python
3.12+ for safe tar extraction in release tests; this host has Python 3.14.8. No
packages were installed and no application dependency was added.
Binary redistributors must inspect their own `readelf -d`/`ldd` dependency set
and supply the notices/source/relinking facilities required by those exact
@ -38,7 +39,9 @@ licenses and linking choices. FLTK's exception does not remove obligations of
other linked libraries. This review covers the current unmodified system-linked
build; static all-in-one bundles and external plugins need a separate review.
## LADSPA review
---
## LADSPA Review
The optional external host includes the system LADSPA 1.1 header (`ladspa`
1.17-7), copyright Richard W.E. Furse, Paul Barton-Davis and Stefan Westerfeld,
@ -50,12 +53,15 @@ The original fixture in tests/plugins is MIT repository code.
Acceptance dynamically loads the user's installed LADSPA sample amp/sine modules
(package metadata LGPL-2.1-or-later) and SWH inverter (swh-plugins 0.4.17-7,
GPL-2.0-or-later). Testing/loading a user-installed module does not grant the right
to bundle or relicense it. Redistributors must review each chosen module and the
combined distribution's obligations separately; xtc's MIT grant covers only its
original code. No third-party plugin is included in source archives or installs.
GPL-2.0-or-later). Testing/loading a user-installed module does not grant the
right to bundle or relicense it. Redistributors must review each chosen module
and the combined distribution's obligations separately; xtc's MIT grant covers
only its original code. No third-party plugin is included in source archives or
installs.
## Pinned header dependencies
---
## Pinned Header Dependencies
CLAP headers are MIT, copyright Alexandre BIQUE, from upstream commit
`a47f6badb49d948fd009998f28309cdab78979c9`. The generated VST3 C ABI header is
@ -63,7 +69,8 @@ BSD-3-Clause, copyright Steinberg Media Technologies GmbH, from commit
`93a116f7c6c52a317ec3aaee9b13789068b5234e`. Exact licenses are retained under
third_party and installed as CLAP-LICENSE / VST3-C-API-LICENSE. These are header
specifications only: no external runtime SDK or network build step was added.
Review updates explicitly; do not assume future revisions share the same license.
Review updates explicitly; do not assume future revisions share the same
license.
The VST2 compatibility declarations and all three ABI fixtures are original MIT
repository code; no Steinberg VST2 SDK or third-party plugin is bundled. Hosting

View file

@ -1,10 +1,11 @@
# Linux setup and installation
# Linux Setup and Installation
Use a C99 compiler, C++17 compiler for the GUI, GNU Make, binutils and pkg-config.
The core needs only the C/math runtime. FLTK 1.4 development files enable the GUI;
JACK-compatible development files enable the optional audio/MIDI backend.
Packaging/install tests additionally use Python 3.12+ (standard library only).
No command starts a server, installs dependencies or changes system settings.
Use a C99 compiler, C++17 compiler for the GUI, GNU Make, binutils and
pkg-config. The core needs only the C/math runtime. FLTK 1.4 development files
enable the GUI; JACK-compatible development files enable the optional audio/MIDI
backend. Packaging/install tests additionally use Python 3.12+ (standard library
only). No command starts a server, installs dependencies or changes system
settings.
```sh
make all test
@ -15,20 +16,24 @@ make gui
Start/configure a JACK or PipeWire JACK-compatible server through your existing
system setup first. xtc opens without starting one and requires reconnect after
server loss/rate changes. In a separate JACK routing tool connect desired MIDI
to `midi_in`, audio capture to `input`, and `output`/`output_right` to left/right
playback ports. No physical routes are automatic. Input/recording remain mono.
See GUI.md, AUDIO_FILES.md, MIDI.md and MIXER.md for composing/recording/editing.
to `midi_in`, audio capture to `input`, and `output`/`output_right` to
left/right playback ports. No physical routes are automatic. Input/recording
remain mono. See GUI.md, AUDIO_FILES.md, MIDI.md and MIXER.md for
composing/recording/editing.
`xtc --help` and `xtc --version` work without a display/server. `xtc project.xtc`
opens one project; no-file startup retains FLTK options such as `-display`.
`xtc-check --validate project.xtc` validates metadata only; native graphs cannot
be serialized. See RECOVERY.md before replacing damaged files.
`xtc --help` and `xtc --version` work without a display/server.
`xtc project.xtc` opens one project; no-file startup retains FLTK options such
as `-display`. `xtc-check --validate project.xtc` validates metadata only;
native graphs cannot be serialized. See RECOVERY.md before replacing damaged
files.
## Staged installation
---
Core-only installation retains the original `make install` behavior and now
adds xtc-check, pkg-config metadata, license and documentation. install-gui adds
the GUI executable and desktop launcher. Defaults to PREFIX=/usr/local:
## Staged Installation
Core-only installation retains the original `make install` behavior and now adds
xtc-check, pkg-config metadata, license and documentation. install-gui adds the
GUI executable and desktop launcher. Defaults to PREFIX=/usr/local:
```sh
make install-gui DESTDIR="$PWD/build/stage" PREFIX=/usr
@ -57,28 +62,33 @@ file records its private dependency. Public headers contain no JACK/FLTK types.
To build without optional libraries, set PKG_CONFIG=false FLTK_CONFIG=false.
Python is not needed to run the installed executables or library.
## Binary/development archive
---
## Binary/Development Archive
`make package` creates a system-linked Linux archive plus SHA-256 sidecar below
BUILD_DIR; `make test-package` verifies its contents. It includes usr/bin,
headers/static library/pkg-config metadata, desktop file (when FLTK is present),
manuals, baseline and MIT license. No shared library, third-party plugin, media,
service or dependency installer is bundled. `BINARY_MANIFEST.json` lists file
hashes/sizes, architecture/libc and ELF NEEDED libraries. The entire archive's hash
covers the manifest too. Do not assume compatibility with another distribution.
hashes/sizes, architecture/libc and ELF NEEDED libraries. The entire archive's
hash covers the manifest too. Do not assume compatibility with another
distribution.
Inspect/test under a temporary directory first. For a writable user prefix,
extract the archive with two leading path components stripped (`xtc-VERSION-linux-
ARCH/usr`), placing bin/include/lib/share directly under that prefix. Put its bin
on PATH and lib/pkgconfig on PKG_CONFIG_PATH. The archive's pkg-config prefix is
relative to its installed location; normal `make install` retains explicit PREFIX.
Use the matching source's uninstall with that PREFIX, or review the manifest
before removing its files. Unknown/unrelated files are retained by source uninstall.
This local tar package is not a pacman/RPM/deb registration or public distribution.
extract the archive with two leading path components stripped
(`xtc-VERSION-linux- ARCH/usr`), placing bin/include/lib/share directly under
that prefix. Put its bin on PATH and lib/pkgconfig on PKG_CONFIG_PATH. The
archive's pkg-config prefix is relative to its installed location; normal
`make install` retains explicit PREFIX. Use the matching source's uninstall with
that PREFIX, or review the manifest before removing its files. Unknown/unrelated
files are retained by source uninstall. This local tar package is not a
pacman/RPM/deb registration or public distribution.
`xtc-check --diagnose` reports build capabilities and format/engine limits without
opening a server or executing a plugin. `xtc --diagnose` works headlessly as well.
Missing runtime GUI libraries are loader errors: install the distribution's
compatible dependencies or rebuild the core-only configuration. Build without
optional libraries using a fresh directory, `PKG_CONFIG=false FLTK_CONFIG=false
CPPFLAGS=-DXTC_DISABLE_LADSPA`. No system setting or package is changed by tests.
`xtc-check --diagnose` reports build capabilities and format/engine limits
without opening a server or executing a plugin. `xtc --diagnose` works
headlessly as well. Missing runtime GUI libraries are loader errors: install the
distribution's compatible dependencies or rebuild the core-only configuration.
Build without optional libraries using a fresh directory,
`PKG_CONFIG=false FLTK_CONFIG=false CPPFLAGS=-DXTC_DISABLE_LADSPA`. No system
setting or package is changed by tests.

View file

@ -1,45 +1,53 @@
# Release and maintenance policy
# Release and Maintenance Policy
## Supported development target
## Supported Development Target
Initial target: x86_64 Artix/Arch-family Linux with glibc, GNU Make, GCC/Clang,
FLTK 1.4 and a JACK-compatible client/server. The inspected host uses GCC 16.2.1,
FLTK 1.4.5 and the dependencies in LICENSES.md. Core-only/no-JACK/no-LADSPA builds
are checked too. Build on the destination distribution for different library ABIs;
there is no cross-distribution static-binary promise. Python 3.12+ and binutils
are build/package/test tools only. No dependency is fetched automatically.
FLTK 1.4 and a JACK-compatible client/server. The inspected host uses GCC
16.2.1, FLTK 1.4.5 and the dependencies in LICENSES.md.
Core-only/no-JACK/no-LADSPA builds are checked too. Build on the destination
distribution for different library ABIs; there is no cross-distribution
static-binary promise. Python 3.12+ and binutils are build/package/test tools
only. No dependency is fetched automatically.
## Release checklist
---
## Release Checklist
1. Work from a clean reviewed commit. Record `git rev-parse HEAD`, compiler,
libc/FLTK/JACK versions and `xtc-check --diagnose`. No automatic version bump,
Git tag, public upload or external release is performed by local targets.
2. Run warning-enabled `make all test gui test-plugins test-plugins-real
test-gui-plugins test-workflow test-gui-jack test-jack-midi test-recording
test-recovery test-reliability test-scale analyze` where dependencies exist.
libc/FLTK/JACK versions and `xtc-check --diagnose`. No automatic version
bump, Git tag, public upload or external release is performed by local
targets.
2. Run warning-enabled
`make all test gui test-plugins test-plugins-real test-gui-plugins test-workflow test-gui-jack test-jack-midi test-recording test-recovery test-reliability test-scale analyze`
where dependencies exist.
3. Use a separate BUILD_DIR for ASan/UBSan/leak runs and another for optional-
dependency builds. GUI leak checks use only the documented fontconfig
suppression; core tests use none. Run publication/recording race tests as
described in RELIABILITY.md. Sanitizers are not latency qualification.
4. Run `make benchmark test-performance test-stress`; compare on matching
host/compiler/flags against a reviewed baseline. Investigate regressions;
do not weaken thresholds or replace evidence without explanation.
host/compiler/flags against a reviewed baseline. Investigate regressions; do
not weaken thresholds or replace evidence without explanation.
5. Run `make test-install test-release test-package`. Inspect archive manifests,
hashes, dynamic dependency names and licenses. Source exports rebuild twice
with identical artifacts on this host. Repeat binary packaging compares bytes.
6. Complete physical listening, recording/device/endurance, and independent human
install/workflow checks before claiming production qualification. They remain
pending for this prototype, even when all automated checks pass.
with identical artifacts on this host. Repeat binary packaging compares
bytes.
6. Complete physical listening, recording/device/endurance, and independent
human install/workflow checks before claiming production qualification. They
remain pending for this prototype, even when all automated checks pass.
7. Save checksums/test logs/conditions with the proposed release. Review known
limitations and format compatibility and update user/developer docs. Publishing
or distro repository submission is a separate action.
limitations and format compatibility and update user/developer docs.
Publishing or distro repository submission is a separate action.
## Regressions and compatibility
---
## Regressions and Compatibility
Report the exact version/commit, OS/toolchain, compiled features, reproduction,
expected/actual output, GUI errors/xruns and whether plugins are involved. Keep a
minimal project/media fixture when redistribution is permitted; do not attach
private recordings or full memory dumps inadvertently. No telemetry/upload occurs.
expected/actual output, GUI errors/xruns and whether plugins are involved. Keep
a minimal project/media fixture when redistribution is permitted; do not attach
private recordings or full memory dumps inadvertently. No telemetry/upload
occurs.
A confirmed defect gets a focused deterministic regression or repeatable runtime
check before its fix. Memory/thread/file-loss defects take priority; disable the
@ -47,22 +55,25 @@ unsafe path rather than silently dropping media or unknown project records. Keep
candidate preparation/plan publication transactional and callbacks bounded.
Format changes increment XTC_PROJECT_FORMAT_VERSION, retain explicit bounded
migrations/fixtures for all supported older versions, and add malformed/roundtrip
checks. Unknown versions/types/records fail with actionable errors. Never rewrite
an unsupported source file during failed Open. Keep the original when diagnosing
post-publication sync errors. Native descriptor ABI compatibility is distinct from
project format and is documented in NATIVE_API.md; opaque DSP state is not promised.
No public stability guarantee or support SLA is implied by development version 0.1.0.
migrations/fixtures for all supported older versions, and add
malformed/roundtrip checks. Unknown versions/types/records fail with actionable
errors. Never rewrite an unsupported source file during failed Open. Keep the
original when diagnosing post-publication sync errors. Native descriptor ABI
compatibility is distinct from project format and is documented in
NATIVE_API.md; opaque DSP state is not promised. No public stability guarantee
or support SLA is implied by development version 0.1.0.
## Support and triage record
---
## Support and Triage Record
Verified compilers are GCC/G++ 16.2.1 and Clang/Clang++ 23.1.3 on x86_64 Linux;
these are tested versions, not a promise that arbitrary older/newer versions work.
The code requires C99 plus GCC/Clang lock-free atomics, C++17 for the GUI, FLTK
1.4 and POSIX/glibc APIs. JACK (including PipeWire's JACK compatibility) is the
only live backend. Native Windows/macOS, other architectures and direct ALSA
backends are not qualified. Core builds can disable JACK, FLTK and LADSPA.
See BUILD.md and LICENSES.md for dependency roles and versions.
these are tested versions, not a promise that arbitrary older/newer versions
work. The code requires C99 plus GCC/Clang lock-free atomics, C++17 for the GUI,
FLTK 1.4 and POSIX/glibc APIs. JACK (including PipeWire's JACK compatibility) is
the only live backend. Native Windows/macOS, other architectures and direct ALSA
backends are not qualified. Core builds can disable JACK, FLTK and LADSPA. See
BUILD.md and LICENSES.md for dependency roles and versions.
Release qualification adds `test-install-layout`, installed example inspection,
all performance report resources, source checksum verification and the current
@ -70,11 +81,12 @@ RELEASE_NOTES.md. Retain the original graph benchmark alert in release records;
a successful compile does not clear timing qualification gaps.
File reproducible bugs with the repository maintainers using the source commit
and archive SHA-256, `xtc-check --version`/`--diagnose`, distro/kernel and toolchain,
exact commands or GUI steps, expected versus observed behavior and frequency.
Reduce to a redistributable project and state whether it reproduces without
external plugins and with the offline/core tools. Include relevant stderr and
sanitizer traces; preserve original recovery files before experimenting.
and archive SHA-256, `xtc-check --version`/`--diagnose`, distro/kernel and
toolchain, exact commands or GUI steps, expected versus observed behavior and
frequency. Reduce to a redistributable project and state whether it reproduces
without external plugins and with the offline/core tools. Include relevant
stderr and sanitizer traces; preserve original recovery files before
experimenting.
Maintainers should first confirm version/features and reproduce in an isolated
build. Prioritize data loss/security/memory/thread defects, then crashes and

View file

@ -1,49 +1,57 @@
# MIDI clips and instrument
# MIDI Clips and Instrument
Drawing persisted notes and live JACK MIDI input are supported. Record still
captures audio to WAV: MIDI capture and Standard MIDI File import are deferred.
---
## Workflow
Add/select a track in Sequencer, choose Add MIDI clip, then Piano roll (Ctrl+4).
Creation adds an instrument-to-output route if no instrument exists. Existing
instruments retain their routes; use Modular Graph to connect them as needed.
Scheduled MIDI clips now apply their track mix/automation before summing to
paired stereo instrument buffers; live input stays centered and unassigned. No hardware ports
are connected automatically. Connect a MIDI source to the backend's `midi_in`
JACK port, and route audio output explicitly to hear it while Play is running.
paired stereo instrument buffers; live input stays centered and unassigned. No
hardware ports are connected automatically. Connect a MIDI source to the
backend's `midi_in` JACK port, and route audio output explicitly to hear it
while Play is running.
The piano roll shows all 128 pitches and edits clip-source time. Click blank
space to draw a one-beat note (velocity 100, filtered channel or channel 0); drag its body to move
it, drag the right edge to resize, or right-click/Delete to remove it. Edits
snap to the configured grid (default 1/16). Escape cancels a drag. Trimmed-out source notes remain editable
and are preserved. Arrangement/note edits stop playback and are blocked during
audio recording. Save/Open preserves MIDI flags, notes and stable IDs.
space to draw a one-beat note (velocity 100, filtered channel or channel 0);
drag its body to move it, drag the right edge to resize, or right-click/Delete
to remove it. Edits snap to the configured grid (default 1/16). Escape cancels a
drag. Trimmed-out source notes remain editable and are preserved.
Arrangement/note edits stop playback and are blocked during audio recording.
Save/Open preserves MIDI flags, notes and stable IDs.
## Timing and ownership
---
The C model owns up to 4096 notes per project, with source-relative ticks at
960 ticks/quarter and constant tempo. Control-thread preparation intersects
notes with clip trims and converts boundaries to integer sample positions.
Scheduled rendering uses absolute note age: block partitions do not change
samples, and seeking into a note chases its phase. Clip ends cut notes off.
The instrument source has its own buffer, separate from external audio input
and WAV timeline playback, and routes through the ordinary graph.
## Timing and Ownership
The C model owns up to 4096 notes per project, with source-relative ticks at 960
ticks/quarter and constant tempo. Control-thread preparation intersects notes
with clip trims and converts boundaries to integer sample positions. Scheduled
rendering uses absolute note age: block partitions do not change samples, and
seeking into a note chases its phase. Clip ends cut notes off. The instrument
source has its own buffer, separate from external audio input and WAV timeline
playback, and routes through the ordinary graph.
Prepared ranges are immutable; only their audio owner changes voice/panic state.
Preparation allocates; rendering uses bounded preallocated storage and performs
no allocation, freeing, I/O, locks, logging or GUI calls. Project preparation
builds audio and MIDI candidates before committing the stopped backend state.
JACK events have sample offsets within the current block. Supported messages
are complete note on/off (including velocity-zero note on) and CC120/123 on
channels 0..15, plus CC7 volume, CC10 pan and CC64 sustain (see CONTROL.md).
Equal-offset events retain input order. Bend, program changes, running status
and SysEx are unsupported.
At most 256 events are accepted per block. Malformed/overflowed input discards
that block's events and triggers panic, with a MIDI-drop status counter.
JACK events have sample offsets within the current block. Supported messages are
complete note on/off (including velocity-zero note on) and CC120/123 on channels
0..15, plus CC7 volume, CC10 pan and CC64 sustain (see CONTROL.md). Equal-offset
events retain input order. Bend, program changes, running status and SysEx are
unsupported. At most 256 events are accepted per block. Malformed/overflowed
input discards that block's events and triggers panic, with a MIDI-drop status
counter.
## Recovery and limits
---
## Recovery and Limits
Panic/F9 clears live voices and suppresses scheduled notes already started;
future timeline notes can still start. CC120/123 does this for its channel.
@ -52,15 +60,17 @@ notes to be chased again. MIDI port disconnection, xruns and callback processing
failures request panic. Control requests use lock-free generation counters;
voice state changes on the audio thread, or while stopped.
The sine instrument scales amplitude by 0.1 times velocity/127. It supports
32 simultaneous scheduled notes and a separate 32 live voices; live overflow
steals the oldest voice, and repeated channel/pitch retriggers it. Preparation
rejects more than 32 overlapping scheduled notes with CAPACITY. Such metadata
can be saved by the C model, but GUI Open/Play rejects it until overlap is
reduced. Immediate note-off/stealing can click; there is no envelope or limiter,
and summed voices can exceed unity. Above-Nyquist pitches are silent.
There is no per-track/channel instrument routing, MIDI recording, SMF import
or piano-roll zoom yet. Note edits participate in project snapshot undo/redo.
The sine instrument scales amplitude by 0.1 times velocity/127. It supports 32
simultaneous scheduled notes and a separate 32 live voices; live overflow steals
the oldest voice, and repeated channel/pitch retriggers it. Preparation rejects
more than 32 overlapping scheduled notes with CAPACITY. Such metadata can be
saved by the C model, but GUI Open/Play rejects it until overlap is reduced.
Immediate note-off/stealing can click; there is no envelope or limiter, and
summed voices can exceed unity. Above-Nyquist pitches are silent. There is no
per-track/channel instrument routing, MIDI recording, SMF import or piano-roll
zoom yet. Note edits participate in project snapshot undo/redo.
---
## Verification
@ -70,13 +80,15 @@ malformed files, plus callback fault recovery. `make test-gui` draws, moves and
resizes notes with FLTK events, then saves/reopens and renders them.
`make test-jack-midi` uses only software MIDI/audio ports to verify live on/off,
panic, disconnection and saved/reopened scheduled playback. JACK port discovery
may lag activation under PipeWire; the test waits before connecting.
Physical MIDI devices, bridge/device loss behind an unchanged port, latency and
may lag activation under PipeWire; the test waits before connecting. Physical
MIDI devices, bridge/device loss behind an unchanged port, latency and
long-running performance remain unverified. Panic/Stop provides manual recovery.
Track mixing, automation and project undo/redo are available; see MIXER.md.
## Composition commands
---
## Composition Commands
The piano-roll **Notes** menu exposes these commands. Shortcuts apply while the
piano roll has focus; use the main window Edit menu for undo/redo.
@ -92,22 +104,22 @@ piano roll has focus; use the main window Edit menu for undo/redo.
| Select filtered | Ctrl+A | Select every note matching the current clip filter |
| Delete | Delete | Remove the single selected note or all filtered notes |
160 ticks is a sixteenth-note triplet; 320 is an eighth-note triplet.
The grid governs drawing, drag movement and minimum resize duration. At 1-tick
snap the visual grid remains 120 ticks to stay legible at the fixed zoom.
The header shows current grid, channel/pitch filter and selection scope.
Velocity also affects unselected note brightness. Preferences are session-only.
160 ticks is a sixteenth-note triplet; 320 is an eighth-note triplet. The grid
governs drawing, drag movement and minimum resize duration. At 1-tick snap the
visual grid remains 120 ticks to stay legible at the fixed zoom. The header
shows current grid, channel/pitch filter and selection scope. Velocity also
affects unselected note brightness. Preferences are session-only.
Click selects one visible note; Ctrl+A selects the whole filtered set. A click
or filter change clears that set selection. Hidden notes cannot be mouse-edited.
A channel filter also sets the channel for newly drawn notes. No lasso or
arbitrary disjoint multi-selection is provided. Moving/resizing uses the mouse;
V edits velocity/channel without changing timing, pitch or IDs. Quantization
changes starts only, preserving durations and other properties. Repetition
keeps originals and gives every copy a fresh stable ID; spacing need not equal
note length, so both overlapping patterns and rests are possible. The existing
clip source must have room for every result; extend it in Sequencer first if
needed. Source notes outside the visible arrangement trim remain editable.
changes starts only, preserving durations and other properties. Repetition keeps
originals and gives every copy a fresh stable ID; spacing need not equal note
length, so both overlapping patterns and rests are possible. The existing clip
source must have room for every result; extend it in Sequencer first if needed.
Source notes outside the visible arrangement trim remain editable.
All batch edits validate before mutation. Invalid pitch/range, copy counts,
capacity overflow or a note crossing the source boundary rejects the entire
@ -120,24 +132,27 @@ These edits use the existing persisted note fields; format remains v7.
Scheduled notes occupy [start,end), with ends counted before starts at an equal
sample for the 32-voice limit. Overlapping same-channel/pitch scheduled notes
are independent sine voices; adjacent repetitions restart phase. Live repeated
notes instead retrigger the matching channel/pitch voice, and a note-off releases
that voice (or defers release under sustain). Equal-sample live on/off/control
messages execute in caller/JACK order, with no implicit priority reordering.
The renderer rejects malformed/unsorted/out-of-range events before touching
output or runtime; raw velocity-zero note-ons decode to note-off.
notes instead retrigger the matching channel/pitch voice, and a note-off
releases that voice (or defers release under sustain). Equal-sample live
on/off/control messages execute in caller/JACK order, with no implicit priority
reordering. The renderer rejects malformed/unsorted/out-of-range events before
touching output or runtime; raw velocity-zero note-ons decode to note-off.
`make test-composition` covers filters, property preservation, triplet rounding,
repeats/fresh IDs, rejected batch rollback, capacity, undo snapshots, equal-offset
live order, adjacent/overlapping notes, exact block partitions and seek chase.
`make test-gui` covers mouse edits and the new commands with undo/redo and engine
reinstallation. Listening and physical MIDI-device validation remain pending.
repeats/fresh IDs, rejected batch rollback, capacity, undo snapshots,
equal-offset live order, adjacent/overlapping notes, exact block partitions and
seek chase. `make test-gui` covers mouse edits and the new commands with
undo/redo and engine reinstallation. Listening and physical MIDI-device
validation remain pending.
CC7/10/64 are defaults, configurable per channel through Audio → MIDI controller
mapping. Disabled/unmapped CCs have no effect; CC120/123 stay panic.
Decode accepts valid CC0..127 messages within the existing event budget. See
CONTROL.md for the exact bindings, ramp, sustain and automation precedence rules.
mapping. Disabled/unmapped CCs have no effect; CC120/123 stay panic. Decode
accepts valid CC0..127 messages within the existing event budget. See CONTROL.md
for the exact bindings, ramp, sustain and automation precedence rules.
## Instrument support
---
## Instrument Support
MIDI tracks can bind CLAP, VST2 and VST3 instruments. In the Track menu, select
**Load instrument plugin** for the selected sequencer track; **Use built-in
@ -148,9 +163,9 @@ their own track's instrument. Independent instances feed track gain/pan,
mute/solo, bus routing and automation. Bindings/live target save in v12.
Supply the actual trusted Linux binary (including the inner .so of a VST3
bundle). Supported instruments have zero audio inputs, one mono output,
float32 processing, zero latency and one note/event input. Default configuration
only: no custom editor, opaque state/preset restoration, modern parameter
bundle). Supported instruments have zero audio inputs, one mono output, float32
processing, zero latency and one note/event input. Default configuration only:
no custom editor, opaque state/preset restoration, modern parameter
editing/automation, stereo/multichannel instruments or MIDI output. No arbitrary
third-party instrument is qualified by the original ABI fixture tests.
@ -168,14 +183,17 @@ the callback and old instances are reclaimed on the control thread. Missing
references remain inspectable without loading; failed GUI preparation retains
the current project. Save a backup for applications limited to v11 or earlier.
## Note dynamics and clip editing
---
## Note Dynamics and Clip Editing
Double-click a MIDI clip to open its piano roll. The Note velocity slider and
fixed Velocity lane edit individual notes from 1 to 127, preserving timing,
pitch and channel. Chord notes at the same start can be selected individually in
the keys before editing their highlighted bar. Completed gestures are undoable
and save in the existing note velocity field. Velocity is not a separate dB
volume stage: the built-in sine scales amplitude, while plugins choose their
own patch-dependent response. The track header instrument dropdown reuses saved
project instruments or opens explicit loading; bus tracks cannot host instruments.
See WORKSPACE.md for grid/snap divisions, filters and control behavior.
volume stage: the built-in sine scales amplitude, while plugins choose their own
patch-dependent response. The track header instrument dropdown reuses saved
project instruments or opens explicit loading; bus tracks cannot host
instruments. See WORKSPACE.md for grid/snap divisions, filters and control
behavior.

View file

@ -1,20 +1,23 @@
# Application configuration and MIDI workflows
# Application Configuration and MIDI Workflows
`~/.xtc.conf` is the application's base configuration. It is read before creating
its first project and backend. Missing keys use compiled defaults. Preferences
Apply writes every tab atomically with owner-only permissions; clean exit also
collects workspace, snap/zoom, loop, piano filters and note defaults. A missing
file is created on Apply or clean exit. Invalid files produce a visible startup
error and are preserved until an explicit successful Apply replaces them. A save
failure keeps the window open for retry. Project contents, plugin states and
session clipboards are project/session data, not application preferences.
`~/.xtc.conf` is the application's base configuration. It is read before
creating its first project and backend. Missing keys use compiled defaults.
Preferences Apply writes every tab atomically with owner-only permissions; clean
exit also collects workspace, snap/zoom, loop, piano filters and note defaults.
A missing file is created on Apply or clean exit. Invalid files produce a
visible startup error and are preserved until an explicit successful Apply
replaces them. A save failure keeps the window open for retry. Project contents,
plugin states and session clipboards are project/session data, not application
preferences.
All persistent preference fields are listed by the shared C schema and available
in Preferences. Existing project tempo/controller maps remain project data;
Preferences project defaults apply to New. No configuration I/O runs in an audio
callback. Tests and screenshot captures use isolated configuration paths.
## MIDI backend
---
## MIDI Backend
xtc exposes `midi_in` and `midi_out` JACK ports. The output sends scheduled MIDI
clip notes from unmuted, solo-eligible tracks, retaining source channels and
@ -27,13 +30,16 @@ are outside this note-output workflow.
Input can filter a single channel, and output can preserve or force a channel.
Optional thru forwards supported live notes, controllers and pitch bend while
transport is active. Thru defaults off to avoid accidental feedback. Callback
work and event buffers are bounded; overflow requests all-notes-off and increments
the MIDI-drop counter. Panic and stop send CC123 on all channels. Immediate stop
waits up to 100 ms for callback acknowledgement before deactivation; delivery
cannot be guaranteed after server/device loss or to devices ignoring CC123.
No device is connected unless a user chooses it or an external patchbay routes it.
work and event buffers are bounded; overflow requests all-notes-off and
increments the MIDI-drop counter. Panic and stop send CC123 on all channels.
Immediate stop waits up to 100 ms for callback acknowledgement before
deactivation; delivery cannot be guaranteed after server/device loss or to
devices ignoring CC123. No device is connected unless a user chooses it or an
external patchbay routes it.
## MIDI preferences
---
## MIDI Preferences
Preferences → midi has editable dropdowns for a default input source and output
destination, plus a Refresh MIDI devices button. Names are enumerated from the
@ -42,26 +48,31 @@ field for no automatic connection. Typed offline names remain saved. Hardware
ports are visible only when the host exposes them through its MIDI bridge.
Input channel `-1` accepts all channels; `0..15` selects MIDI channel 1..16.
Output channel `-1` preserves note channels; `0..15` forces a channel. Thru defaults
off. Apply saves these choices; the next Play/Record restores the selected routes.
A missing port rejects that start and stops the backend, preserving the saved
choice for retry. Reconnect audio after server loss, then retry Play. Defaults
also apply when playback resumes after a loop edit. MIDI input/thru currently
requires active transport. Existing editing and project_defaults tabs contain
note length, velocity, channel, filters and new-project controller mappings.
Output channel `-1` preserves note channels; `0..15` forces a channel. Thru
defaults off. Apply saves these choices; the next Play/Record restores the
selected routes. A missing port rejects that start and stops the backend,
preserving the saved choice for retry. Reconnect audio after server loss, then
retry Play. Defaults also apply when playback resumes after a loop edit. MIDI
input/thru currently requires active transport. Existing editing and
project_defaults tabs contain note length, velocity, channel, filters and
new-project controller mappings.
## Export a selected clip
---
Select exactly one MIDI clip and choose File → Export selected clip as MIDI.
The save chooser confirms overwrites. Export writes an atomic Standard MIDI File
(type 0, 960 PPQN) with project tempo, clip name and audible notes relative to the
clip start, retaining channels and velocities. Notes crossing trimmed boundaries
are cropped and notes outside the trim are omitted. Trailing silence remains.
Export does not edit the project or undo history. Audio clips reject. Only note
messages are exported: instruments, opaque plugin data, controller maps,
automation and audio are excluded. Export is unavailable during recording.
## Export A Selected Clip
## Import MIDI files
Select exactly one MIDI clip and choose File → Export selected clip as MIDI. The
save chooser confirms overwrites. Export writes an atomic Standard MIDI File
(type 0, 960 PPQN) with project tempo, clip name and audible notes relative to
the clip start, retaining channels and velocities. Notes crossing trimmed
boundaries are cropped and notes outside the trim are omitted. Trailing silence
remains. Export does not edit the project or undo history. Audio clips reject.
Only note messages are exported: instruments, opaque plugin data, controller
maps, automation and audio are excluded. Export is unavailable during recording.
---
## Import MIDI Files
File → Import MIDI (also in the sequencer Create menu) adds a clip at the cursor
on the selected non-bus track, or creates a track when none is selected. Import
@ -71,17 +82,18 @@ backend preparation retain the old project and IDs.
The reader supports Standard MIDI File types 0 and 1 with PPQN timing, including
running status and velocity-zero note-offs. File tracks merge into one clip;
channels and velocities remain. Timing rounds to 960 ticks per quarter note,
with positive sub-tick notes given one tick. Trailing silence remains. The project
tempo is retained: tempo maps, time signatures, CC/sustain, program changes,
pitch bend, SysEx and other metadata are not imported. This is a notes workflow,
not full-song or instrument reconstruction.
with positive sub-tick notes given one tick. Trailing silence remains. The
project tempo is retained: tempo maps, time signatures, CC/sustain, program
changes, pitch bend, SysEx and other metadata are not imported. This is a notes
workflow, not full-song or instrument reconstruction.
Limits are 1 MiB per file, 64 file tracks, 65,536 parsed events and 4,096 notes
within the project's total note capacity. SMPTE timing and type 2 reject explicitly.
Unterminated notes, zero source duration and malformed events reject rather than
silently dropping notes. Unmatched note-offs are harmless and ignored. Overlapping
same-pitch notes pair FIFO within each file track/channel. Project save/reload
uses the existing format; no format migration or extra dependency is required.
within the project's total note capacity. SMPTE timing and type 2 reject
explicitly. Unterminated notes, zero source duration and malformed events reject
rather than silently dropping notes. Unmatched note-offs are harmless and
ignored. Overlapping same-pitch notes pair FIFO within each file track/channel.
Project save/reload uses the existing format; no format migration or extra
dependency is required.
Configured startup holds the transport clock and emits silence until selected
MIDI ports are connected, so notes at the song start are not consumed before

View file

@ -1,42 +1,46 @@
# Studio mixer
# Studio Mixer
Open **View / Mixer** (Ctrl+2) to see a channel strip for every track and bus.
The horizontal scrollbar reveals additional channels; the master output monitor
stays pinned on the right. The bank supports the full 64-track/bus project limit.
Channel order follows the sequencer, and colors match the track headers.
stays pinned on the right. The bank supports the full 64-track/bus project
limit. Channel order follows the sequencer, and colors match the track headers.
Each strip offers:
- A name button that selects its track for automation below the bank.
- **Mute** and **Solo** switches using the existing inclusive track/bus solo rules.
- **Mute** and **Solo** switches using the existing inclusive track/bus solo
rules.
- A flat **Pan** knob: left -1, center 0, right +1. Drag to turn it;
double-click to center. The text shows direction and percentage.
- An output dropdown for **Master** or a project bus. Bus outputs stay at Master;
nested bus routing remains unsupported. **Add bus** creates a new bus strip.
- A vertical level fader, displayed in dB. Zero dB is unity gain. The upper limit
is approximately +12.04 dB (linear gain 4); the -60 dB bottom position is silence.
- An output dropdown for **Master** or a project bus. Bus outputs stay at
Master; nested bus routing remains unsupported. **Add bus** creates a new bus
strip.
- A vertical level fader, displayed in dB. Zero dB is unity gain. The upper
limit is approximately +12.04 dB (linear gain 4); the -60 dB bottom position
is silence.
- Independent stereo sample-peak meters, numeric dBFS readouts and sticky clip
indications. **Reset peaks** clears all strip and master clip latches.
Changing a fader or knob commits on release, so a completed gesture is one undo
step. The existing preparation guard stops playback before model edits and
rejects changes while recording. Failed edits restore the displayed model values.
Selections are UI state and create no undo step. Track IDs keep controls attached
to the correct channel after reordering, undo, deletion or project replacement.
rejects changes while recording. Failed edits restore the displayed model
values. Selections are UI state and create no undo step. Track IDs keep controls
attached to the correct channel after reordering, undo, deletion or project
replacement.
The top Track selector provides another way to select automation targets; the
bank scrolls to reveal the selection. M/S toggle the selected track's mute/solo,
Left/Right adjust its linear gain, Shift+Left/Right adjust pan, and Up/Down select
adjacent tracks. The existing automation controls below the bank retain Gain/Pan,
First tick and Linear/Hold interpolation. Click to add a point and right-click
near a point to remove it.
Left/Right adjust its linear gain, Shift+Left/Right adjust pan, and Up/Down
select adjacent tracks. The existing automation controls below the bank retain
Gain/Pan, First tick and Linear/Hold interpolation. Click to add a point and
right-click near a point to remove it.
Faders and knobs display **static** mix values. Automation replaces those values
during playback; the controls are not motorized automation displays. Automation
values retain their existing linear gain and pan units. Existing project files
store mix values and automation without a format change. Very small nonzero
gains display at the fader floor; pan/mute/solo edits preserve their actual value.
Moving the fader to its bottom explicitly sets silence.
gains display at the fader floor; pan/mute/solo edits preserve their actual
value. Moving the fader to its bottom explicitly sets silence.
Meters use the backend's consumed peak snapshot for every track and bus,
including offscreen channels. Track taps are after track controls and before the
@ -44,41 +48,49 @@ bus, bus taps follow bus controls, and the master follows the modular graph and
transport envelope. Missing taps decay to silence. Displays use instant attack,
20 dB/s release and sticky clipping at a linear sample peak of 1 or more.
Numeric displays have a -90 dBFS floor, channel bars a -60 dBFS floor; master
bars cover -90 to +6 dBFS. These are sample-peak meters, not loudness or true-peak
meters. The master strip is an output monitor, not a new gain control.
bars cover -90 to +6 dBFS. These are sample-peak meters, not loudness or
true-peak meters. The master strip is an output monitor, not a new gain control.
## Engine mixing behavior
---
Gain is a linear multiplier from 0 to 4. Pan ranges from -1 (left) to +1 (right):
center has unity on both sides and moving away attenuates the opposite side
linearly. This preserves existing centered mono levels; it is not an equal-power
pan law. Mute wins over Solo. Solo behavior across tracks and buses is defined below. WAV clips and scheduled MIDI use their containing track's settings before
summing into the audio/instrument graph sources. Live MIDI input and independent
graph oscillators/external input are unassigned sources and bypass track mixing.
Stereo WAV files are still downmixed at import; stereo output comes from panning
those mono sources. Separate per-track graph inserts/sends are deferred.
## Engine Mixing Behavior
## Automation lanes
Gain is a linear multiplier from 0 to 4. Pan ranges from -1 (left) to +1
(right): center has unity on both sides and moving away attenuates the opposite
side linearly. This preserves existing centered mono levels; it is not an
equal-power pan law. Mute wins over Solo. Solo behavior across tracks and buses
is defined below. WAV clips and scheduled MIDI use their containing track's
settings before summing into the audio/instrument graph sources. Live MIDI input
and independent graph oscillators/external input are unassigned sources and
bypass track mixing. Stereo WAV files are still downmixed at import; stereo
output comes from panning those mono sources. Separate per-track graph
inserts/sends are deferred.
---
## Automation Lanes
Choose Gain or Pan under Automation. The lane shows four bars (15360 ticks);
First tick moves its viewport across the project. Grid labels are absolute ticks.
Click to add a point or replace the point at that snapped time (1/16 note, 240
ticks). Vertical position maps to gain 0..4 or pan -1..1, top to bottom descending.
Right-click near a point's time to remove it. Removing every point restores the
static control. There are at most 1024 points globally, with one per
track/parameter/tick. Track deletion cascades to its automation.
First tick moves its viewport across the project. Grid labels are absolute
ticks. Click to add a point or replace the point at that snapped time (1/16
note, 240 ticks). Vertical position maps to gain 0..4 or pan -1..1, top to
bottom descending. Right-click near a point's time to remove it. Removing every
point restores the static control. There are at most 1024 points globally, with
one per track/parameter/tick. Track deletion cascades to its automation.
A nonempty lane replaces that parameter's static value. Between points the C
engine interpolates linearly or holds each segment (Interpolation chooser),
holding the first/last value outside
their range. Preparation converts ticks to integer sample endpoints, sorts and
indexes lanes. When several ticks quantize to one sample at very low sample
rates, the last point wins at that sample. Rendering searches prepared bounded
arrays; it never sorts, allocates, accesses project data, or calls the GUI.
Seeking and block partitioning give the same timeline values. Mute/solo are
static; automation capture, modes, curves and tempo automation are deferred.
holding the first/last value outside their range. Preparation converts ticks to
integer sample endpoints, sorts and indexes lanes. When several ticks quantize
to one sample at very low sample rates, the last point wins at that sample.
Rendering searches prepared bounded arrays; it never sorts, allocates, accesses
project data, or calls the GUI. Seeking and block partitioning give the same
timeline values. Mute/solo are static; automation capture, modes, curves and
tempo automation are deferred.
## Stereo output and meters
---
## Stereo Output and Meters
The GUI opens the JACK backend with two audio output ports: `output` (left) and
`output_right`. Input and raw WAV recording remain mono. Connect both outputs
@ -89,30 +101,34 @@ port. Old mono audio/MIDI render functions apply gain/mute/solo and gain
automation while ignoring pan; the stereo variants apply both lanes.
The graph executes the same topology separately on left and right buses;
oscillators advance once per frame and are centered on both sides. Existing
port metadata and validation describe the mono graph topology. Stereo processing
uses explicit paired buffers, not new port IDs. Output buffers must be distinct;
oscillators advance once per frame and are centered on both sides. Existing port
metadata and validation describe the mono graph topology. Stereo processing uses
explicit paired buffers, not new port IDs. Output buffers must be distinct;
stereo callers must not alias them with source buffers. Graph-node meter reads
report the maximum of both sides on the processing owner only.
Track and bus meters measure coherent audio plus scheduled MIDI sums, after their
own gain/pan/mute/solo controls, before the shared modular graph. A track tap is
before its parent bus: a muted bus can have active member-track meters. Bus taps
are after bus controls. Master L/R measure the final graph output after transport
fades; downstream effects and graph routing can change its level independently.
Track and bus meters measure coherent audio plus scheduled MIDI sums, after
their own gain/pan/mute/solo controls, before the shared modular graph. A track
tap is before its parent bus: a muted bus can have active member-track meters.
Bus taps are after bus controls. Master L/R measure the final graph output after
transport fades; downstream effects and graph routing can change its level
independently.
The backend retains interval sample-peak maxima until one serialized control
reader consumes them with `xtc_backend_read_meters`. The GUI polls about every
100 ms. Atomic channels are independent (not a simultaneous waveform snapshot);
peaks are quantized to 0.000001 linear and capped at 1000. Legacy backend status
peaks remain last-block linear values. GUI readouts display 20*log10(peak) dBFS, floor -90, with instantaneous
attack and 20 dB/second release. Channel bars cover -60 to +6 dBFS; master
bars cover -90 to +6 dBFS.
Each channel latches red CLIP at linear peak >=1 (0 dBFS) until Reset peaks.
Reset is session state, not an audio edit. Selection changes preserve every strip's meter history. These are sample-peak meters, not RMS, LUFS or true-peak measurements.
There is no limiter; summed levels can exceed unity.
peaks remain last-block linear values. GUI readouts display 20*log10(peak) dBFS,
floor -90, with instantaneous attack and 20 dB/second release. Channel bars
cover -60 to +6 dBFS; master bars cover -90 to +6 dBFS. Each channel latches red
CLIP at linear peak >=1 (0 dBFS) until Reset peaks. Reset is session state, not
an audio edit. Selection changes preserve every strip's meter history. These are
sample-peak meters, not RMS, LUFS or true-peak measurements. There is no
limiter; summed levels can exceed unity.
## Mix buses and gain staging
---
## Mix Buses and Gain Staging
Use Add bus in Mixer, select an ordinary track, then choose its Route: Master or
an existing bus. Buses share the 64-track capacity and gain/pan automation APIs.
@ -123,42 +139,45 @@ routing. Editing uses the existing stopped-transport preparation workflow.
Track controls apply first, then parent bus controls, then the shared graph.
Gain multiplies (track 2 times bus 2 gives 4); pan uses the same center-unity
balance law at both stages. There is no normalization or automatic attenuation.
Solo is one inclusive domain: a soloed track passes through its bus, a soloed bus
admits all its members, and unrelated tracks are silent. Track mute silences
Solo is one inclusive domain: a soloed track passes through its bus, a soloed
bus admits all its members, and unrelated tracks are silent. Track mute silences
that member; bus mute silences its entire output even when a member is soloed.
Live MIDI, external audio inputs and independent graph oscillators remain
unassigned and outside this solo domain.
Master here means the source mix feeding the shared graph, not independent
per-bus effect chains. Graph fan-out and gain branches permit parallel processing
of shared sources. Independent per-track aux sends, sidechains, bus plugin
inserts and nested buses are not represented and remain deferred.
per-bus effect chains. Graph fan-out and gain branches permit parallel
processing of shared sources. Independent per-track aux sends, sidechains, bus
plugin inserts and nested buses are not represented and remain deferred.
Preparation allocates shared stereo monitor scratch on the control thread:
2 * track_count * max_frames * sizeof(float), at most 32 MiB at current limits.
The callback clears, accumulates and scans bounded buffers, with no allocation,
I/O, locks or GUI calls. Immutable audio/MIDI snapshots borrow this scratch until
Preparation allocates shared stereo monitor scratch on the control thread: 2 *
track_count * max_frames * sizeof(float), at most 32 MiB at current limits. The
callback clears, accumulates and scans bounded buffers, with no allocation, I/O,
locks or GUI calls. Immutable audio/MIDI snapshots borrow this scratch until
stopped replacement or backend destruction.
## Undo and keyboard workflow
---
Ctrl+Z undoes; Ctrl+Shift+Z or Ctrl+Y redoes. The C history keeps 32 project edits
plus a baseline, cloning metadata and graph, never audio samples. Common graph,
track, clip, note, mixer, import/relink and automation edits are included. One
accepted gesture is one step; selection, scrolling and graph-node placement are
GUI state and are not undoable. New/Open resets history; Save retains it.
Undo/redo stops and resets transport; surviving selections are preserved by
stable ID (layout remains session state). The
candidate is installed before moving the history cursor: validation, asset-plan
or backend failure retains the current document and history position. Missing
assets retain the existing silent/reporting behavior. A new edit after Undo
removes the redo branch; the oldest state is evicted at capacity. Allocation
ID high-water marks survive Undo/branching so abandoned IDs are not reused. History is
session-only and Undo marks the document dirty even if it matches a saved state.
External WAV files are never deleted or restored by Undo; recording itself cannot
be undone while active. Snapshot storage is reserved before GUI commands mutate/publish, so an
allocation failure rejects the command before adoption; reserved capture itself
does not allocate. C clients using unreserved history record must handle errors.
## Undo and Keyboard Workflow
Ctrl+Z undoes; Ctrl+Shift+Z or Ctrl+Y redoes. The C history keeps 32 project
edits plus a baseline, cloning metadata and graph, never audio samples. Common
graph, track, clip, note, mixer, import/relink and automation edits are
included. One accepted gesture is one step; selection, scrolling and graph-node
placement are GUI state and are not undoable. New/Open resets history; Save
retains it. Undo/redo stops and resets transport; surviving selections are
preserved by stable ID (layout remains session state). The candidate is
installed before moving the history cursor: validation, asset-plan or backend
failure retains the current document and history position. Missing assets retain
the existing silent/reporting behavior. A new edit after Undo removes the redo
branch; the oldest state is evicted at capacity. Allocation ID high-water marks
survive Undo/branching so abandoned IDs are not reused. History is session-only
and Undo marks the document dirty even if it matches a saved state. External WAV
files are never deleted or restored by Undo; recording itself cannot be undone
while active. Snapshot storage is reserved before GUI commands mutate/publish,
so an allocation failure rejects the command before adoption; reserved capture
itself does not allocate. C clients using unreserved history record must handle
errors.
With mixer focus, M/S toggles mute/solo, Left/Right changes gain by 0.05,
Shift+Left/Right changes pan, and Up/Down selects a track. Native sliders also
@ -168,30 +187,34 @@ singly selected; automation-point dragging is not implemented. Track rename and
arrangement clipboard editing are available. See MODERN_EDITING.md for static
mixer A/B comparison and its automation limits.
## Verification and limits
---
## Verification and Limits
`test_mixer` verifies exact stereo gain/pan, mute/solo precedence, automation
samples and block equivalence, stereo graph phase/routing, WAV mixing, v4 exact
roundtrip/truncations/malformed records, lane capacities and history eviction and
branching. Backend fault injection verifies stereo registration failure, output
names, output separation and atomic meters. GUI tests edit lanes with mouse
events, use mixer keys and exercise undo/redo, failed installation rollback,
branching, save/reopen and history reset. Live JACK MIDI acceptance verifies
saved/reopened hard-right playback and stereo peaks using only software clients.
A native Xvfb mouse smoke check covers sliders and automation drawing.
Checks cover independent strip callbacks/meters, fader gestures, pan
centering, failed-edit restoration, routing, undo/reload, duplicate names and
stable IDs across reorder/removal. A 64-channel GUI session verifies scrolling
and minimum/large layouts. Screenshots include live JACK meters and a bus strip.
roundtrip/truncations/malformed records, lane capacities and history eviction
and branching. Backend fault injection verifies stereo registration failure,
output names, output separation and atomic meters. GUI tests edit lanes with
mouse events, use mixer keys and exercise undo/redo, failed installation
rollback, branching, save/reopen and history reset. Live JACK MIDI acceptance
verifies saved/reopened hard-right playback and stereo peaks using only software
clients. A native Xvfb mouse smoke check covers sliders and automation drawing.
Checks cover independent strip callbacks/meters, fader gestures, pan centering,
failed-edit restoration, routing, undo/reload, duplicate names and stable IDs
across reorder/removal. A 64-channel GUI session verifies scrolling and
minimum/large layouts. Screenshots include live JACK meters and a bus strip.
Physical stereo devices, listening quality, realtime endurance and high-density
project performance remain unverified; no new dependency was introduced.
`test_buses` covers bus routing, exact taps, audio/MIDI cancellation, controls,
automation, persistence, invalid routes, deletion/undo and dBFS ballistics. Backend
tests cover consuming interval peaks; GUI tests cover routing undo and clipping.
automation, persistence, invalid routes, deletion/undo and dBFS ballistics.
Backend tests cover consuming interval peaks; GUI tests cover routing undo and
clipping.
Linear/Hold interpolation is available for each point added or replaced. The
point's mode applies to its following segment. Static controls remain fallbacks
while a lane exists; moving/splitting clips does not move absolute track/bus lanes.
See CONTROL.md for common parameter IDs and controller/automation precedence.
while a lane exists; moving/splitting clips does not move absolute track/bus
lanes. See CONTROL.md for common parameter IDs and controller/automation
precedence.

View file

@ -1,57 +1,127 @@
# Modern editing workflows
# Modern Editing Workflows
These control-thread edits were introduced without changing format 15.
The current writer uses format 16 for arrangement markers and still reads older projects. Session selections,
clipboard and comparison snapshots are not saved in projects. Audio callbacks
continue to consume prepared plans; edits use the existing stop/history workflow.
These control-thread edits were introduced without changing format 15. The
current writer uses format 16 for arrangement markers and still reads older
projects. Session selections, clipboard and comparison snapshots are not saved
in projects. Audio callbacks continue to consume prepared plans; edits use the
existing stop/history workflow.
## Arrangement Multi-selection
---
Shift-click clips to toggle selection; Ctrl+A in the sequencer or Edit/Select all clips selects every clip. Plain clicking replaces the selection. Selection is session UI state; no marquee selection yet.
## Arrangement Multi-Selection
Shift-click clips to toggle selection; Ctrl+A in the sequencer or Edit/Select
all clips selects every clip. Plain clicking replaces the selection. Selection
is session UI state; no marquee selection yet.
---
## Transactional Group Editing
Use Left/Right in the sequencer (or Edit/Nudge selection) to move the selected phrase by the current snap step. Ctrl+D duplicates after its complete bounding span; Delete removes selected clips. Edits are atomic and undoable. Mouse dragging remains a single-clip gesture.
Use Left/Right in the sequencer (or Edit/Nudge selection) to move the selected
phrase by the current snap step. Ctrl+D duplicates after its complete bounding
span; Delete removes selected clips. Edits are atomic and undoable. Mouse
dragging remains a single-clip gesture.
---
## Arrangement Clipboard
Ctrl+C/Ctrl+V in the sequencer, or Edit/Copy clips and Paste clips at cursor, copy/paste the selection. Earliest clip lands on the cursor. The earliest source track maps to the currently selected destination track; intervening track spacing is retained. All required tracks must already exist and must not be buses. Instruments, automation and media bytes are not copied. The clipboard survives document changes within the window but is not the OS clipboard.
Ctrl+C/Ctrl+V in the sequencer, or Edit/Copy clips and Paste clips at cursor,
copy/paste the selection. Earliest clip lands on the cursor. The earliest source
track maps to the currently selected destination track; intervening track
spacing is retained. All required tracks must already exist and must not be
buses. Instruments, automation and media bytes are not copied. The clipboard
survives document changes within the window but is not the OS clipboard.
---
## Repeat Arrangement Patterns
Edit/Repeat selected phrase asks for 1..1024 additional copies. The bounding span includes gaps and overlaps; copies remain on their original tracks. The entire repeat is one undo step. Clip/note capacity and timeline endpoints can limit the requested count.
Edit/Repeat selected phrase asks for 1..1024 additional copies. The bounding
span includes gaps and overlaps; copies remain on their original tracks. The
entire repeat is one undo step. Clip/note capacity and timeline endpoints can
limit the requested count.
---
## Align and Slip Editing
Edit/Align clip starts to cursor places every selected start at the cursor. Slip sources earlier/later changes source offsets by the snap step while retaining timeline positions and durations. Trimming must leave source material available; an invalid slip rejects the whole selection. No time stretching or destructive media edits are performed.
Edit/Align clip starts to cursor places every selected start at the cursor. Slip
sources earlier/later changes source offsets by the snap step while retaining
timeline positions and durations. Trimming must leave source material available;
an invalid slip rejects the whole selection. No time stretching or destructive
media edits are performed.
---
## MIDI Swing Quantization
Select MIDI clips and use Edit/MIDI tools/Swing quantize. Grid is in source ticks; swing delays alternate subdivisions by 0..half a grid step, while strength blends the old start toward the nearest swung point. Durations stay fixed. All source notes are affected, including notes outside visible trims. Mixed audio/MIDI selections and notes that would exceed source bounds reject the entire edit. This is destructive note editing with undo, not a live groove engine.
Select MIDI clips and use Edit/MIDI tools/Swing quantize. Grid is in source
ticks; swing delays alternate subdivisions by 0..half a grid step, while
strength blends the old start toward the nearest swung point. Durations stay
fixed. All source notes are affected, including notes outside visible trims.
Mixed audio/MIDI selections and notes that would exceed source bounds reject the
entire edit. This is destructive note editing with undo, not a live groove
engine.
---
## MIDI Legato and Gate Tools
Edit/MIDI tools/Note gate scales lengths from 1 to 400 percent, rounded to at least one tick. Legato ends notes at the next strictly later onset in the same clip and channel; notes in a chord end together. The final onset extends to source end. Gate expansion beyond the source rejects the whole edit. Undo restores original durations.
Edit/MIDI tools/Note gate scales lengths from 1 to 400 percent, rounded to at
least one tick. Legato ends notes at the next strictly later onset in the same
clip and channel; notes in a chord end together. The final onset extends to
source end. Gate expansion beyond the source rejects the whole edit. Undo
restores original durations.
---
## MIDI Scale Correction
Edit/MIDI tools/Correct to scale takes a root (0=C through 11=B) and chromatic, major or natural minor. Each pitch moves to the nearest permitted MIDI note; ties choose the lower pitch. Timing, channel and velocity remain unchanged. Corrections can produce unison notes; notes are not merged. This changes MIDI notes, not audio pitch or a live scale filter.
Edit/MIDI tools/Correct to scale takes a root (0=C through 11=B) and chromatic,
major or natural minor. Each pitch moves to the nearest permitted MIDI note;
ties choose the lower pitch. Timing, channel and velocity remain unchanged.
Corrections can produce unison notes; notes are not merged. This changes MIDI
notes, not audio pitch or a live scale filter.
---
## MIDI Velocity Shaping
Edit/MIDI tools/Shape velocities scales from 0 to 400 percent, rounds, then applies an offset from -127 to 127. Results clamp to 1..127, keeping every note audible rather than introducing velocity-zero note-offs. Use 0 percent with a positive offset for a fixed velocity. No randomization is applied.
Edit/MIDI tools/Shape velocities scales from 0 to 400 percent, rounds, then
applies an offset from -127 to 127. Results clamp to 1..127, keeping every note
audible rather than introducing velocity-zero note-offs. Use 0 percent with a
positive offset for a fixed velocity. No randomization is applied.
---
## Batch Audio Fade Tools
Edit/Audio tools/Set boundary fades sets linear fade-in and fade-out lengths in ticks; 0 or Remove boundary fades clears them. Each fade must fit within half of every selected clip duration. Gain and source offsets stay unchanged. These are boundary envelopes, not overlap-aware crossfades, equal-power curves or destructive WAV processing.
Edit/Audio tools/Set boundary fades sets linear fade-in and fade-out lengths in
ticks; 0 or Remove boundary fades clears them. Each fade must fit within half of
every selected clip duration. Gain and source offsets stay unchanged. These are
boundary envelopes, not overlap-aware crossfades, equal-power curves or
destructive WAV processing.
---
## Mixer A/B Comparison
Edit/Mixer comparison captures or recalls A and B. Capture stores static gain, pan, mute and solo for every track and bus without changing the document. Recall requires the same stable track IDs, stops playback through the normal edit guard and creates one undo step. Track order may change. Routing, plugins and automation are excluded; automation still overrides static gain and pan. Slots are session-only and clear on New/Open. This is static mix comparison, not seamless live scene morphing.
Edit/Mixer comparison captures or recalls A and B. Capture stores static gain,
pan, mute and solo for every track and bus without changing the document. Recall
requires the same stable track IDs, stops playback through the normal edit guard
and creates one undo step. Track order may change. Routing, plugins and
automation are excluded; automation still overrides static gain and pan. Slots
are session-only and clear on New/Open. This is static mix comparison, not
seamless live scene morphing.
## Validation and remaining scope
---
The code is built with warnings enabled and tested. Reproduce the core, transaction and allocation-failure checks with:
## Validation and Remaining Scope
The code is built with warnings enabled and tested. Reproduce the core,
transaction and allocation-failure checks with:
```sh
make BUILD_DIR=build/roadmap-audit all gui test-reliability
@ -65,7 +135,7 @@ system Fontconfig suppression. Save/reload comparisons render identical nonzero
MIDI output after musical transforms; no project format migration is required.
These workflows improve arrangement and musical editing workflows. They do not
provide DAW feature parity: time stretching, comping, a clip launcher, tempo maps,
track insert chains and customizable shortcuts need separate designs. No new
dependencies or audio-callback operations are introduced. Human listening,
provide DAW feature parity: time stretching, comping, a clip launcher, tempo
maps, track insert chains and customizable shortcuts need separate designs. No
new dependencies or audio-callback operations are introduced. Human listening,
physical devices and arbitrary third-party plugins remain unqualified.

View file

@ -1,12 +1,15 @@
# Native C processor API, version 1
# Native C Processor API, Version 1
`xtc_native.h` defines a small compile-time extension boundary for trusted C99
sources, effects and instruments. Build `make build/native-gain` and run it for a
complete graph-effect example in `examples/native_gain.c`. This is a source API
for this release, not a promise of binary compatibility across builds/platforms.
There is no module scanner, dynamic loader, SDK dependency or sandbox.
sources, effects and instruments. Build `make build/native-gain` and run it for
a complete graph-effect example in `examples/native_gain.c`. This is a source
API for this release, not a promise of binary compatibility across
builds/platforms. There is no module scanner, dynamic loader, SDK dependency or
sandbox.
## Descriptor and ownership
---
## Descriptor and Ownership
Set `abi_version = XTC_NATIVE_ABI_VERSION`, `struct_size = sizeof(descriptor)`,
a terminated printable non-space ASCII identifier (1..63 bytes), kind and all
@ -30,59 +33,68 @@ inside callbacks is trusted: xtc cannot enforce its realtime behavior or protect
against memory corruption. C++ processors must use C linkage and never throw
across this boundary; domain/DSP code in this repository remains C.
## Block contract
---
A block contains mono input/output, frames, absolute sample position and optional
MIDI events. Input NULL means silence; exact input/output aliasing is allowed and
processors must support it. Output is writable for every frame, including silent
frames; do not retain any borrowed block pointers. Other overlapping buffers are
not supported. Only instruments receive events; event count <=256, offsets are
sorted and strictly less than frames, with equal offsets retaining caller order.
Pitch/velocity/channel/type bounds follow xtc_midi.h. Zero frames is a no-op.
## Block Contract
A block contains mono input/output, frames, absolute sample position and
optional MIDI events. Input NULL means silence; exact input/output aliasing is
allowed and processors must support it. Output is writable for every frame,
including silent frames; do not retain any borrowed block pointers. Other
overlapping buffers are not supported. Only instruments receive events; event
count <=256, offsets are sorted and strictly less than frames, with equal
offsets retaining caller order. Pitch/velocity/channel/type bounds follow
xtc_midi.h. Zero frames is a no-op.
The host rejects invalid blocks before changing state/output, and reports
CAPACITY above the prepared block limit. Position arithmetic cannot overflow.
A discontinuous position resets held voices/state before processing. Call reset
CAPACITY above the prepared block limit. Position arithmetic cannot overflow. A
discontinuous position resets held voices/state before processing. Call reset
for Stop/panic when hosting instruments yourself; send channel ALL_OFF for
channel-specific panic. The native host does not implement sustain or routing.
On successful processing, nonfinite samples become silence. Callback failure
silences the block, resets that processor and returns its error. Already advanced
processors in a graph are not rolled back. Never rely on failure to transactionally
rewind DSP; prepare fallible resources before publication. There is no latency
compensation, tail protocol, parameter ramp or dynamic port negotiation in ABI 1.
silences the block, resets that processor and returns its error. Already
advanced processors in a graph are not rolled back. Never rely on failure to
transactionally rewind DSP; prepare fallible resources before publication. There
is no latency compensation, tail protocol, parameter ramp or dynamic port
negotiation in ABI 1.
## Graph and instrument integration
---
`xtc_graph_add_native` adds effects (input 0/output 1) or sources (output 1), with
a scalar immutable parameter. Compilation creates two independent mono instances
for stereo; no mutable state is shared between sides. Graph-only edits retain
the existing pending/retired publication contract. Reset clears native state and
its graph-local frame clock; graph positions count successfully rendered frames
since compilation/reset, independently of backend seek/transport. Sources needing
transport time should be hosted explicitly with a block position.
## Graph and Instrument Integration
`xtc_graph_add_native` adds effects (input 0/output 1) or sources (output 1),
with a scalar immutable parameter. Compilation creates two independent mono
instances for stereo; no mutable state is shared between sides. Graph-only edits
retain the existing pending/retired publication contract. Reset clears native
state and its graph-local frame clock; graph positions count successfully
rendered frames since compilation/reset, independently of backend
seek/transport. Sources needing transport time should be hosted explicitly with
a block position.
For an instrument, use create/process directly with sample-offset events, then
feed its output to an existing XTC_NODE_INSTRUMENT through process_sources or
process_stereo. The host owns event routing and panic/reset delivery. Graph-native
instrument addition returns UNSUPPORTED because current graph ports carry audio,
not MIDI. This separation avoids silently dropping instrument events. The built-in
JACK MIDI sine instrument remains the GUI's instrument; custom instrument selection
and event graph ports are future work.
process_stereo. The host owns event routing and panic/reset delivery.
Graph-native instrument addition returns UNSUPPORTED because current graph ports
carry audio, not MIDI. This separation avoids silently dropping instrument
events. The built-in JACK MIDI sine instrument remains the GUI's instrument;
custom instrument selection and event graph ports are future work.
Descriptors survive graph/project cloning and transactional remove/connect edits;
custom ADD_NODE commands use the dedicated constructor rather than a GUI menu.
Native nodes have no project-file representation: Save returns UNSUPPORTED and
preserves the destination. Format v4 still accepts only builtin types 1..6.
A durable identifier/registry, versioned processor state and missing-module policy
must precede serialized native extensions or external plugin hosting.
Descriptors survive graph/project cloning and transactional remove/connect
edits; custom ADD_NODE commands use the dedicated constructor rather than a GUI
menu. Native nodes have no project-file representation: Save returns UNSUPPORTED
and preserves the destination. Format v4 still accepts only builtin types 1..6.
A durable identifier/registry, versioned processor state and missing-module
policy must precede serialized native extensions or external plugin hosting.
Tests cover ABI/size/field rejection, descriptor copying, aliased buffers,
prepare/process failures, stereo compile cleanup, retirement/destruction outside
processing, event offsets, seek note reset, source ports, instrument-buffer graph
routing and unsupported-save preservation. Run `make test`.
processing, event offsets, seek note reset, source ports, instrument-buffer
graph routing and unsupported-save preservation. Run `make test`.
## Structured parameter processors
---
## Structured Parameter Processors
`xtc_processor.h` is the public C99 parameter-only extension interface (ABI 2).
It complements the ABI 1 graph adapter without changing its layout or behavior.
@ -90,27 +102,28 @@ It complements the ABI 1 graph adapter without changing its layout or behavior.
instrument using only public headers; `make test-processor` builds and exercises
both. The instrument has immediate note-off and no voice stealing/release tail.
Descriptors have an exact ABI version and structure size, a persistent identifier,
and up to 16 parameters with unique unsigned IDs, names, units, finite ranges and
defaults. Unsupported ABI versions/sizes are rejected before initialization.
Changing identifier, parameter order, IDs or ranges requires an explicit migration
by the extension author. There is no promise of binary compatibility across
compiler architectures, and no C++/FLTK types in either API.
Descriptors have an exact ABI version and structure size, a persistent
identifier, and up to 16 parameters with unique unsigned IDs, names, units,
finite ranges and defaults. Unsupported ABI versions/sizes are rejected before
initialization. Changing identifier, parameter order, IDs or ranges requires an
explicit migration by the extension author. There is no promise of binary
compatibility across compiler architectures, and no C++/FLTK types in either
API.
Create prepares a copied descriptor on the control thread. Failed preparation
calls destroy, including with NULL: callbacks must implement partial cleanup.
Reset/process/parameter have one audio owner and must never allocate, free, block,
perform I/O or call GUI functions. Destroy is control-thread only after callback
quiescence. Reconfiguration prepares a new independent instance; publish it with
an appropriate owner handoff and retire the old instance after quiescence. The
callback code must remain loaded throughout that lifetime.
Reset/process/parameter have one audio owner and must never allocate, free,
block, perform I/O or call GUI functions. Destroy is control-thread only after
callback quiescence. Reconfiguration prepares a new independent instance;
publish it with an appropriate owner handoff and retire the old instance after
quiescence. The callback code must remain loaded throughout that lifetime.
Each instance has one mono output and either one optional input (effect) or no
input (source/instrument). NULL input means silence. Stereo effects use two
independent instances; multichannel interdependent effects have no adapter.
Instruments receive at most 256 sorted sample-offset MIDI events per block.
The API intentionally has no channel-count argument: unsupported layouts cannot
be represented. Graph ABI 1 rejects instrument descriptors; explicitly render
Instruments receive at most 256 sorted sample-offset MIDI events per block. The
API intentionally has no channel-count argument: unsupported layouts cannot be
represented. Graph ABI 1 rejects instrument descriptors; explicitly render
instrument output and feed the existing instrument source buffers.
Up to 256 sorted parameter events split the process block at exact sample
@ -122,13 +135,14 @@ reset retains current parameters. Callback errors silence the whole output and
reset voices/history; already applied parameters persist. Nonfinite/subnormal
successful samples become zero. Input/output may alias exactly.
`xtc_processor_save`/`restore` run only while quiescent and serialize configuration,
not DSP history. Format: `XTCN`, little-endian uint32 version 2, zero-padded
64-byte identifier, uint32 parameter count, zero uint32 reserved, then ordered
pairs of uint32 ID and IEEE float32 bits. Header is 80 bytes, pairs 8 bytes.
Restore validates exact length, identity, IDs, ranges and defaults/descriptor
compatibility before preparing a new instance; failure leaves existing handles
intact. Opaque assets and hidden state are outside this ABI. ABI 1 graph nodes
remain session-only and fail project saves explicitly; ABI 2 configuration blobs
are a separate public facility, not automatically embedded in project files.
GUI graph access continues through public C APIs only.
`xtc_processor_save`/`restore` run only while quiescent and serialize
configuration, not DSP history. Format: `XTCN`, little-endian uint32 version 2,
zero-padded 64-byte identifier, uint32 parameter count, zero uint32 reserved,
then ordered pairs of uint32 ID and IEEE float32 bits. Header is 80 bytes, pairs
8 bytes. Restore validates exact length, identity, IDs, ranges and
defaults/descriptor compatibility before preparing a new instance; failure
leaves existing handles intact. Opaque assets and hidden state are outside this
ABI. ABI 1 graph nodes remain session-only and fail project saves explicitly;
ABI 2 configuration blobs are a separate public facility, not automatically
embedded in project files. GUI graph access continues through public C APIs
only.

View file

@ -1,48 +1,54 @@
# Performance and realtime qualification
# Performance and Realtime Qualification
The original `test-performance` ran a hardware-independent 48 kHz, 128-frame stereo
workload: 64 graph nodes, 4096 MIDI notes (32 simultaneous), 16 tracks and gain
automation. Preparation and block timing use CLOCK_MONOTONIC outside the engine;
callback code itself does not clock or log. The test checks finite output and
reports mean, p99, maximum and CPU-time/audio-duration ratio. A run is a workload
measurement, not a universal realtime threshold. Run more blocks with:
The original `test-performance` ran a hardware-independent 48 kHz, 128-frame
stereo workload: 64 graph nodes, 4096 MIDI notes (32 simultaneous), 16 tracks
and gain automation. Preparation and block timing use CLOCK_MONOTONIC outside
the engine; callback code itself does not clock or log. The test checks finite
output and reports mean, p99, maximum and CPU-time/audio-duration ratio. A run
is a workload measurement, not a universal realtime threshold. Run more blocks
with:
```sh
./build/test_performance 10000
```
Initial measurement on an AMD Ryzen Threadripper 1920X, GCC 16.2.1, -O2,
2000 blocks: preparation 3.045 ms, mean 308.301 us, p99 422.153 us, maximum
665.096 us, versus a 2666.667 us block deadline; CPU/audio ratio 0.1156. These are
one host/run's observed values and depend on scheduling/load/compiler. The test
Initial measurement on an AMD Ryzen Threadripper 1920X, GCC 16.2.1, -O2, 2000
blocks: preparation 3.045 ms, mean 308.301 us, p99 422.153 us, maximum 665.096
us, versus a 2666.667 us block deadline; CPU/audio ratio 0.1156. These are one
host/run's observed values and depend on scheduling/load/compiler. The test
excludes WAV disk preparation, external/native plugin complexity, GUI redraw,
server scheduling and actual hardware latency. Native callbacks must provide
their own bounded realtime implementation.
Existing software JACK loopback, stereo MIDI/save/reopen, record/reopen,
restart/close and concurrent graph handoff tests remain available. Runtime
fault injection covers loss/rate/xrun/null-buffer and lifecycle failures without
restart/close and concurrent graph handoff tests remain available. Runtime fault
injection covers loss/rate/xrun/null-buffer and lifecycle failures without
altering the shared server. ASan/UBSan validate memory/error paths; TSan checks
recorder/handoff races, not timing. Sanitized runs are not latency qualification.
recorder/handoff races, not timing. Sanitized runs are not latency
qualification.
Current worst-case storage includes up to 256 MiB preloaded mono WAV samples,
paired graph buffers (128 MiB at 256 nodes/65536 frames; GUI limit 32 MiB), bounded
MIDI/mix metadata and up to 33 metadata/graph history snapshots. Pending/retired
plans can retain additional graph memory. Native preparation can allocate more;
the API cannot enforce an extension's memory budget. There is no CPU overload
scheduler, disk streaming, latency compensation or callback allocator audit of
arbitrary third-party code. High-density interactive projects, physical devices,
long-duration stereo/recording and live server termination remain unqualified.
paired graph buffers (128 MiB at 256 nodes/65536 frames; GUI limit 32 MiB),
bounded MIDI/mix metadata and up to 33 metadata/graph history snapshots.
Pending/retired plans can retain additional graph memory. Native preparation can
allocate more; the API cannot enforce an extension's memory budget. There is no
CPU overload scheduler, disk streaming, latency compensation or callback
allocator audit of arbitrary third-party code. High-density interactive
projects, physical devices, long-duration stereo/recording and live server
termination remain unqualified.
## Matrix, capacity and contention checks
---
## Matrix, Capacity and Contention Checks
`make benchmark` records 24 warmed graph cases: 2/16/64/256 nodes, mono/stereo,
44.1k/31, 48k/128 and 96k/1024. Each reports control-thread compile time separately
from 300 process calls, mean/p99/max, block deadline, mean load, and paired graph
buffer bytes. Timing, sorting, allocation and logging happen outside processing.
Metered gain chains test graph scaling; they do not represent expensive plugins.
The checked-in `performance-baseline.json` records CPU/compiler/flags/kernel and
all measurements. Copy a new run only after reviewing changes and conditions.
44.1k/31, 48k/128 and 96k/1024. Each reports control-thread compile time
separately from 300 process calls, mean/p99/max, block deadline, mean load, and
paired graph buffer bytes. Timing, sorting, allocation and logging happen
outside processing. Metered gain chains test graph scaling; they do not
represent expensive plugins. The checked-in `performance-baseline.json` records
CPU/compiler/flags/kernel and all measurements. Copy a new run only after
reviewing changes and conditions.
```sh
make benchmark test-performance test-scale test-stress
@ -60,37 +66,42 @@ shared-machine contention still affect results. No real-time scheduling/pinning
or external profiler is required. CPU-pressure runs are correctness checks, not
comparable timing baselines. Tests/benchmark outputs live below BUILD_DIR.
Initial matrix (Threadripper 1920X, GCC 16.2.1, -O2, 300 blocks): 256-node stereo
48k/128 mean 353.214 us, p99 396.495 us, load .132; 96k/1024 mean 2478.520 us,
p99 3092.434 us, load .232. The full JSON is the evidence. These met the local
investigation target of mean load <.5 for ordinary graph chains; this is a
host-specific development target, not a supported maximum CPU or latency promise.
Initial matrix (Threadripper 1920X, GCC 16.2.1, -O2, 300 blocks): 256-node
stereo 48k/128 mean 353.214 us, p99 396.495 us, load .132; 96k/1024 mean
2478.520 us, p99 3092.434 us, load .232. The full JSON is the evidence. These
met the local investigation target of mean load <.5 for ordinary graph chains;
this is a host-specific development target, not a supported maximum CPU or
latency promise.
The mixed 64-node/4096-note workload measured 340–357 us before removing unused
live-controller processing and 313 us after (mean over 2000 blocks); p99 varied
with system load. The measured hot-path change skips live voices/controller ramps
when neither is active and avoids finished-ramp calls. Exact CC/odd-block tests
continue to pass. No other graph optimization was justified by this measurement.
with system load. The measured hot-path change skips live voices/controller
ramps when neither is active and avoids finished-ramp calls. Exact CC/odd-block
tests continue to pass. No other graph optimization was justified by this
measurement.
`test-scale` constructs all 64 tracks, 1024 clips, 1024 automation points and
4096 notes, compiles a standalone 256-node metered graph, renders stereo odd-sized
blocks and rejects capacity excesses. Project/GUI graphs remain capped at 64
nodes. `test-stress` runs publication, allocation/retirement, capacity and optional
GUI/timer/redraw tests while two ordinary CPU workers compete; subprocesses have
timeouts and workers are always terminated. Existing callback guards verify no
host allocation/free/loading/unloading. This does not qualify arbitrary plugins.
4096 notes, compiles a standalone 256-node metered graph, renders stereo
odd-sized blocks and rejects capacity excesses. Project/GUI graphs remain capped
at 64 nodes. `test-stress` runs publication, allocation/retirement, capacity and
optional GUI/timer/redraw tests while two ordinary CPU workers compete;
subprocesses have timeouts and workers are always terminated. Existing callback
guards verify no host allocation/free/loading/unloading. This does not qualify
arbitrary plugins.
Callback work is bounded by documented node/frame/clip/note/event/point capacities.
Exceeding model/plan/mailbox capacities returns CAPACITY transactionally; invalid
or overlarge processing buffers are silenced and reported, JACK xruns are counted.
There is no CPU scheduler or preemption inside a DSP callback: an expensive trusted
plugin can miss a deadline or hang. Automated LADSPA lanes currently use run(1)
and bounded per-sample lane searches and may be costly; they are excluded from
the gain-chain baseline. Large WAV memory limits remain those in AUDIO_FILES.md.
Physical-device endurance, CPU-overload sound quality and a full GUI latency
benchmark remain unqualified.
Callback work is bounded by documented node/frame/clip/note/event/point
capacities. Exceeding model/plan/mailbox capacities returns CAPACITY
transactionally; invalid or overlarge processing buffers are silenced and
reported, JACK xruns are counted. There is no CPU scheduler or preemption inside
a DSP callback: an expensive trusted plugin can miss a deadline or hang.
Automated LADSPA lanes currently use run(1) and bounded per-sample lane searches
and may be costly; they are excluded from the gain-chain baseline. Large WAV
memory limits remain those in AUDIO_FILES.md. Physical-device endurance,
CPU-overload sound quality and a full GUI latency benchmark remain unqualified.
## Qualification workloads
---
## Qualification Workloads
The current `test-performance` emits one JSON record (the old prose output is
retired). Default: 2,000 stereo blocks at 48 kHz/128, 64 graph nodes, 16 tracks,
@ -101,10 +112,10 @@ for this revision. `benchmark-project` runs nine cases: 1/16/64 tracks,
Two long notes per first 16 tracks remain audible. Other notes are scheduled
later to remain within the 32-voice plan limit. The position wraps every 96,000
samples to keep long runs active. Future notes exercise storage/preparation and
bounded scans rather than additional simultaneous synthesis. Each track has
16 gain points; graph processing uses the same 64-node chain in every case.
The graph matrix separately scales nodes and chain connections from 2 to 256.
These synthetic cases do not qualify every possible dense graph or plugin chain.
bounded scans rather than additional simultaneous synthesis. Each track has 16
gain points; graph processing uses the same 64-node chain in every case. The
graph matrix separately scales nodes and chain connections from 2 to 256. These
synthetic cases do not qualify every possible dense graph or plugin chain.
```sh
make benchmark benchmark-project
@ -122,22 +133,25 @@ exceedances, not actual JACK xruns: there is no server scheduling in the timing
loop. Graph compilation or project construction/preparation is timed separately.
`process_cpu_ms` measures CLOCK_PROCESS_CPUTIME_ID over the complete measured
loop, including timing and finite-output checks; it excludes preparation and
sorting. Project `cpu_audio_ratio` divides that CPU time by simulated audio time.
`peak_rss_kib` is Linux getrusage process high-water RSS, including model, plans,
measurement storage and executable/runtime overhead; it is not current heap size
or a leak test. Graph buffer bytes remain a separate exact buffer-size estimate.
Timing probes, memory queries, sorting and output occur outside engine calls.
sorting. Project `cpu_audio_ratio` divides that CPU time by simulated audio
time. `peak_rss_kib` is Linux getrusage process high-water RSS, including model,
plans, measurement storage and executable/runtime overhead; it is not current
heap size or a leak test. Graph buffer bytes remain a separate exact buffer-size
estimate. Timing probes, memory queries, sorting and output occur outside engine
calls.
Checked-in `project-performance-baseline.json` provides initial project cases;
the original graph baseline is retained. `graph-performance-baseline.json` adds
a current graph report with CPU/RSS/deadline fields. Gates use the existing
1.5x mean-time budget and 1 us noise allowance, with p99/max/misses and memory
a current graph report with CPU/RSS/deadline fields. Gates use the existing 1.5x
mean-time budget and 1 us noise allowance, with p99/max/misses and memory
reported for investigation. There is no universal hardware-independent deadline
or RSS pass threshold. `test-benchmark-tools` verifies same-context success,
regression rejection, missing cases and host/workload mismatch without relying
on machine timing. Do not replace a failing baseline without investigating.
## Endurance, faults and callback audit
---
## Endurance, Faults and Callback Audit
```sh
make test-soak test-stress test-stress-plugins
@ -153,24 +167,26 @@ quiescent position/rebuild cycles. It runs faster than wall time and models
position wrap/seek and restart publication; it is not a real device start/stop
or hardware endurance test. The project benchmark separately wraps active MIDI.
`test-stress` defaults to three rounds under two ordinary CPU workers. Each round
runs concurrent publication, allocation/lifetime guards, maximum model capacity,
project relocation/reload and missing-media retention, save-fault diagnostics,
and the soak test. With JACK headers present it also repeats the deterministic
backend fake's start/stop/seek, rate/buffer/server-loss and failure/retry cases.
Optional GUI tests run after those rounds under the same CPU load.
`test-stress-plugins` adds the original LADSPA fixture's identity/layout/range,
instantiation-error and invalid-metadata cases each round; it needs LADSPA headers.
No command changes a physical device, kills the shared server or loads unknown
plugins. Subprocess deadlines bound each test and workers are cleaned up on exit.
`test-stress` defaults to three rounds under two ordinary CPU workers. Each
round runs concurrent publication, allocation/lifetime guards, maximum model
capacity, project relocation/reload and missing-media retention, save-fault
diagnostics, and the soak test. With JACK headers present it also repeats the
deterministic backend fake's start/stop/seek, rate/buffer/server-loss and
failure/retry cases. Optional GUI tests run after those rounds under the same
CPU load. `test-stress-plugins` adds the original LADSPA fixture's
identity/layout/range, instantiation-error and invalid-metadata cases each
round; it needs LADSPA headers. No command changes a physical device, kills the
shared server or loads unknown plugins. Subprocess deadlines bound each test and
workers are cleaned up on exit.
The reliability executable now interposes fopen/fread/fwrite/read/write,
pthread_mutex_lock and pthread_cond_wait in addition to malloc/calloc/realloc/free.
Guarded host graph/realtime render calls fail immediately on these operations.
This complements the existing native lifetime and plugin callback guards and
control/callback ownership review in RELIABILITY.md. It does not intercept every
libc entry point, raw syscalls, hidden dynamic-module calls, or GUI code by magic;
third-party DSP remains trusted. No known host callback safety violation was found.
pthread_mutex_lock and pthread_cond_wait in addition to
malloc/calloc/realloc/free. Guarded host graph/realtime render calls fail
immediately on these operations. This complements the existing native lifetime
and plugin callback guards and control/callback ownership review in
RELIABILITY.md. It does not intercept every libc entry point, raw syscalls,
hidden dynamic-module calls, or GUI code by magic; third-party DSP remains
trusted. No known host callback safety violation was found.
`check-development` combines the ordinary build/tests, capacity/soak checks,
benchmark gate tests, static analysis and ASan/UBSan. The sanitizer target now
@ -178,16 +194,18 @@ includes maximum capacity and virtual endurance. These are opt-in developer
checks requiring installed Clang/Python; no runtime dependency was added.
Sanitized/contended/profiled runs must not become timing baselines.
## Profiling evidence and remaining costs
---
## Profiling Evidence and Remaining Costs
Before considering DSP changes, a 10,000-block, 64-track/128-frame project was
sampled with GCC `-O2 -g -pg` and GNU gprof on the documented baseline host.
Flat samples attributed about 30% to mixer automation `value`, 29% to MIDI
`segment`, and 11% to finite-value handling. Instrumentation and inlining affect
attribution; this identifies investigation candidates, not exact production
cost or evidence that checks should be removed. MIDI scans bounded stored-note
arrays and evaluates per-voice, per-sample gain/pan lanes. No DSP optimization
was introduced without a separate correctness/performance justification.
attribution; this identifies investigation candidates, not exact production cost
or evidence that checks should be removed. MIDI scans bounded stored-note arrays
and evaluates per-voice, per-sample gain/pan lanes. No DSP optimization was
introduced without a separate correctness/performance justification.
Reproduce in an isolated build (GNU gprof must already be installed):
@ -209,10 +227,10 @@ CPU/audio .142 and peak process RSS 19,296 KiB. The 256-node stereo/128 graph
mean was 286.398 us, p99 484.712 us, compile .300 ms. These case runs observed
zero deadline exceedances. See JSON for all cases and host metadata.
The project comparison passed. Repeated graph comparisons flagged variable
small cases despite unchanged graph processing code; a 3,000-block run against
the retained baseline still flagged 16-node mono/31-frame mean 5.089 us
above its 1.5x budget. The new graph report is evidence, not a replacement chosen
to make that comparison pass. This shared-host timing alert remains an
investigation item; no CPU/latency guarantee is inferred from the correctness
suite. Longer comparisons can use `BENCHMARK_BLOCKS=3000`.
The project comparison passed. Repeated graph comparisons flagged variable small
cases despite unchanged graph processing code; a 3,000-block run against the
retained baseline still flagged 16-node mono/31-frame mean 5.089 us above its
1.5x budget. The new graph report is evidence, not a replacement chosen to make
that comparison pass. This shared-host timing alert remains an investigation
item; no CPU/latency guarantee is inferred from the correctness suite. Longer
comparisons can use `BENCHMARK_BLOCKS=3000`.

View file

@ -1,4 +1,4 @@
# External plugin hosting evaluation
# External Plugin Hosting Evaluation
The original decision deferred external hosting. The native source API is useful
for proving processor contracts without adding an SDK or scanning/loading code.
@ -9,27 +9,32 @@ for proving processor contracts without adding an SDK or scanning/loading code.
| CLAP | C ABI, explicit lifecycle and event model | Extension negotiation, parameter/state persistence, host callbacks, main/audio-thread scheduling and UI integration |
| VST3 | Broad plugin availability | SDK/license review for the selected SDK release, C++ adapter outside the C core, component/controller and editor lifecycle, state and event translation |
No candidate was linked, scanned or loaded. API/SDK and plugin
licenses must be checked at the revision actually adopted; they are separate
from xtc's MIT license. Supporting a format does not grant redistribution rights
to individual plugins. Do not search user plugin directories from the callback.
No candidate was linked, scanned or loaded. API/SDK and plugin licenses must be
checked at the revision actually adopted; they are separate from xtc's MIT
license. Supporting a format does not grant redistribution rights to individual
plugins. Do not search user plugin directories from the callback.
Before implementation, require versioned module identity and state, a missing-
plugin recovery policy, preparation failure/partial cleanup tests, bounded events
and parameter publication, latency/tail handling, callback-safe unload after
retirement, a GUI/main-thread boundary and an explicit crash-containment decision.
Native ABI tests cover some ownership mechanics but do not prove third-party
code safe. Event graph ports remain a gap; track instrument selection is described below.
Choose one format with concrete user needs after those contracts are specified;
do not introduce a general multi-format framework now. See decision 0008.
plugin recovery policy, preparation failure/partial cleanup tests, bounded
events and parameter publication, latency/tail handling, callback-safe unload
after retirement, a GUI/main-thread boundary and an explicit crash-containment
decision. Native ABI tests cover some ownership mechanics but do not prove
third-party code safe. Event graph ports remain a gap; track instrument
selection is described below. Choose one format with concrete user needs after
those contracts are specified; do not introduce a general multi-format framework
now. See decision 0008.
## LADSPA implementation (supersedes the earlier deferral)
---
Decision [0009](decisions/0009-parameter-processors-and-ladspa.md) selects a bounded
LADSPA 1.1 host. `xtc_plugin.h` is the C API; no C++ engine or FLTK type is needed.
## LADSPA Implementation (Supersedes the Earlier Deferral)
1. Choose **File → Load LADSPA plugin** and a trusted absolute `.so` path. Loading
executes native code immediately, including constructors during discovery.
Decision [0009](decisions/0009-parameter-processors-and-ladspa.md) selects a
bounded LADSPA 1.1 host. `xtc_plugin.h` is the C API; no C++ engine or FLTK type
is needed.
1. Choose **File → Load LADSPA plugin** and a trusted absolute `.so` path.
Loading executes native code immediately, including constructors during
discovery.
2. The descriptor browser lists names, labels, makers and source/effect roles.
Select a row and Choose. Counts report compatible and skipped descriptors.
3. In the Modular Graph, connect the new source/effect output to an output or
@ -37,18 +42,19 @@ LADSPA 1.1 host. `xtc_plugin.h` is the C API; no C++ engine or FLTK type is need
receive external input, as other graph processors do.
4. Select its node and use **Edit → Selected plugin control**. The editor shows
name, maker, copyright, module path, descriptor index and unique ID, then
actual port IDs, editable values, rate-adjusted bounds, defaults and discrete/
logarithmic hints. Apply validates all values and candidate initialization;
Cancel and window close preserve the document. Toggled controls require 0/1
and integer controls require integral values.
actual port IDs, editable values, rate-adjusted bounds, defaults and
discrete/ logarithmic hints. Apply validates all values and candidate
initialization; Cancel and window close preserve the document. Toggled
controls require 0/1 and integer controls require integral values.
5. Save normally, reopen, reconnect external JACK playback if needed, and Play.
Plugin changes stop/rebuild playback and participate in ordinary undo/redo.
The GUI uses only public graph/model/plugin APIs. Discovery probes explicit module
files by descriptor index, bounded to 1024 descriptors. It never recursively scans
system directories. The C API exposes probe, parameter metadata, instance
create/process/destroy, and add/get/set graph configuration. Missing header means
hosting returns UNSUPPORTED; metadata persistence continues to work.
The GUI uses only public graph/model/plugin APIs. Discovery probes explicit
module files by descriptor index, bounded to 1024 descriptors. It never
recursively scans system directories. The C API exposes probe, parameter
metadata, instance create/process/destroy, and add/get/set graph configuration.
Missing header means hosting returns UNSUPPORTED; metadata persistence continues
to work.
Supported: 0/1 audio input, 1 output, <=64 total ports and <=16 input controls.
Stereo graph channels are two independent mono instances. Stereo/multichannel
@ -65,22 +71,25 @@ incompatible identity/range/layout, or instantiate returning NULL unwinds the
candidate and leaves active/pending plans intact. Only run and bounded host
buffer copies execute in audio. Destruction and unloading happen after callback
quiescence or retirement acknowledgement on the control thread. Transport reset
retains LADSPA history; compile/reopen starts fresh. A LADSPA run callback has no
error return. Nonfinite/subnormal samples become zero, but native crashes,
retains LADSPA history; compile/reopen starts fresh. A LADSPA run callback has
no error return. Nonfinite/subnormal samples become zero, but native crashes,
corruption, runaway CPU, allocations or I/O inside a plugin cannot be contained
or prevented. Use trusted plugins; hard-realtime properties are declarations,
not proof. This is an in-process host, not a crash-isolation service.
Project v5 stores absolute module path, index, label, LADSPA unique ID, mono input
count and ordered input-control port/value pairs. Restore verifies identity,
layout, parameter ports and ranges against the loaded module. No pointers are
serialized. Relocating a module requires restoring it at its saved absolute path
or deliberately modifying its metadata through a C client. GUI Open reports the
missing/incompatible identity/path and retains the current document. Metadata
load/save and `xtc-check --validate` do not execute plugin code and do not certify
module availability. LADSPA IDs are not cryptographic binary/version identities.
Project v5 stores absolute module path, index, label, LADSPA unique ID, mono
input count and ordered input-control port/value pairs. Restore verifies
identity, layout, parameter ports and ranges against the loaded module. No
pointers are serialized. Relocating a module requires restoring it at its saved
absolute path or deliberately modifying its metadata through a C client. GUI
Open reports the missing/incompatible identity/path and retains the current
document. Metadata load/save and `xtc-check --validate` do not execute plugin
code and do not certify module availability. LADSPA IDs are not cryptographic
binary/version identities.
## Repeatable acceptance
---
## Repeatable Acceptance
```sh
make all test gui test-plugins test-gui-plugins analyze
@ -91,9 +100,10 @@ The first plugin target builds an original MIT shared-library fixture with gain,
source, unsupported stereo and failed-initialization descriptors. Tests exercise
identity/range errors, exact in-place gain, save/reopen/exact rendering, missing
module metadata preservation and 100 plan replacements with callback guards
against host allocation/free/dlopen/dlclose. GUI commands cover add, control edit,
invalid-value rollback, undo/redo and save/New/reopen. ASan/UBSan/leak checks cover
host/fixture lifetimes; plugin code from system packages is not instrumented.
against host allocation/free/dlopen/dlclose. GUI commands cover add, control
edit, invalid-value rollback, undo/redo and save/New/reopen. ASan/UBSan/leak
checks cover host/fixture lifetimes; plugin code from system packages is not
instrumented.
`test-plugins-real` requires these separately installed modules (nothing is
bundled or installed by the target):
@ -108,32 +118,35 @@ Tested packages: ladspa 1.17-7 and swh-plugins 0.4.17-7. These zero-latency
processors define the initial compatible set; other plugins remain unqualified.
Physical listening and plugin CPU/endurance qualification remain pending.
## Discovery, editing and graph lifecycle
---
## Discovery, Editing and Graph Lifecycle
Discovery remains explicit-file only, bounded to 1024 descriptor indices; no
recursive scan, plugin database or new dependency is introduced. Unsupported
layouts are skipped with counts; source/effect roles describe audio ports, not
MIDI instrument capabilities. Modern track instruments are described below. LADSPA sources can be identified, edited, replaced and removed in
the graph but cannot receive MIDI events.
MIDI instrument capabilities. Modern track instruments are described below.
LADSPA sources can be identified, edited, replaced and removed in the graph but
cannot receive MIDI events.
**Edit → Insert LADSPA on selected wire** replaces one existing connection with
`source -> effect -> destination`, as one undo step. Only one-input effects are
accepted; stale wires, capacity failures and initialization failures retain the
original graph. **Edit → Replace selected plugin** accepts the same audio input
layout, keeps the node ID and all wires, and installs the new descriptor's default
controls with no inherited automation. It can replace from another module.
A source/effect layout mismatch is rejected. Undo restores the old identity,
values and automation. Every candidate is compiled before adoption even when
the backend is disconnected; no intermediate graph is published.
layout, keeps the node ID and all wires, and installs the new descriptor's
default controls with no inherited automation. It can replace from another
module. A source/effect layout mismatch is rejected. Undo restores the old
identity, values and automation. Every candidate is compiled before adoption
even when the backend is disconnected; no intermediate graph is published.
Ordering is graph signal flow, not descriptor order, creation order or a flat
rack. Use the graph's connection/disconnection tools to place effects in series
or parallel. Insertion on a wire preserves its endpoints and changes only that
stage. Select a plugin and Delete to remove it and incident wires, then reconnect
as desired; deletion does not automatically bridge or sum routes. A now-unwired
effect receives external audio, following existing graph semantics. Undo/redo
restores removal and routing. Independent bus inserts are still outside the
shared-source graph model.
stage. Select a plugin and Delete to remove it and incident wires, then
reconnect as desired; deletion does not automatically bridge or sum routes. A
now-unwired effect receives external audio, following existing graph semantics.
Undo/redo restores removal and routing. Independent bus inserts are still
outside the shared-source graph model.
Reset defaults in the editor previews every control's declared/default-derived
value. Apply also clears that plugin's automation; Cancel preserves it. The
@ -156,8 +169,8 @@ validated; impossible defaults fail discovery rather than reaching the engine.
The generic modal editor owns copied metadata and temporary widget values only.
It never owns or retains module handles, descriptors or audio instance pointers.
Closing it cannot unload active DSP; Apply builds a new immutable plan before
installation and existing retirement/quiescence rules release old instances.
No embedded third-party editor UI is supported.
installation and existing retirement/quiescence rules release old instances. No
embedded third-party editor UI is supported.
### Compatibility and recovery
@ -174,8 +187,8 @@ Missing/incompatible modules reject GUI Open with the offending label/path and
leave the current project intact. Restore a compatible module at that path and
retry; use metadata-only validation/save to preserve an unavailable module's
records. No dummy replacement silently removes its controls. Failed discovery,
replacement or instantiate reports an error and retains the current document.
A LADSPA `run` callback has no error return; finite-sample sanitation does not
replacement or instantiate reports an error and retains the current document. A
LADSPA `run` callback has no error return; finite-sample sanitation does not
contain crashes, hangs, memory corruption or prohibited work inside a plugin.
Only trusted in-process plugins are supported. See RECOVERY.md.
@ -193,71 +206,83 @@ cover process-error silence/reset. The separately installed amp/inverter/sine
modules remain the qualified LADSPA set. Human listening, arbitrary third-party
editors/plugins and extended realtime endurance remain unverified.
## Parameter targets
---
## Parameter Targets
`xtc_control.h` supplies common stable owner/parameter IDs and control-thread
metadata/edit APIs for track/bus gain/pan and LADSPA input ports. The GUI's plugin
automation command uses this contract. Linear/hold LADSPA encoding and its
integer/toggle restrictions remain unchanged; v10 adds mixer interpolation and
live-sine CC maps without changing plugin state records. Direct static edits do
not override existing lanes. Live CCs do not map to plugins; missing targets or
modules fail without installing partial state. See CONTROL.md for timing,
metadata/edit APIs for track/bus gain/pan and LADSPA input ports. The GUI's
plugin automation command uses this contract. Linear/hold LADSPA encoding and
its integer/toggle restrictions remain unchanged; v10 adds mixer interpolation
and live-sine CC maps without changing plugin state records. Direct static edits
do not override existing lanes. Live CCs do not map to plugins; missing targets
or modules fail without installing partial state. See CONTROL.md for timing,
precedence, reset and backward-jump behavior.
## CLAP, VST2 and VST3 effect loaders
---
**File → Load plugin** now detects LADSPA, CLAP, VST2 and VST3 entry points in an
explicit trusted Linux module. **Insert plugin on selected wire** and **Replace
selected plugin** use the same browser and transactional graph preparation. The
identity/editor dialog names the format and stable ID. Existing LADSPA controls
and automation continue to work. Modern effects currently use their default
configuration: the editor shows identity but no editable parameters.
## CLAP, VST2 and VST3 Effect Loaders
Choose the actual binary: `.clap` for CLAP, a VST2 `.so` exporting `VSTPluginMain`,
or a VST3 bundle's `Contents/<architecture>-linux/<module>.so` exporting
`ModuleEntry`, `ModuleExit` and `GetPluginFactory`. The chooser does not resolve
bundle directories or recursively scan plugin folders. On Linux this avoids
executing unrelated plugins just to discover one selected module. Auto-detection
prefers CLAP, then VST2, then VST3, otherwise LADSPA; the C API also provides
`xtc_plugin_probe_format` for an explicit format.
**File → Load plugin** now detects LADSPA, CLAP, VST2 and VST3 entry points in
an explicit trusted Linux module. **Insert plugin on selected wire** and
**Replace selected plugin** use the same browser and transactional graph
preparation. The identity/editor dialog names the format and stable ID. Existing
LADSPA controls and automation continue to work. Modern effects currently use
their default configuration: the editor shows identity but no editable
parameters.
The modern subset is **one mono audio input and one mono output, float32, default
configuration, zero reported latency**. Stereo graph playback uses independent
instances. For the modern effect subset, true stereo/multichannel/sidechain buses, instruments, MIDI/events,
custom editors, parameter changes/automation, programs/presets, opaque state,
transport/time information, workers and latency compensation are not implemented
for these three formats. Modern metadata with controls/automation is rejected,
not silently ignored. VST3 event buses and CLAP note ports are rejected. Plugins
Choose the actual binary: `.clap` for CLAP, a VST2 `.so` exporting
`VSTPluginMain`, or a VST3 bundle's `Contents/<architecture>-linux/<module>.so`
exporting `ModuleEntry`, `ModuleExit` and `GetPluginFactory`. The chooser does
not resolve bundle directories or recursively scan plugin folders. On Linux this
avoids executing unrelated plugins just to discover one selected module.
Auto-detection prefers CLAP, then VST2, then VST3, otherwise LADSPA; the C API
also provides `xtc_plugin_probe_format` for an explicit format.
The modern subset is **one mono audio input and one mono output, float32,
default configuration, zero reported latency**. Stereo graph playback uses
independent instances. For the modern effect subset, true
stereo/multichannel/sidechain buses, instruments, MIDI/events, custom editors,
parameter changes/automation, programs/presets, opaque state, transport/time
information, workers and latency compensation are not implemented for these
three formats. Modern metadata with controls/automation is rejected, not
silently ignored. VST3 event buses and CLAP note ports are rejected. Plugins
requiring unsupported host extensions may refuse initialization. This is not a
claim that all CLAP/VST2/VST3 plugins will load or sound identically after updates.
claim that all CLAP/VST2/VST3 plugins will load or sound identically after
updates.
Only original effect fixtures have been qualified for the new formats. Trusted
plugin code is in-process and can crash/hang or violate realtime rules. No plugin
binary is included in packages. A VST2 ABI adapter does not provide rights to the
discontinued SDK or plugin redistribution. See decision 0010 and LICENSES.md.
plugin code is in-process and can crash/hang or violate realtime rules. No
plugin binary is included in packages. A VST2 ABI adapter does not provide
rights to the discontinued SDK or plugin redistribution. See decision 0010 and
LICENSES.md.
The control/main thread owns discovery, activation, module reference accounting,
retirement and unloading. CLAP's start-processing call runs on the audio owner;
main-thread callbacks are coalesced and serviced via `xtc_plugin_idle` (GUI timer,
currently 100 ms). C hosts using CLAP must pump that API on their serialized main
thread while instances live, and stop/join audio before destruction. Restart and
VST2 I/O-change requests make subsequent blocks fail/silence; stop and rebuild or
reload the plugin rather than reconfiguring inside the callback. CLAP process
errors and VST3 failure results silence a block. VST2 has no process error return.
main-thread callbacks are coalesced and serviced via `xtc_plugin_idle` (GUI
timer, currently 100 ms). C hosts using CLAP must pump that API on their
serialized main thread while instances live, and stop/join audio before
destruction. Restart and VST2 I/O-change requests make subsequent blocks
fail/silence; stop and rebuild or reload the plugin rather than reconfiguring
inside the callback. CLAP process errors and VST3 failure results silence a
block. VST2 has no process error return.
```sh
make all test gui test-modern-plugins test-gui-modern-plugins
make test-sanitize analyze
```
Original CLAP/VST2/VST3 shared-library fixtures exercise exact in-place and stereo
processing, entry/instance lifetimes, rejected layouts, partial initialization,
CLAP start/process failures, graph replacement rollback, 50 plan retirements per
format, missing modules, v11 save/reopen and GUI add/browser/undo/reopen. Host
allocation/loading/unloading guards remain active while rendering. This evidence
qualifies the subset above, not arbitrary external plugins or physical audio.
Original CLAP/VST2/VST3 shared-library fixtures exercise exact in-place and
stereo processing, entry/instance lifetimes, rejected layouts, partial
initialization, CLAP start/process failures, graph replacement rollback, 50 plan
retirements per format, missing modules, v11 save/reopen and GUI
add/browser/undo/reopen. Host allocation/loading/unloading guards remain active
while rendering. This evidence qualifies the subset above, not arbitrary
external plugins or physical audio.
## Instrument support
---
## Instrument Support
MIDI tracks can bind CLAP, VST2 and VST3 instruments. In the Track menu, select
**Load instrument plugin** for the selected sequencer track; **Use built-in
@ -268,9 +293,9 @@ their own track's instrument. Independent instances feed track gain/pan,
mute/solo, bus routing and automation. Bindings/live target save in v12.
Supply the actual trusted Linux binary (including the inner .so of a VST3
bundle). Supported instruments have zero audio inputs, one mono output,
float32 processing, zero latency and one note/event input. Default configuration
only: no custom editor, opaque state/preset restoration, modern parameter
bundle). Supported instruments have zero audio inputs, one mono output, float32
processing, zero latency and one note/event input. Default configuration only:
no custom editor, opaque state/preset restoration, modern parameter
editing/automation, stereo/multichannel instruments or MIDI output. No arbitrary
third-party instrument is qualified by the original ABI fixture tests.

View file

@ -1,19 +1,21 @@
# Application preferences and looping
# Application Preferences and Looping
Open **Edit / Preferences** (Ctrl+,). Each tab edits application settings.
**Apply** validates every tab, saves `~/.xtc.conf`, and applies the values.
**Close** discards edits made since the last Apply. Apply stops playback; stop
recording before opening Preferences. Clean exit also saves collected application settings.
Startup reads `~/.xtc.conf` before project/backend creation; missing keys use defaults.
Malformed configuration reports an error and uses defaults without overwriting
it. Invalid input or failed writes preserve the previous saved configuration.
recording before opening Preferences. Clean exit also saves collected
application settings. Startup reads `~/.xtc.conf` before project/backend
creation; missing keys use defaults. Malformed configuration reports an error
and uses defaults without overwriting it. Invalid input or failed writes
preserve the previous saved configuration.
The file is INI with `[audio]`, `[plugins]`, `[midi]`, `[interface]`, `[editing]`,
`[project_defaults]` and `[transport]` sections. Save uses a mode-0600 temporary
file, flushes and fsyncs it, then atomically renames it. Unknown keys/sections,
duplicate keys, invalid ranges, embedded NULs and files larger than 64 KiB are
rejected. Blank lines and full-line `;` / `#` comments are supported. Strings
may be quoted, with `\\` and `\"` escapes. Apply rewrites comments and formatting.
The file is INI with `[audio]`, `[plugins]`, `[midi]`, `[interface]`,
`[editing]`, `[project_defaults]` and `[transport]` sections. Save uses a
mode-0600 temporary file, flushes and fsyncs it, then atomically renames it.
Unknown keys/sections, duplicate keys, invalid ranges, embedded NULs and files
larger than 64 KiB are rejected. Blank lines and full-line `;` / `#` comments
are supported. Strings may be quoted, with `\\` and `\"` escapes. Apply rewrites
comments and formatting.
| Section | Settings and defaults |
| --- | --- |
@ -30,37 +32,41 @@ Audio client name, block capacity, output channels and fades take effect on
these remain JACK server settings. Offline rate is used when disconnected.
Recording capacity applies to the next take. Tempo and CC maps apply to **New**
projects. Track mix, routing, instruments, plugin parameters, automation and
musical content remain project data, saved through Save Project. Internal
safety limits and plugin ABI requirements are not runtime preferences.
musical content remain project data, saved through Save Project. Internal safety
limits and plugin ABI requirements are not runtime preferences.
Snap accepts 1 (Off), 120, 160, 240, 320, 480, 960, 1920 or 3840 ticks.
Zoom accepts 24, 48, 96, 192 or 384 pixels per beat. MIDI channels are zero-based;
Snap accepts 1 (Off), 120, 160, 240, 320, 480, 960, 1920 or 3840 ticks. Zoom
accepts 24, 48, 96, 192 or 384 pixels per beat. MIDI channels are zero-based;
filter channel -1 shows all channels. Window sizes, grids and filters currently
in use populate Preferences when it opens; Apply or a clean application exit persists them.
in use populate Preferences when it opens; Apply or a clean application exit
persists them.
Plugin selection now opens a library of `.clap` and `.so` files from the search
paths. `~/` expands to the home directory; empty/nonabsolute roots are skipped.
Traversal does not follow directory symlinks and stops at depth six, 4096 entries
or 512 files. Linux VST3 bundle binaries are found inside bundle directories.
Listing reads filenames only. **Inspect plugin** explicitly loads the selected
trusted binary through the existing descriptor chooser; **Browse files** allows
other locations. It is not a plugin certification or isolation service.
Traversal does not follow directory symlinks and stops at depth six, 4096
entries or 512 files. Linux VST3 bundle binaries are found inside bundle
directories. Listing reads filenames only. **Inspect plugin** explicitly loads
the selected trusted binary through the existing descriptor chooser; **Browse
files** allows other locations. It is not a plugin certification or isolation
service.
## Loop playback
---
## Loop Playback
**Shift-drag the sequencer ruler** to select a region using the selected Snap.
Dragging in either direction works. A band shows the region; **Loop** enables
or disables it. **Loop range...** also accepts start and end in ticks. Selection
Dragging in either direction works. A band shows the region; **Loop** enables or
disables it. **Loop range...** also accepts start and end in ticks. Selection
and toggling take effect immediately; Apply or clean exit persists the region.
The region is global application configuration and is not stored in `.xtc`
projects. Changing a region during playback stops, reconfigures and restarts the
backend. Selecting a region does not itself enable looping.
Playback wraps at the exclusive end sample, including inside an audio block,
and starts at the region beginning when the transport lies outside the region.
The region follows the current project's tempo. Enabled regions must span at
least 64 samples at the active rate. Scheduled MIDI uses the existing seek/reset
and note-chase behavior at a wrap. Effects retain tails/state. No loop-boundary
Playback wraps at the exclusive end sample, including inside an audio block, and
starts at the region beginning when the transport lies outside the region. The
region follows the current project's tempo. Enabled regions must span at least
64 samples at the active rate. Scheduled MIDI uses the existing seek/reset and
note-chase behavior at a wrap. Effects retain tails/state. No loop-boundary
crossfade is added, so discontinuous audio can click. Recording requires looping
to be disabled. Export retains its explicit linear range.
@ -70,24 +76,25 @@ plus JACK callback wrap positions and existing GUI/core regressions. Tests use
temporary configuration paths and do not write the user's configuration.
`[transport] tail_ms=3000` (0..60000) and `stop_after_tail=0` (0/1) are
supported. Export always includes the configured tail; optional
playback stopping uses the GUI timer and is disabled during looping/recording.
See BUILTIN_DEVICES.md for endpoint, cancellation and reset semantics.
supported. Export always includes the configured tail; optional playback
stopping uses the GUI timer and is disabled during looping/recording. See
BUILTIN_DEVICES.md for endpoint, cancellation and reset semantics.
`[interface]` provides `transport_detached`, `mixer_detached`, and each panel's
`_x`, `_y`, `_width`, `_height` rectangle. Use Preferences / Apply or close the application cleanly after
moving panels to save the captured layout. Restoration clamps rectangles to the
current display; the floating transport wraps at narrow widths.
`_x`, `_y`, `_width`, `_height` rectangle. Use Preferences / Apply or close the
application cleanly after moving panels to save the captured layout. Restoration
clamps rectangles to the current display; the floating transport wraps at narrow
widths.
`[interface]` also includes `sequencer_detached`, `graph_detached`,
`piano_detached`, editor window geometry and `track_header_width` (180..360).
Piano retains `piano_width`/`piano_height` and adds `piano_x`/`piano_y`.
Reset workspace layout changes the session; Preferences / Apply or clean exit persists it.
Only original-tab docking and floating windows are supported.
Piano retains `piano_width`/`piano_height` and adds `piano_x`/`piano_y`. Reset
workspace layout changes the session; Preferences / Apply or clean exit persists
it. Only original-tab docking and floating windows are supported.
Configuration also includes complete workspace collection and MIDI
ports/channels/thru. Invalid startup files are preserved on exit; explicit Apply
can replace them after validation. If saving fails, the window stays open for
retry. See [MIDI_WORKFLOWS.md](MIDI_WORKFLOWS.md) for device setup and notes-only
Standard MIDI File import/export. JACK server hardware settings and musical
project contents remain owned by the server and project respectively.
retry. See [MIDI_WORKFLOWS.md](MIDI_WORKFLOWS.md) for device setup and
notes-only Standard MIDI File import/export. JACK server hardware settings and
musical project contents remain owned by the server and project respectively.

View file

@ -1,15 +1,17 @@
# Project format v17 (reads v1–v17)
# Project Format v17 (Reads v1–v17)
The project uses a bounded text format implemented in C99 without a parser library.
The extension is `.xtc`. File operations run exclusively on the control thread.
The project owns its graph, tracks and silent/audio arrangement regions. Audio samples remain in external WAV
files; MIDI notes are stored inline.
The project uses a bounded text format implemented in C99 without a parser
library. The extension is `.xtc`. File operations run exclusively on the control
thread. The project owns its graph, tracks and silent/audio arrangement regions.
Audio samples remain in external WAV files; MIDI notes are stored inline.
---
## Schema
Records occur in this exact order, separated by LF, including the final record.
Fields use ASCII spaces. Integers are unsigned decimal, without signs.
Counts determine the number of subsequent records:
Fields use ASCII spaces. Integers are unsigned decimal, without signs. Counts
determine the number of subsequent records:
```text
XTC_PROJECT 11
@ -55,37 +57,38 @@ END
- `TEMPO`: constant milli-BPM (20000..300000, default 120000), fixed 4/4.
- `IDS`: next track ID and next clip ID. IDs are uint64, monotonic and never
reused; UINT64_MAX is reserved. Track and clip ID namespaces are separate.
- `GRAPH`: output-node policy (0/1), next node ID, node count, edge count.
Nodes retain stable IDs (limited to the host's size_t range). No pointers or
- `GRAPH`: output-node policy (0/1), next node ID, node count, edge count. Nodes
retain stable IDs (limited to the host's size_t range). No pointers or
compiled state are serialized.
- `NODE`: ID, type (1 gain, 2 oscillator, 3 meter, 4 output, 5 audio source, 6 instrument, 8 LADSPA plugin), and exactly eight
hexadecimal digits encoding the IEEE binary32 parameter bits, independent
of locale and host byte order. Nonfinite values are rejected.
- `EDGE`: source and destination node IDs. Port directions are implied
by node type; endpoints, duplicates and cycles are validated by the graph API.
- `NODE`: ID, type (1 gain, 2 oscillator, 3 meter, 4 output, 5 audio source, 6
instrument, 8 LADSPA plugin), and exactly eight hexadecimal digits encoding
the IEEE binary32 parameter bits, independent of locale and host byte order.
Nonfinite values are rejected.
- `EDGE`: source and destination node IDs. Port directions are implied by node
type; endpoints, duplicates and cycles are validated by the graph API.
- `TRACK`: ID and hex-encoded name bytes. In v4/v5 each is immediately followed
by `MIX gain_bits pan_bits mute solo`: finite binary32 gain 0..4, pan -1..1
encoded as eight hex digits, mute and solo unsigned 0/1.
- `ROUTE`: since v9, required after MIX: bus flag and destination bus ID;
UINT64_MAX denotes master. See the mix routing constraints below.
- `CLIP`: ID, track ID, start tick, length tick, source offset tick, source
length tick, hex-encoded name bytes. Trimming
preserves source bounds, so a region can re-extend only within its original
length. Overlaps are allowed and audio clips mix through the audio source node.
- `ASSET`: required after each CLIP in v2/v3/v4/v5; "-" means no audio asset, otherwise
hex-encoded absolute path bytes, <=511 bytes after decoding, no embedded NUL
or ASCII control characters. Each clip directly references a WAV; no sample
data, pointers or DSP state are embedded. Paths can contain spaces.
- `MIDI`: required after each ASSET in v3/v4/v5; 0 for silent/audio regions, 1 for
MIDI clips. MIDI clips must have no asset.
length tick, hex-encoded name bytes. Trimming preserves source bounds, so a
region can re-extend only within its original length. Overlaps are allowed and
audio clips mix through the audio source node.
- `ASSET`: required after each CLIP in v2/v3/v4/v5; "-" means no audio asset,
otherwise hex-encoded absolute path bytes, <=511 bytes after decoding, no
embedded NUL or ASCII control characters. Each clip directly references a WAV;
no sample data, pointers or DSP state are embedded. Paths can contain spaces.
- `MIDI`: required after each ASSET in v3/v4/v5; 0 for silent/audio regions, 1
for MIDI clips. MIDI clips must have no asset.
- `NOTES`: next stable note ID and global note count, followed by that many
`NOTE id clip_id start_tick length_tick pitch velocity channel` records.
Notes reference MIDI clips; start/length are source-relative, positive length
within source bounds. Pitch is 0..127, velocity 1..127, channel 0..15.
Note IDs are unique, monotonic uint64 IDs with UINT64_MAX reserved.
`NOTE id clip_id start_tick length_tick pitch velocity channel` records. Notes
reference MIDI clips; start/length are source-relative, positive length within
source bounds. Pitch is 0..127, velocity 1..127, channel 0..15. Note IDs are
unique, monotonic uint64 IDs with UINT64_MAX reserved.
- `AUTOMATION`: global point count, after the last NOTE and before END in v4/v5.
Follow with `POINT track_id parameter tick value_bits step` in v10 (v4–v9
omit step and migrate to linear). Step is 0 linear or 1 hold for the next segment.
Follow with `POINT track_id parameter tick value_bits step` in v10 (v4–v9 omit
step and migrate to linear). Step is 0 linear or 1 hold for the next segment.
Parameter 0 is gain (0..4), 1 pan (-1..1); values use finite binary32 hex.
Track must exist; tick <= MAX_TICK; duplicate track/parameter/tick is invalid.
Record order is preserved; rendering sorts a separate prepared copy.
@ -94,20 +97,24 @@ END
Ticks use 960 per quarter note, 3840 per bar. All region endpoints are bounded
by 38400000 ticks (10000 bars). Limits: 64 tracks, 1024 clips, 64 graph nodes,
4096 edges, 4096 notes, 1024 automation points, 4 MiB input, 1535 bytes per record, 12 tokens per record. The reader
rejects invalid references, duplicate IDs, inconsistent next IDs, ranges,
truncated records, raw control/non-ASCII bytes, and trailing data.
4096 edges, 4096 notes, 1024 automation points, 4 MiB input, 1535 bytes per
record, 12 tokens per record. The reader rejects invalid references, duplicate
IDs, inconsistent next IDs, ranges, truncated records, raw control/non-ASCII
bytes, and trailing data.
Unsupported format versions return UNSUPPORTED. Other malformed data returns
FORMAT. The reader explicitly accepts v1 records without ASSET lines, with
node types 1..4, migrating their clips to empty asset paths. Version 2 accepts
node types 1..5 and ASSET records. Both migrate to MIDI=0 and no notes.
Version 3 accepts instrument nodes and MIDI/NOTES records. Versions 1–3 migrate
all tracks to unity gain, centered pan, mute/solo off and no automation.
Saving always writes v15. Unknown versions are rejected without replacing the destination; older
applications without v12 support cannot read current saves. Migration and maximum-path roundtrips are tested.
FORMAT. The reader explicitly accepts v1 records without ASSET lines, with node
types 1..4, migrating their clips to empty asset paths. Version 2 accepts node
types 1..5 and ASSET records. Both migrate to MIDI=0 and no notes. Version 3
accepts instrument nodes and MIDI/NOTES records. Versions 1–3 migrate all tracks
to unity gain, centered pan, mute/solo off and no automation. Saving always
writes v15. Unknown versions are rejected without replacing the destination;
older applications without v12 support cannot read current saves. Migration and
maximum-path roundtrips are tested.
## Ownership and failures
---
## Ownership and Failures
Load builds and validates a separate candidate, closes the input, then replaces
the destination. Any failure retains the complete previous project. Saving
@ -116,35 +123,42 @@ directory, writes, flushes, fsyncs, closes and renames over the destination.
Failures before rename retain the previous destination and remove the temporary.
After rename, the parent directory is fsynced. A directory-sync failure reports
I/O error even though the new complete destination may already be present.
Storage and filesystem behavior still determine power-loss durability. Save replaces symlinks themselves and does not preserve
existing file permissions. POSIX I/O is private to the Linux-first persistence
module; public headers contain no POSIX types.
Storage and filesystem behavior still determine power-loss durability. Save
replaces symlinks themselves and does not preserve existing file permissions.
POSIX I/O is private to the Linux-first persistence module; public headers
contain no POSIX types.
Saved data reproduces the arrangement, graph, track mix and automation, including
ID allocation state. Undo history is not saved. See MIXER.md for pan/interpolation.
Graph node positions, timeline zoom/scroll/selection, transport position and DSP
oscillator phase are GUI/session state and are not saved. Asset references are
absolute in memory; saves use relative paths for media inside the project folder,
otherwise absolute paths. Media are not copied on Save As. Metadata loading preserves
missing references; audio-plan preparation reports unreadable/invalid assets
and renders them silently. Relinking replaces a selected clip's path without
changing its IDs or arrangement. See AUDIO_FILES.md for recovery and limitations.
Saved data reproduces the arrangement, graph, track mix and automation,
including ID allocation state. Undo history is not saved. See MIXER.md for
pan/interpolation. Graph node positions, timeline zoom/scroll/selection,
transport position and DSP oscillator phase are GUI/session state and are not
saved. Asset references are absolute in memory; saves use relative paths for
media inside the project folder, otherwise absolute paths. Media are not copied
on Save As. Metadata loading preserves missing references; audio-plan
preparation reports unreadable/invalid assets and renders them silently.
Relinking replaces a selected clip's path without changing its IDs or
arrangement. See AUDIO_FILES.md for recovery and limitations.
## Extension and recovery policy
---
Format v4 was retained. Native processor descriptors are process-local and have no
serialized representation; saving a native graph returns UNSUPPORTED and retains
the existing destination. Unknown file versions/types remain rejected. See
NATIVE_API.md for the extension boundary and RECOVERY.md for write-failure, killed
writer and unfinished-recording behavior. Metadata validation is available via
`xtc-check --validate PROJECT` without a GUI or server; it does not load assets.
## Extension and Recovery Policy
## Version 5: plugin metadata
Format v4 was retained. Native processor descriptors are process-local and have
no serialized representation; saving a native graph returns UNSUPPORTED and
retains the existing destination. Unknown file versions/types remain rejected.
See NATIVE_API.md for the extension boundary and RECOVERY.md for write-failure,
killed writer and unfinished-recording behavior. Metadata validation is
available via `xtc-check --validate PROJECT` without a GUI or server; it does
not load assets.
`XTC_PROJECT 5` was introduced; its reader also supported versions 1–4. All v4 records and
order remain the same. Node type 8 is a LADSPA plugin; native pointer-bearing type
7 is still unsupported in files. Immediately after each type-8 `NODE id 8
00000000` record come:
---
## Version 5: Plugin Metadata
`XTC_PROJECT 5` was introduced; its reader also supported versions 1–4. All v4
records and order remain the same. Node type 8 is a LADSPA plugin; native
pointer-bearing type 7 is still unsupported in files. Immediately after each
type-8 `NODE id 8 00000000` record come:
```text
PLUGIN unique_id descriptor_index mono_inputs control_count hex_label
@ -155,130 +169,153 @@ CONTROL port_index float32_hex_bits
There are exactly `control_count` CONTROL records. Label length is 1–63 bytes,
module path 1–511 bytes (absolute), both hex-encoded with no embedded NUL. Index
is <1024, mono_inputs 0/1, controls <=16, control port indices <64 strictly
increasing, and values finite. Unique ID fits uint32. The one output is implicit.
Structural validation never opens libraries. On graph compilation the host checks
identity, layout, input control port IDs and rate-adjusted legal ranges against
the module. Unknown nodes/versions fail without mutating the current project.
Missing modules remain preserved by metadata load/save; GUI installation rejects
them with a repair message. No transient DSP state, pointer or plugin binary is
stored. See PLUGIN_HOSTING.md and decision 0009 for supported processors/limits.
increasing, and values finite. Unique ID fits uint32. The one output is
implicit. Structural validation never opens libraries. On graph compilation the
host checks identity, layout, input control port IDs and rate-adjusted legal
ranges against the module. Unknown nodes/versions fail without mutating the
current project. Missing modules remain preserved by metadata load/save; GUI
installation rejects them with a repair message. No transient DSP state, pointer
or plugin binary is stored. See PLUGIN_HOSTING.md and decision 0009 for
supported processors/limits.
---
## Version 6
Version 6 was introduced and its reader accepted v1–v6. After each plugin's CONTROL records,
`PLUGIN_AUTOMATION count` is followed by exactly count (<=32) records:
`PARAM tick port mode float32_hex_bits`. Mode is 0 linear or 1 step. Duplicate
port/tick pairs, nonfinite values, unknown control ports and ticks beyond the
project limit are rejected. Loaded plugin tempo comes from TEMPO. Range and
discrete-mode compatibility are checked on control-thread compilation. Versions
1–5 migrate with empty plugin lanes. See CONTROL.md for timing/ordering.
Version 6 was introduced and its reader accepted v1–v6. After each plugin's
CONTROL records, `PLUGIN_AUTOMATION count` is followed by exactly count (<=32)
records: `PARAM tick port mode float32_hex_bits`. Mode is 0 linear or 1 step.
Duplicate port/tick pairs, nonfinite values, unknown control ports and ticks
beyond the project limit are rejected. Loaded plugin tempo comes from TEMPO.
Range and discrete-mode compatibility are checked on control-thread compilation.
Versions 1–5 migrate with empty plugin lanes. See CONTROL.md for
timing/ordering.
## Version 7 and project folders
---
Version 7 was introduced; its readers migrated v1–v6. Record ordering and field bounds
are otherwise unchanged from v6. v7 ASSET may contain a relative path, interpreted
against the actual containing directory of the named project file (resolved on
the control thread), never the launch working directory. Opening a symlink to a project uses the
resolved target project directory; Save replaces the named entry as before. After loading, all core
asset references are absolute, including missing media. Media inside the save
folder are written relative; references outside remain absolute. Save As resolves
against its new folder and preserves media identity rather than rebasing it by
accident. Copy/move a project folder together with its referenced media to retain
## Version 7 and Project Folders
Version 7 was introduced; its readers migrated v1–v6. Record ordering and field
bounds are otherwise unchanged from v6. v7 ASSET may contain a relative path,
interpreted against the actual containing directory of the named project file
(resolved on the control thread), never the launch working directory. Opening a
symlink to a project uses the resolved target project directory; Save replaces
the named entry as before. After loading, all core asset references are
absolute, including missing media. Media inside the save folder are written
relative; references outside remain absolute. Save As resolves against its new
folder and preserves media identity rather than rebasing it by accident.
Copy/move a project folder together with its referenced media to retain
portability. Saving does not copy assets: import/record into that folder first.
Plugin binaries remain trusted absolute module references, not bundled resources.
Plugin binaries remain trusted absolute module references, not bundled
resources.
Empty assets still use `-`. Paths exclude embedded NUL/control bytes and must fit
511 bytes after resolution; overly long resolved references fail transactionally.
Relative references can name outside the folder using ordinary POSIX semantics;
this is not a sandbox. Symlinks follow filesystem behavior, so portability requires
moving their targets too. Old formats continue to require absolute media paths.
No unknown version/node/record is silently discarded; unsupported versions/types
or malformed/trailing data reject the candidate and retain the current project.
Parsing never opens media/plugins or constructs an audio engine.
Empty assets still use `-`. Paths exclude embedded NUL/control bytes and must
fit 511 bytes after resolution; overly long resolved references fail
transactionally. Relative references can name outside the folder using ordinary
POSIX semantics; this is not a sandbox. Symlinks follow filesystem behavior, so
portability requires moving their targets too. Old formats continue to require
absolute media paths. No unknown version/node/record is silently discarded;
unsupported versions/types or malformed/trailing data reject the candidate and
retain the current project. Parsing never opens media/plugins or constructs an
audio engine.
Save validates the complete model, writes/fsyncs a same-folder temporary, closes
it, atomically renames it, then fsyncs the containing directory. Failures before
rename retain the old destination. A directory sync failure *after* rename reports
I/O error but the new complete file may already be present: inspect/reopen it;
there is no rollback across that durability boundary. Filesystem/hardware power
loss guarantees still depend on the underlying storage. Recording destinations
have their separate no-clobber policy; ordinary project Save intentionally replaces
the selected document. In-memory models and the existing destination are preserved
on load failure. No automatic unknown-field stripping or media deletion occurs.
rename retain the old destination. A directory sync failure *after* rename
reports I/O error but the new complete file may already be present:
inspect/reopen it; there is no rollback across that durability boundary.
Filesystem/hardware power loss guarantees still depend on the underlying
storage. Recording destinations have their separate no-clobber policy; ordinary
project Save intentionally replaces the selected document. In-memory models and
the existing destination are preserved on load failure. No automatic
unknown-field stripping or media deletion occurs.
`make test-portability test test-plugins test-recovery test-recording` covers checked-in
v1–v10 fixtures (including missing v5/v6 plugins), current exact roundtrips, moved-folder
playback from a different cwd, missing media retention, unknown records, every
truncation and deterministic byte mutations, malformed plugin metadata, interrupted
writers and write-limit failures. ASan/UBSan/static analysis additionally audit the
bounded parser. This is regression/fault-injection coverage, not exhaustive fuzzing
or a claim of power-cut qualification.
`make test-portability test test-plugins test-recovery test-recording` covers
checked-in v1–v10 fixtures (including missing v5/v6 plugins), current exact
roundtrips, moved-folder playback from a different cwd, missing media retention,
unknown records, every truncation and deterministic byte mutations, malformed
plugin metadata, interrupted writers and write-limit failures. ASan/UBSan/static
analysis additionally audit the bounded parser. This is
regression/fault-injection coverage, not exhaustive fuzzing or a claim of
power-cut qualification.
## Version 8: audio clip settings
---
## Version 8: Audio Clip Settings
Version 8 adds audio clip gain/fade settings and reads v1–v7 with unity clip
gain and no explicit clip fades. After **every** clip's `MIDI` record (including silent and MIDI
clips), v8 requires:
gain and no explicit clip fades. After **every** clip's `MIDI` record (including
silent and MIDI clips), v8 requires:
```text
AUDIO gain_float32_hex_bits fade_in_ticks fade_out_ticks
```
Gain is finite, linear and within 0..4. Fade durations are unsigned decimal,
0..source_length_tick; zero disables that explicit fade. MIDI clips require
gain exactly 1 and both fades zero. Invalid or missing AUDIO records reject the
whole candidate. All v7 media-folder rules and atomic-save guarantees remain.
Older applications reject v8 rather than silently stripping clip settings.
Rebuild C/C++ consumers against the extended `xtc_clip_info` public struct;
this development release does not promise an unchanged struct binary layout.
0..source_length_tick; zero disables that explicit fade. MIDI clips require gain
exactly 1 and both fades zero. Invalid or missing AUDIO records reject the whole
candidate. All v7 media-folder rules and atomic-save guarantees remain. Older
applications reject v8 rather than silently stripping clip settings. Rebuild
C/C++ consumers against the extended `xtc_clip_info` public struct; this
development release does not promise an unchanged struct binary layout.
Fades follow the audible trimmed clip boundaries, in current-tempo ticks, with
sample endpoints defined in AUDIO_FILES.md. Duplicating retains settings and
source references; splitting keeps the original ID on the left, gives the right
a fresh ID/source offset, retains outer explicit fades and clears inner ones.
No waveform cache or source audio is serialized. Checked-in v1–v7 migrations,
v8 exact roundtrips, malformed new metadata and byte-mutation tests cover the
a fresh ID/source offset, retains outer explicit fades and clears inner ones. No
waveform cache or source audio is serialized. Checked-in v1–v7 migrations, v8
exact roundtrips, malformed new metadata and byte-mutation tests cover the
format change (`make test test-portability test-audio-editing`).
## Version 9: mix routing
---
Version 9 adds `ROUTE is_bus bus_id` immediately after each TRACK's MIX record. `is_bus` is 0 or 1; `bus_id` is an existing bus track ID,
or decimal UINT64_MAX (18446744073709551615) for master. Buses must route to
master and cannot contain clips. Invalid destinations, flags and nested routing
are rejected. v1–v8 migrate all tracks to ordinary direct-to-master tracks.
Bus controls and automation use existing MIX and POINT records. Older readers
reject v9 rather than discard routing. The public `xtc_track_info` grew fields;
API consumers must rebuild. Fixtures cover v1–v8 migration and tests exercise
v9 exact roundtrips and malformed routing.
## Version 9: Mix Routing
## Version 10: interpolation and controller bindings
Version 9 adds `ROUTE is_bus bus_id` immediately after each TRACK's MIX record.
`is_bus` is 0 or 1; `bus_id` is an existing bus track ID, or decimal UINT64_MAX
(18446744073709551615) for master. Buses must route to master and cannot contain
clips. Invalid destinations, flags and nested routing are rejected. v1–v8
migrate all tracks to ordinary direct-to-master tracks. Bus controls and
automation use existing MIX and POINT records. Older readers reject v9 rather
than discard routing. The public `xtc_track_info` grew fields; API consumers
must rebuild. Fixtures cover v1–v8 migration and tests exercise v9 exact
roundtrips and malformed routing.
Version 10 adds interpolation modes and migration from v1–v9. Track/bus automation points are
`POINT track_id parameter tick value_bits step`, where step is 0 linear or 1
hold for the segment starting there. v1–v9 points migrate to step=0. LADSPA
`PARAM` encoding remains unchanged (already has interpolation mode).
---
## Version 10: Interpolation and Controller Bindings
Version 10 adds interpolation modes and migration from v1–v9. Track/bus
automation points are `POINT track_id parameter tick value_bits step`, where
step is 0 linear or 1 hold for the segment starting there. v1–v9 points migrate
to step=0. LADSPA `PARAM` encoding remains unchanged (already has interpolation
mode).
Immediately after all POINT records, before END, v10 requires `CONTROL_MAPS 16`
and exactly 16 records in ascending channel order:
`MAP channel volume_CC pan_CC sustain_CC`. Channels are 0..15. Controllers are
0..127 or 128 (disabled); enabled numbers must be distinct within a row and may
not be 120 or 123. Duplicate/missing/out-of-order channels, invalid CCs, reserved
bindings and invalid interpolation modes reject the complete candidate. v1–v9
supply default 7/10/64 bindings on every channel. No event history/current live
controller values are serialized. Older readers reject v10.
not be 120 or 123. Duplicate/missing/out-of-order channels, invalid CCs,
reserved bindings and invalid interpolation modes reject the complete candidate.
v1–v9 supply default 7/10/64 bindings on every channel. No event history/current
live controller values are serialized. Older readers reject v10.
Fixtures cover v1–v10 loading and migration. Exact roundtrips, malformed metadata, step/linear
renders and remapped live events cover the new writer/reader. Public clients
must rebuild and initialize the appended `xtc_automation_point.step` field.
Fixtures cover v1–v10 loading and migration. Exact roundtrips, malformed
metadata, step/linear renders and remapped live events cover the new
writer/reader. Public clients must rebuild and initialize the appended
`xtc_automation_point.step` field.
## Compatibility and diagnostic contract
---
## Compatibility and Diagnostic Contract
The current reader accepts versions 1–15 and the writer always emits version 15.
Compatibility diagnostics made no format change; plugin format identity is added in v11. Saving an older document upgrades it;
retain an original backup if an older application must still open it. APP is
informational; compatibility depends on the format version, not the producer's
application version. A future incompatible schema requires a new format number.
Public C/C++ consumers must rebuild when public structs change.
Compatibility diagnostics made no format change; plugin format identity is added
in v11. Saving an older document upgrades it; retain an original backup if an
older application must still open it. APP is informational; compatibility
depends on the format version, not the producer's application version. A future
incompatible schema requires a new format number. Public C/C++ consumers must
rebuild when public structs change.
| Version | Added representation | Defaults when loading earlier versions |
| --- | --- | --- |
@ -303,41 +340,47 @@ incompatible plugins retain the current document and identify the module.
Missing media references remain available for relinking and render silently.
`xtc_project_load_diagnostic` adds a copied result, parsed format version (zero
if unavailable), expected record/stage and message. Syntax locations are 1-based;
line zero denotes I/O, size, path resolution or whole-model validation. MODEL
errors do not identify the original line of a cross-reference. The original
`xtc_project_load` remains available with identical transactional semantics.
if unavailable), expected record/stage and message. Syntax locations are
1-based; line zero denotes I/O, size, path resolution or whole-model validation.
MODEL errors do not identify the original line of a cross-reference. The
original `xtc_project_load` remains available with identical transactional
semantics.
`xtc-check --validate PROJECT` checks bounded metadata without loading dependencies.
`xtc-check --inspect PROJECT` additionally lists resolved media and module paths,
plugin labels, unique IDs and descriptor indices. It checks readable regular-file
availability only. Exit status is 0 when references are available, 2 when valid
metadata has missing/unreadable references, and 1 for invalid metadata or I/O.
Neither command executes plugin code, decodes WAV data, connects JACK or verifies
hardware. A readable file can still be an invalid WAV or incompatible plugin;
actual preparation performs those checks. Paths and labels are escaped for
terminal output. There is no search-path substitution or automatic plugin relink.
`xtc-check --validate PROJECT` checks bounded metadata without loading
dependencies. `xtc-check --inspect PROJECT` additionally lists resolved media
and module paths, plugin labels, unique IDs and descriptor indices. It checks
readable regular-file availability only. Exit status is 0 when references are
available, 2 when valid metadata has missing/unreadable references, and 1 for
invalid metadata or I/O. Neither command executes plugin code, decodes WAV data,
connects JACK or verifies hardware. A readable file can still be an invalid WAV
or incompatible plugin; actual preparation performs those checks. Paths and
labels are escaped for terminal output. There is no search-path substitution or
automatic plugin relink.
Portability currently targets Linux/POSIX. The text representation is independent
of byte order and locale and requires IEEE binary32 floats. IDs used by the graph
must fit the receiving host's size_t; a 64-bit file is not guaranteed to fit a
32-bit build. Paths follow POSIX semantics; Windows drive letters and separators
are not translated. Relative media can travel with their project folder, while
absolute references and symlink targets must be restored separately. LADSPA
modules are absolute installed references: match architecture, ABI, descriptor
identity, ports and legal controls on the destination. Plugin binaries and state
are not bundled. JACK availability, sample rate, block limits and device routing
are runtime dependencies; loading metadata does not restore device connections.
Portability currently targets Linux/POSIX. The text representation is
independent of byte order and locale and requires IEEE binary32 floats. IDs used
by the graph must fit the receiving host's size_t; a 64-bit file is not
guaranteed to fit a 32-bit build. Paths follow POSIX semantics; Windows drive
letters and separators are not translated. Relative media can travel with their
project folder, while absolute references and symlink targets must be restored
separately. LADSPA modules are absolute installed references: match
architecture, ABI, descriptor identity, ports and legal controls on the
destination. Plugin binaries and state are not bundled. JACK availability,
sample rate, block limits and device routing are runtime dependencies; loading
metadata does not restore device connections.
`make test` includes v1–v10 migration, malformed/truncated/mutated/oversized input,
retained-state diagnostics, CLI dependency inspection, and deterministic save
faults at flush, file sync, close, rename and directory sync. Pre-rename faults
preserve the old bytes and remove the temporary. After rename, a sync failure
reports I/O while leaving a complete new document; inspect it before retrying.
Fault wrappers are linked into tests only. These tests complement killed-writer
coverage in `make test-recovery`; they do not qualify power-cut behavior.
`make test` includes v1–v10 migration, malformed/truncated/mutated/oversized
input, retained-state diagnostics, CLI dependency inspection, and deterministic
save faults at flush, file sync, close, rename and directory sync. Pre-rename
faults preserve the old bytes and remove the temporary. After rename, a sync
failure reports I/O while leaving a complete new document; inspect it before
retrying. Fault wrappers are linked into tests only. These tests complement
killed-writer coverage in `make test-recovery`; they do not qualify power-cut
behavior.
## Version 11: plugin format identity
---
## Version 11: Plugin Format Identity
Immediately after every plugin MODULE record, v11 requires:
@ -345,29 +388,34 @@ Immediately after every plugin MODULE record, v11 requires:
FORMAT format_number hex_identity_or_dash
```
0 = LADSPA, 1 = CLAP, 2 = VST2, 3 = VST3. Identity is `-` for LADSPA/VST2;
CLAP uses its stable plugin ID (1–127 non-NUL bytes); VST3 uses the 32-character lowercase
hex class ID string, itself hex-encoded like other byte strings. PLUGIN's unique
ID remains the VST2/LADSPA integer ID (zero for CLAP/VST3), descriptor index and
label. MODULE is still the actual absolute binary path. Versions 1–10 migrate
to format 0 and empty extra identity. Older readers reject v11. The public plugin
struct has grown; C/C++ consumers must rebuild and initialize it to zero.
0 = LADSPA, 1 = CLAP, 2 = VST2, 3 = VST3. Identity is `-` for LADSPA/VST2; CLAP
uses its stable plugin ID (1–127 non-NUL bytes); VST3 uses the 32-character
lowercase hex class ID string, itself hex-encoded like other byte strings.
PLUGIN's unique ID remains the VST2/LADSPA integer ID (zero for CLAP/VST3),
descriptor index and label. MODULE is still the actual absolute binary path.
Versions 1–10 migrate to format 0 and empty extra identity. Older readers reject
v11. The public plugin struct has grown; C/C++ consumers must rebuild and
initialize it to zero.
Modern adapters accept one-input mono effects, default configuration only, with
zero controls and automation points. Unsupported modern control metadata is
rejected, not discarded. State/editor/preset changes are not exposed, so no opaque
state is serialized. Reloading recreates plugin defaults, which can change after
a plugin update or with its external configuration. Matching ID/name/layout is
not a binary hash or sound-reproducibility guarantee. See PLUGIN_HOSTING.md.
rejected, not discarded. State/editor/preset changes are not exposed, so no
opaque state is serialized. Reloading recreates plugin defaults, which can
change after a plugin update or with its external configuration. Matching
ID/name/layout is not a binary hash or sound-reproducibility guarantee. See
PLUGIN_HOSTING.md.
Metadata validation and inspection never initialize modules. Actual graph
preparation verifies format, descriptor, stable identity, name and layout before
publishing. Missing module metadata survives C load/save; GUI preparation retains
the existing document on failure. No automatic path search or plugin substitution
occurs. Current and historical migration fixtures, malformed fields, identity
mismatch, missing-module and exact-render roundtrips cover this extension.
publishing. Missing module metadata survives C load/save; GUI preparation
retains the existing document on failure. No automatic path search or plugin
substitution occurs. Current and historical migration fixtures, malformed
fields, identity mismatch, missing-module and exact-render roundtrips cover this
extension.
## Version 12: track instruments
---
## Version 12: Track Instruments
After CONTROL_MAPS/MAP records and before END:
@ -389,25 +437,32 @@ modules; preparing playback verifies binary identity and configuration. Deleting
tracks removes bindings; reordering preserves stable track identities. Undo
snapshots include bindings. Public clients must rebuild for the new spec field.
## Version 13 internal devices
---
## Version 13 Internal Devices
Format value 4 identifies dependency-free internal devices at `/xtc/internal`.
Indices 0/1 bind fixed Soft Keys/Sub Bass instruments; 2/3 are Delay/Reverb
effects with three controls and existing automation records. Identity and control
ranges are validated without loading libraries. See [BUILTIN_DEVICES.md](BUILTIN_DEVICES.md).
effects with three controls and existing automation records. Identity and
control ranges are validated without loading libraries. See
[BUILTIN_DEVICES.md](BUILTIN_DEVICES.md).
## Version 14 instrument parameters
---
## Version 14 Instrument Parameters
Each track INSTRUMENT identity is followed by `INSTRUMENT_CONTROLS count`,
`CONTROL port value_bits` records, `INSTRUMENT_AUTOMATION count` and
`PARAM tick port mode value_bits` records. Bounds, duplicate points and metadata
are validated before destination replacement, just as for graph effects.
Earlier bindings have no controls/automation; v13 internal instruments retain
fixed default sounds. Modern external instruments still require zero controls.
are validated before destination replacement, just as for graph effects. Earlier
bindings have no controls/automation; v13 internal instruments retain fixed
default sounds. Modern external instruments still require zero controls.
Instrument tempo follows project tempo. Instrument preset files use this same
schema with exactly one instrument track and no clips; they are metadata only.
## Version 15 sample references
---
## Version 15 Sample References
Each instrument parameter block ends with `SAMPLE hex_path_or_dash`. Only the
internal Sample Player (index 4) may carry a nonempty sample path. Paths within
@ -415,19 +470,23 @@ the project directory are relative on disk and resolve against the project
file's directory; other paths are absolute. Metadata loading does not read WAVs.
Missing/invalid samples remain references and become reported silence during
preparation. Controls describe root key, whole-file loop, attack, release and
gain. Earlier projects have empty sample paths. Rebuild public API consumers
for the additional copied `sample_path` field in `xtc_plugin_spec`.
gain. Earlier projects have empty sample paths. Rebuild public API consumers for
the additional copied `sample_path` field in `xtc_plugin_spec`.
## Version 16: arrangement markers
---
## Version 16: Arrangement Markers
After `LIVE_INSTRUMENT` and before `END`, version 16 writes `MARKERS count`
followed by `MARKER tick hex_name` records. Count is 0..128; ticks must be strictly
increasing and at most MAX_TICK. Names use the existing bounded UTF-8 hex encoding.
Versions 1–15 migrate to an empty marker list. Older builds reject version 16.
Markers are copied by snapshots and history; the tick is their stable key.
followed by `MARKER tick hex_name` records. Count is 0..128; ticks must be
strictly increasing and at most MAX_TICK. Names use the existing bounded UTF-8
hex encoding. Versions 1–15 migrate to an empty marker list. Older builds reject
version 16. Markers are copied by snapshots and history; the tick is their
stable key.
---
## Version 17: per-track insert chains
## Version 17: per-Track Insert Chains
After MARKERS/MARKER and before END, the writer emits a chain for every track,
in dense track order. Earlier versions migrate to empty chains with unity gain.
@ -448,16 +507,16 @@ PARAM tick port step value_bits
Each CHAIN has exactly slot_count INSERT blocks. CONTROLS/PARAMETERS determine
the subsequent CONTROL/PARAM record counts, including zero. Slots are ordered,
at most eight, and accept mono non-instrument effects only. Enabled is 0/1,
wet is finite 0–1, output gain finite 0–4; inputs must be 1. The format values
are LADSPA=0, CLAP=1, VST2=2, VST3=3 and Internal=4. Existing plugin specification
at most eight, and accept mono non-instrument effects only. Enabled is 0/1, wet
is finite 0–1, output gain finite 0–4; inputs must be 1. The format values are
LADSPA=0, CLAP=1, VST2=2, VST3=3 and Internal=4. Existing plugin specification
validation applies, including descriptor/identity/port/automation bounds and
20,000–300,000 milli-BPM. Bus chains must be empty. No sample reference is
permitted on an effect. Duplicate, missing, reordered or excessive chain
records, malformed strings and nonfinite values reject the whole load.
Loading and dependency inspection only validate metadata; no plugin module
is opened. Enabled plugins are verified when a render plan is prepared. Chain
Loading and dependency inspection only validate metadata; no plugin module is
opened. Enabled plugins are verified when a render plan is prepared. Chain
preset files reuse this schema with exactly one non-bus track and no clips or
instrument. Track insert APIs are declared in `xtc_plugin.h`; see
[TRACK_INSERTS.md](TRACK_INSERTS.md) for playback restrictions and workflows.

View file

@ -1,44 +1,51 @@
# Persistence and failure recovery
# Persistence and Failure Recovery
The project format is v12. Readers migrate v1–v11, validate bounded input
and atomically install a candidate only after complete success. Existing tests
cover exact migration/roundtrip, truncations, malformed references, counts,
IDs, nonfinite values and failed GUI/backend installation. Native processors
are session-only; saving their graph returns UNSUPPORTED rather than writing
process addresses or an unreadable project. See PROJECT_FORMAT.md.
The project format is v12. Readers migrate v1–v11, validate bounded input and
atomically install a candidate only after complete success. Existing tests cover
exact migration/roundtrip, truncations, malformed references, counts, IDs,
nonfinite values and failed GUI/backend installation. Native processors are
session-only; saving their graph returns UNSUPPORTED rather than writing process
addresses or an unreadable project. See PROJECT_FORMAT.md.
Project Save writes a mode-0600 same-directory `NAME.tmp.XXXXXX`, flushes and
fsyncs it, closes and renames it over the destination. Before rename, handled
failures remove the temporary and retain the destination. POSIX rename gives
complete old-or-new destination contents during process failure. The containing directory is fsynced after rename. If that sync fails, Save
reports I/O failure but the complete new file may already be present. Preserve
and validate it before retrying; filesystem/hardware durability still varies. Symlinks are
complete old-or-new destination contents during process failure. The containing
directory is fsynced after rename. If that sync fails, Save reports I/O failure
but the complete new file may already be present. Preserve and validate it
before retrying; filesystem/hardware durability still varies. Symlinks are
replaced rather than followed at rename; original file permissions are not kept.
Recording similarly writes/finalizes a same-directory temporary, then renames
it. Stop must synchronize callbacks before draining/finalizing. Ring overflow
returns an explicit error and may finalize a partial take for manual recovery;
I/O failure retains the previous destination. A killed recorder does not finish
its WAV header. Source WAV files remain external; references inside the project folder are
saved relative to it. Save As does not collect them. Back up the project and assets together.
its WAV header. Source WAV files remain external; references inside the project
folder are saved relative to it. Save As does not collect them. Back up the
project and assets together.
## After a crash
---
## After A Crash
1. Preserve the destination and any `.tmp.*` files before attempting repair.
2. Run `xtc-check --validate project.xtc` for metadata validation without a GUI,
display or audio server. Run `xtc-check --inspect project.xtc` to list resolved
media/module paths and plugin identities: status 2 means missing/unreadable
references, 1 means invalid metadata/I/O, 0 means files are available. Neither
command verifies file contents, plugin initialization or realtime preparation.
3. A valid destination can be opened normally. If only a temporary contains useful
data, copy it to a separate candidate and validate that copy before replacing
anything. Partial project files are rejected; there is no salvage parser.
4. An unfinished WAV temporary may have a zero/incorrect data length and needs
a separate recovery tool or manual header repair on a copy. xtc does not guess
display or audio server. Run `xtc-check --inspect project.xtc` to list
resolved media/module paths and plugin identities: status 2 means
missing/unreadable references, 1 means invalid metadata/I/O, 0 means files
are available. Neither command verifies file contents, plugin initialization
or realtime preparation.
3. A valid destination can be opened normally. If only a temporary contains
useful data, copy it to a separate candidate and validate that copy before
replacing anything. Partial project files are rejected; there is no salvage
parser.
4. An unfinished WAV temporary may have a zero/incorrect data length and needs a
separate recovery tool or manual header repair on a copy. xtc does not guess
its intended length or automatically rename orphan temporaries.
5. Missing assets preserve silent clips. Use Relink selected audio, then Play to
revalidate. For server loss/format changes, Stop/Reconnect and explicit routing
are required. Panic/F9 clears held notes. Failed close retains ownership for retry.
revalidate. For server loss/format changes, Stop/Reconnect and explicit
routing are required. Panic/F9 clears held notes. Failed close retains
ownership for retry.
There is no autosave, crash journal, automatic orphan recovery or persisted Undo
history. Existing finalized files are protected against the tested process-level
@ -51,18 +58,22 @@ draining but before finalization. Complete old/new destinations and explicit
orphan temporaries are verified. It does not cut power, terminate the shared
audio server, simulate a failing filesystem, or qualify disk/device endurance.
## Retained recording prefixes
---
## Retained Recording Prefixes
Recording now defaults to no-clobber publication; the GUI requires an unused
path. Failed nonempty temporary recordings survive destruction. Query the
recorder recovery path before destruction, or use the GUI's reported path.
`xtc-check --recover-recording TEMP NEW.wav` reconstructs a canonical mono
float32 WAV from complete on-disk frames, ignoring a trailing partial frame.
It never changes the source or overwrites an existing destination. This
supersedes the earlier lack of a WAV recovery tool described above. See
AUDIO_FILES.md for format/ownership/size limits and `make test-recording`.
float32 WAV from complete on-disk frames, ignoring a trailing partial frame. It
never changes the source or overwrites an existing destination. This supersedes
the earlier lack of a WAV recovery tool described above. See AUDIO_FILES.md for
format/ownership/size limits and `make test-recording`.
## Plugin load and replacement failures
---
## Plugin Load and Replacement Failures
GUI Open reports an unavailable/incompatible plugin's label and absolute module
path and retains the current document. Preserve the saved file; restore a
@ -77,27 +88,32 @@ ID only for the same audio layout. New defaults replace controls/automation;
Undo restores the original configuration. Failed discovery/compile/instantiate
retains the original graph, controls and history position. Cancel in the generic
parameter editor discards its temporary values. Reset defaults clears automation
only when applied. No host recovery can contain a crash or hang in trusted native
plugin code; LADSPA has no runtime error-return channel.
only when applied. No host recovery can contain a crash or hang in trusted
native plugin code; LADSPA has no runtime error-return channel.
## Save diagnostics
---
`make test-project-diagnostics` deterministically injects flush, file-sync, close,
rename and directory-sync errors. Pre-rename failures retain the previous bytes
and clean temporary files. Post-rename sync errors leave a complete new file and
return failure; there is no rollback after replacement. Load diagnostics identify
the expected record and syntax line, or line zero for whole-model/I/O stages.
Unknown versions report the supported range (1–12); unknown records/fields are
rejected without partially changing the current project.
## Save Diagnostics
`make test-project-diagnostics` deterministically injects flush, file-sync,
close, rename and directory-sync errors. Pre-rename failures retain the previous
bytes and clean temporary files. Post-rename sync errors leave a complete new
file and return failure; there is no rollback after replacement. Load
diagnostics identify the expected record and syntax line, or line zero for
whole-model/I/O stages. Unknown versions report the supported range (1–12);
unknown records/fields are rejected without partially changing the current
project.
Modern CLAP/VST2/VST3 module identity is preserved in v11 metadata even when the
binary is unavailable. Restore the compatible saved binary path and retry Open.
CLAP restart and VST2 I/O-change requests fail/silence subsequent blocks; stop
and rebuild/reload rather than attempting callback-time reconfiguration. Modern
instances restart at default configuration; no edited opaque state is exposed
or saved by the initial adapters. Trusted plugin crashes cannot be contained.
instances restart at default configuration; no edited opaque state is exposed or
saved by the initial adapters. Trusted plugin crashes cannot be contained.
## Instrument support
---
## Instrument Support
MIDI tracks can bind CLAP, VST2 and VST3 instruments. In the Track menu, select
**Load instrument plugin** for the selected sequencer track; **Use built-in
@ -108,9 +124,9 @@ their own track's instrument. Independent instances feed track gain/pan,
mute/solo, bus routing and automation. Bindings/live target save in v12.
Supply the actual trusted Linux binary (including the inner .so of a VST3
bundle). Supported instruments have zero audio inputs, one mono output,
float32 processing, zero latency and one note/event input. Default configuration
only: no custom editor, opaque state/preset restoration, modern parameter
bundle). Supported instruments have zero audio inputs, one mono output, float32
processing, zero latency and one note/event input. Default configuration only:
no custom editor, opaque state/preset restoration, modern parameter
editing/automation, stereo/multichannel instruments or MIDI output. No arbitrary
third-party instrument is qualified by the original ABI fixture tests.

View file

@ -1,50 +1,60 @@
# 0.1.0 development release notes
# 0.1.0 Development Release Notes
This remains a development prototype, not a production-qualified DAW. The local
release workflow is complete without bumping the application version, creating a
tag or publishing artifacts. The exact source archive SHA-256 and
reviewed Git commit identify a build; version 0.1.0 alone does not identify all
of the development revisions that used that version.
tag or publishing artifacts. The exact source archive SHA-256 and reviewed Git
commit identify a build; version 0.1.0 alone does not identify all of the
development revisions that used that version.
## Included functionality
---
The C99 engine provides bounded graphs, immutable plan publication, stereo audio,
MIDI instruments and controller bindings, track/bus mixing, automation and inline
MIDI editing. The C++17 FLTK application adds arrangement, routing, recording,
undo/redo and a generic LADSPA editor. JACK is the only live audio/MIDI backend;
core validation and offline rendering work without it. Native/LADSPA processors
are trusted in-process code. No proprietary energyXT resources are included.
## Included Functionality
Project format v13 reads versions 1–13 transactionally. Syntax diagnostics identify
records and lines. `xtc-check --inspect` lists dependency paths without executing
plugins. Timing/CPU/RSS reports, repeatable stress runs and virtual endurance are
available. Configurable install directories, shared install/remove inventory,
installed MIDI examples and source archive checksum sidecars are also included.
The C99 engine provides bounded graphs, immutable plan publication, stereo
audio, MIDI instruments and controller bindings, track/bus mixing, automation
and inline MIDI editing. The C++17 FLTK application adds arrangement, routing,
recording, undo/redo and a generic LADSPA editor. JACK is the only live
audio/MIDI backend; core validation and offline rendering work without it.
Native/LADSPA processors are trusted in-process code. No proprietary energyXT
resources are included.
## Upgrading and removal
Project format v13 reads versions 1–13 transactionally. Syntax diagnostics
identify records and lines. `xtc-check --inspect` lists dependency paths without
executing plugins. Timing/CPU/RSS reports, repeatable stress runs and virtual
endurance are available. Configurable install directories, shared install/remove
inventory, installed MIDI examples and source archive checksum sidecars are also
included.
---
## Upgrading and Removal
Back up documents and external media before upgrading. Opening an older project
does not rewrite it; saving upgrades it to v12, which older readers may reject.
Use Save As for a separate migrated copy. Moving a project folder preserves media
stored inside it via relative references. Absolute media and plugin module paths
must be restored separately. There is no bundled plugin/media migration or saved
JACK port configuration. See PROJECT_FORMAT.md and RECOVERY.md for exact policies.
Use Save As for a separate migrated copy. Moving a project folder preserves
media stored inside it via relative references. Absolute media and plugin module
paths must be restored separately. There is no bundled plugin/media migration or
saved JACK port configuration. See PROJECT_FORMAT.md and RECOVERY.md for exact
policies.
Install to the same layout to repair owned files. Only documented owned filenames
are replaced; unrelated files with other names remain. Use the same source
version and directory variables for uninstall. It removes owned files and empty
owned directories, never recordings or recursively deleting a prefix. A changed
layout or an older release with a different inventory may require removing that
old version before installing the new one. No database/package-manager registration
or automatic cleanup of historical inventories is performed.
Install to the same layout to repair owned files. Only documented owned
filenames are replaced; unrelated files with other names remain. Use the same
source version and directory variables for uninstall. It removes owned files and
empty owned directories, never recordings or recursively deleting a prefix. A
changed layout or an older release with a different inventory may require
removing that old version before installing the new one. No
database/package-manager registration or automatic cleanup of historical
inventories is performed.
## Known qualification limits
---
## Known Qualification Limits
Physical devices, multi-hour wall-clock endurance, power-cut behavior and other
Linux distributions remain unqualified. There is no disk streaming, crash autosave,
plugin sandbox, latency compensation or stable shared-library ABI. A trusted
plugin can allocate, block or crash the process. Metadata inspection checks file
availability only. See KNOWN_LIMITATIONS.md for the complete list.
Linux distributions remain unqualified. There is no disk streaming, crash
autosave, plugin sandbox, latency compensation or stable shared-library ABI. A
trusted plugin can allocate, block or crash the process. Metadata inspection
checks file availability only. See KNOWN_LIMITATIONS.md for the complete list.
The retained graph performance gate has a documented small-case timing alert on
the shared development host; the project timing comparison passed. This alert
@ -53,56 +63,60 @@ measurements as latency expectations. Sanitizers and virtual endurance qualify
software paths, not hardware timing.
Default-configuration CLAP/VST2/VST3 mono effect loaders, common GUI
discovery/insertion/replacement and v11 format identity are supported. Pinned MIT CLAP and
BSD-3-Clause VST3 C headers ship with notices; VST2 uses original compatibility
declarations. Modern instruments, parameters/state/editors and multichannel
layouts remain unsupported. No third-party modern plugin has been qualified.
discovery/insertion/replacement and v11 format identity are supported. Pinned
MIT CLAP and BSD-3-Clause VST3 C headers ship with notices; VST2 uses original
compatibility declarations. Modern instruments, parameters/state/editors and
multichannel layouts remain unsupported. No third-party modern plugin has been
qualified.
CLAP/VST2/VST3 mono instruments are supported, bound to MIDI tracks, with
sample-offset notes, a saved live target, track mixing, GUI assignment/undo and
v12 persistence.
See PLUGIN_HOSTING.md for compatibility and controller/state limits.
v12 persistence. See PLUGIN_HOSTING.md for compatibility and controller/state
limits.
A flat DAW workspace, shared dark theme, track-creation sidebar, read-only
musical transport/track inspector, colored track clips and real MIDI previews are
included. New MIDI/audio track commands are undoable and prepare candidates
before adoption. The default window is 1280 × 800, minimum 1220 × 720.
No dependency or project-format change. See WORKSPACE.md and the new
musical transport/track inspector, colored track clips and real MIDI previews
are included. New MIDI/audio track commands are undoable and prepare candidates
before adoption. The default window is 1280 × 800, minimum 1220 × 720. No
dependency or project-format change. See WORKSPACE.md and the new
`modern-session.xtc` example; existing engine/plugin limitations still apply.
A sequencer grid/Snap dropdown, absolute grid snapping for moves and trims,
MIDI double-click editing, a piano velocity lane/slider and per-track instrument
A sequencer grid/Snap dropdown, absolute grid snapping for moves and trims, MIDI
double-click editing, a piano velocity lane/slider and per-track instrument
dropdowns are included. Snap preferences are UI state; velocity and instrument
bindings use the existing v12 format. No new dependency or engine feature is
required. See WORKSPACE.md for supported divisions, controller semantics and limits.
required. See WORKSPACE.md for supported divisions, controller semantics and
limits.
Edit / Preferences provides an Apply button and `~/.xtc.conf` INI persistence,
configurable plugin search paths and a file library, plus sequencer ruler loop
selection and sample-boundary playback wrapping. See PREFERENCES.md
for settings, reconnect behavior and looping limits.
selection and sample-boundary playback wrapping. See PREFERENCES.md for
settings, reconnect behavior and looping limits.
The single-track mixer panel is replaced by a scrolling studio mixer: vertical
dB faders, flat pan knobs, mute/solo, routing and stereo meters on every track and
bus, plus a pinned master monitor. Selected-track automation remains
below the strips. No project format or engine changes. Screenshots are refreshed;
see MIXER.md and screenshots/README.md.
dB faders, flat pan knobs, mute/solo, routing and stereo meters on every track
and bus, plus a pinned master monitor. Selected-track automation remains below
the strips. No project format or engine changes. Screenshots are refreshed; see
MIXER.md and screenshots/README.md.
Searchable Commands (Ctrl+P), editable project BPM, a live track inspector,
session statistics, Fit song and an optional persistent Follow playhead control
are included. Numeric input remains intact during display refreshes.
Screenshots are refreshed, including the command palette. See WORKSPACE.md.
are included. Numeric input remains intact during display refreshes. Screenshots
are refreshed, including the command palette. See WORKSPACE.md.
Dependency-free Soft Keys/Sub Bass instruments and Delay/Reverb effects are
included, with saved controls and automation. Choose instruments in track headers;
insert effects on selected graph wires from Edit. See [BUILTIN_DEVICES.md](BUILTIN_DEVICES.md).
included, with saved controls and automation. Choose instruments in track
headers; insert effects on selected graph wires from Edit. See
[BUILTIN_DEVICES.md](BUILTIN_DEVICES.md).
## Audio source editing
---
Audio clips now open a modal waveform editor with native frame selection,
zoom, source statistics, nondestructive timeline trims, selection export and
save-copy adoption. Local one-step undo supports reverse, polarity inversion,
DC removal, linear/equal-power fades and gain/normalization. Silence boundaries,
## Audio Source Editing
Audio clips now open a modal waveform editor with native frame selection, zoom,
source statistics, nondestructive timeline trims, selection export and save-copy
adoption. Local one-step undo supports reverse, polarity inversion, DC removal,
linear/equal-power fades and gain/normalization. Silence boundaries,
zero-crossing snapping and amplitude attack markers support transactional clip
slicing. Originals are never overwritten; exported media remains available to
project undo/redo. Source editing uses the existing mono downmix and bounded

View file

@ -1,10 +1,13 @@
# Release preparation (0.1.0 development prototype)
# Release Preparation (0.1.0 Development Prototype)
Source packaging, staged install/uninstall and repeatable builds are testable. It does not turn the prototype into a production-qualified DAW. The
version remains 0.1.0; no tag, external release or binary publication is created
by `make dist` or `make release`.
Source packaging, staged install/uninstall and repeatable builds are testable.
It does not turn the prototype into a production-qualified DAW. The version
remains 0.1.0; no tag, external release or binary publication is created by
`make dist` or `make release`.
## Build and package
---
## Build and Package
```sh
make release
@ -24,63 +27,75 @@ a source manifest; stage intended files first and check `git diff --cached`.
Untracked files, ignored build output and .git are excluded. Exported archives
can reproduce themselves without Git using SOURCE_MANIFEST.json.
Archive order, permissions, ownership, gzip filename and timestamps are normalized.
SOURCE_DATE_EPOCH defaults to 0; set an unsigned 32-bit epoch explicitly if desired.
The archive does not claim to identify a commit: release from a clean checkout
and record its commit/hash externally. Do not treat a dirty-tree archive as an
attested release. No artifact is uploaded by these targets.
Archive order, permissions, ownership, gzip filename and timestamps are
normalized. SOURCE_DATE_EPOCH defaults to 0; set an unsigned 32-bit epoch
explicitly if desired. The archive does not claim to identify a commit: release
from a clean checkout and record its commit/hash externally. Do not treat a
dirty-tree archive as an attested release. No artifact is uploaded by these
targets.
test-release unpacks the source into two different temporary paths, runs release
in each, compares libxtc.a/xtc-check/optional xtc SHA-256 values, and repacks each
export to compare archive bytes. This verifies reproducibility for the same host,
compiler, flags and dependency versions. It does not promise bit-identical output
across distributions, compilers or library updates. Compiler/debug/sanitizer
build directories are intentionally separate; changing toolchain/flags in place
requires a clean directory. Python release tests require version 3.12+.
in each, compares libxtc.a/xtc-check/optional xtc SHA-256 values, and repacks
each export to compare archive bytes. This verifies reproducibility for the same
host, compiler, flags and dependency versions. It does not promise bit-identical
output across distributions, compilers or library updates.
Compiler/debug/sanitizer build directories are intentionally separate; changing
toolchain/flags in place requires a clean directory. Python release tests
require version 3.12+.
## Qualification record
---
## Qualification Record
Run normal core/GUI and live software JACK tests, no-JACK builds and ASan/UBSan.
GUI leak tests retain the narrow fontconfig suppression; core tests do not.
TSan recorder/publication tests complement lifecycle tests. Native API tests
verify callback failures, cleanup and retirement; they cannot certify arbitrary
GUI leak tests retain the narrow fontconfig suppression; core tests do not. TSan
recorder/publication tests complement lifecycle tests. Native API tests verify
callback failures, cleanup and retirement; they cannot certify arbitrary
extensions. See BUILD.md and tests/integration/README.md for commands.
Record versions, archive hashes, installation/consumer checks and measured timing.
Use LICENSES.md for dependency/license review and PLUGIN_HOSTING.md for the
external-hosting decision. Review RECOVERY.md for migration and process-crash
semantics. Physical devices, power loss, sustained recording/performance, user
acceptance on other distributions and binary redistribution are still separate
qualification work. There is no DEB/RPM/AppImage, bundled runtime, autosave,
external plugin host or stable shared-library extension ABI in this release.
Record versions, archive hashes, installation/consumer checks and measured
timing. Use LICENSES.md for dependency/license review and PLUGIN_HOSTING.md for
the external-hosting decision. Review RECOVERY.md for migration and
process-crash semantics. Physical devices, power loss, sustained
recording/performance, user acceptance on other distributions and binary
redistribution are still separate qualification work. There is no
DEB/RPM/AppImage, bundled runtime, autosave, external plugin host or stable
shared-library extension ABI in this release.
## Local binary packaging and installed workflow
---
## Local Binary Packaging and Installed Workflow
`make test-package` builds the release, stages installation only in a temporary
DESTDIR, writes a normalized Linux binary/development archive, checks its SHA-256
sidecar and per-file manifest, relocatable pkg-config paths and headless commands.
Same-host repeated packaging is byte-identical; dependency versions and toolchain
still determine binary compatibility. `make test-install` now repeats installation
to verify repair/upgrade of owned files while preserving an unrelated sentinel,
then compiles both C99 and C++17 installed consumers that create MIDI, save/reopen
and compare nonzero exact rendering. Removal remains exact and idempotent.
DESTDIR, writes a normalized Linux binary/development archive, checks its
SHA-256 sidecar and per-file manifest, relocatable pkg-config paths and headless
commands. Same-host repeated packaging is byte-identical; dependency versions
and toolchain still determine binary compatibility. `make test-install` now
repeats installation to verify repair/upgrade of owned files while preserving an
unrelated sentinel, then compiles both C99 and C++17 installed consumers that
create MIDI, save/reopen and compare nonzero exact rendering. Removal remains
exact and idempotent.
USER_MANUAL.md consolidates operation; KNOWN_LIMITATIONS.md lists qualification
gaps; MAINTENANCE.md defines the release checklist, regression and compatibility
policy. Build diagnostics make no server/plugin calls. There is no asynchronous
crash uploader or signal-handler cleanup: inspect OS core/debugger evidence on
the control side, preserving project/media recovery paths. No release is publicly
published by these commands, and human/device/listening checks remain pending.
the control side, preserving project/media recovery paths. No release is
publicly published by these commands, and human/device/listening checks remain
pending.
## Release workflow
---
Installation now shares one explicit file inventory with uninstallation. GNU-style
`exec_prefix`, `bindir`, `libdir`, `includedir`, `datarootdir`, `datadir`, `docdir`,
`pkgconfigdir` and the xtc-specific `licensedir`, `examplesdir`, `desktopdir` can be
overridden alongside `PREFIX`/`DESTDIR`. Paths must be absolute POSIX paths;
spaces are supported. Do not include DESTDIR in a directory variable: staging is
applied separately and never embedded in pkg-config metadata. Staging refuses
parent symlinks that escape DESTDIR. Use exactly the same variables for removal.
## Release Workflow
Installation now shares one explicit file inventory with uninstallation.
GNU-style `exec_prefix`, `bindir`, `libdir`, `includedir`, `datarootdir`,
`datadir`, `docdir`, `pkgconfigdir` and the xtc-specific `licensedir`,
`examplesdir`, `desktopdir` can be overridden alongside `PREFIX`/`DESTDIR`.
Paths must be absolute POSIX paths; spaces are supported. Do not include DESTDIR
in a directory variable: staging is applied separately and never embedded in
pkg-config metadata. Staging refuses parent symlinks that escape DESTDIR. Use
exactly the same variables for removal.
```sh
make BUILD_DIR=build/release-check all test gui
@ -90,18 +105,20 @@ make PREFIX=/usr DESTDIR=/tmp/xtc-stage libdir=/usr/lib64 uninstall
make test-release test-package
```
Core install includes the static library, public headers, `xtc-check`, pkg-config
metadata, Markdown documentation, all checked-in performance reports, license and
`share/xtc/examples` (a media/plugin-free MIDI demo). GUI install adds the desktop
entry and `xtc`. No desktop MIME association is claimed. Package archives keep
their fixed `/usr` layout and relocatable pkg-config metadata; custom live layouts
are tested separately. Existing owned file symlinks are replaced, not followed;
installation is atomic per file, not a transaction over the entire prefix.
Core install includes the static library, public headers, `xtc-check`,
pkg-config metadata, Markdown documentation, all checked-in performance reports,
license and `share/xtc/examples` (a media/plugin-free MIDI demo). GUI install
adds the desktop entry and `xtc`. No desktop MIME association is claimed.
Package archives keep their fixed `/usr` layout and relocatable pkg-config
metadata; custom live layouts are tested separately. Existing owned file
symlinks are replaced, not followed; installation is atomic per file, not a
transaction over the entire prefix.
`make dist` now writes a `.sha256` sidecar as well as the normalized source archive.
Record that hash, the reviewed commit, version and build/dependency conditions
with release logs. `test-release` verifies the hash, builds/tests two fresh source
exports and compares artifacts and repacks. It validates exported tracked content,
including staged work; it does not attest that the caller's Git tree is clean.
For public release use a clean reviewed checkout and inspect `git status` first.
RELEASE_NOTES.md supplies upgrade guidance and remaining qualification limits.
`make dist` now writes a `.sha256` sidecar as well as the normalized source
archive. Record that hash, the reviewed commit, version and build/dependency
conditions with release logs. `test-release` verifies the hash, builds/tests two
fresh source exports and compares artifacts and repacks. It validates exported
tracked content, including staged work; it does not attest that the caller's Git
tree is clean. For public release use a clean reviewed checkout and inspect
`git status` first. RELEASE_NOTES.md supplies upgrade guidance and remaining
qualification limits.

View file

@ -1,10 +1,12 @@
# Engine correctness and reliability
# Engine Correctness and Reliability
The existing engine was audited rather than extended with musical features. The
review covers graph edits/compilation, offline DSP, plan publication, JACK
callbacks/lifecycle, audio/MIDI source plans and the recorder handoff.
## Ownership and synchronization
---
## Ownership and Synchronization
| Resource | Owner and lifetime |
| --- | --- |
@ -24,7 +26,9 @@ The public realtime API is SPSC: multiple producers, simultaneous process calls,
collection from arbitrary threads, and destruction during processing are not
supported. Atomics do not remove those ownership requirements.
## Bounded callback work
---
## Bounded Callback Work
Compilation, topological sorting, asset I/O, allocation and native preparation
are control-thread operations. Graph processing traverses at most 256 nodes,
@ -50,13 +54,15 @@ callbacks are trusted: the host cannot prevent their allocation, blocking,
crashes or excessive runtime. Maximum supported capacities are safety bounds,
not a promise that every workload meets its audio deadline.
## Findings and regression coverage
---
## Findings and Regression Coverage
Graph rendering previously mixed the left output before reading right-channel
sources. With external input equal to the left output, the right pass could
read already processed samples (gain applied twice). Both passes now finish
source reads before either output is written. Tests check mono input aliasing
and cross-channel source aliasing against exact expected samples. Output buffers
sources. With external input equal to the left output, the right pass could read
already processed samples (gain applied twice). Both passes now finish source
reads before either output is written. Tests check mono input aliasing and
cross-channel source aliasing against exact expected samples. Output buffers
must not overlap each other. A redundant assignment in missing-asset preparation
was also removed after Clang's dead-store diagnostic; behavior is unchanged.
@ -67,20 +73,23 @@ that failed replacement preserves output and revisions, and counts both stereo
native instances through retirement, collection and destruction of a pending
plan. It repeats this lifecycle 100 times. A maximum 256-node reverse-insertion
chain checks dependency scheduling, long-cycle rejection/rollback and compiled
snapshot independence. The pthread handoff test submits 2000 native graphs
while processing, checks whole-block output/revision consistency, asserts that
snapshot independence. The pthread handoff test submits 2000 native graphs while
processing, checks whole-block output/revision consistency, asserts that
prepare/destruction run on the control thread, and balances all native lifetimes
after joining the consumer.
Existing backend fault tests cover repeated start/stop, partial registration,
callback setup, open/activate/deactivate/close failures and retries, server loss,
rate/buffer changes, missing buffers, MIDI disconnect/panic, and recorder detach.
Other suites cover deterministic variable-block DSP, finite-output handling,
project errors, record-ring saturation and concurrent draining, and crash/write
failure preservation. No critical realtime safety defect remains known within
these contracts; these tests are evidence for tested paths, not a formal proof.
callback setup, open/activate/deactivate/close failures and retries, server
loss, rate/buffer changes, missing buffers, MIDI disconnect/panic, and recorder
detach. Other suites cover deterministic variable-block DSP, finite-output
handling, project errors, record-ring saturation and concurrent draining, and
crash/write failure preservation. No critical realtime safety defect remains
known within these contracts; these tests are evidence for tested paths, not a
formal proof.
## Reproducing the audit
---
## Reproducing the Audit
From the repository root:
@ -92,22 +101,22 @@ make BUILD_DIR=build/audit test-gui test-gui-jack test-jack test-jack-audio test
make BUILD_DIR=build/nojack PKG_CONFIG=false all test-reliability gui
```
`test-sanitize` needs installed Clang and runs core, allocation-failure and recovery
tests with ASan/UBSan and Linux's default ASan leak checking. `analyze` uses
Clang's path-sensitive analyzer on every core translation unit, including JACK
when its development files are available, and fails on diagnostics. Python and
Clang are development tools, not new runtime dependencies. The allocation guard
requires a GNU-compatible linker. Live tests require an existing JACK-compatible
server; GUI tests use installed FLTK and Xvfb. No target installs dependencies or
changes server/system configuration.
`test-sanitize` needs installed Clang and runs core, allocation-failure and
recovery tests with ASan/UBSan and Linux's default ASan leak checking. `analyze`
uses Clang's path-sensitive analyzer on every core translation unit, including
JACK when its development files are available, and fails on diagnostics. Python
and Clang are development tools, not new runtime dependencies. The allocation
guard requires a GNU-compatible linker. Live tests require an existing
JACK-compatible server; GUI tests use installed FLTK and Xvfb. No target
installs dependencies or changes server/system configuration.
Verification on GCC 16.2.1 / Clang 23.1.3 passed normal and no-JACK builds,
all core tests, ASan/UBSan/leak checks, Clang static analysis, GUI/mock and live
Verification on GCC 16.2.1 / Clang 23.1.3 passed normal and no-JACK builds, all
core tests, ASan/UBSan/leak checks, Clang static analysis, GUI/mock and live
JACK integrations, plus GCC TSan on the pthread native-publication and recorder
concurrency tests. The GUI also passed ASan/UBSan with the existing
`tests/integration/lsan.supp` fontconfig-only suppression; core tests use no leak
suppression. The live graph test observed both replacement gains, ten restart
cycles and zero xruns during its five-second software run.
`tests/integration/lsan.supp` fontconfig-only suppression; core tests use no
leak suppression. The live graph test observed both replacement gains, ten
restart cycles and zero xruns during its five-second software run.
Physical audio devices, hard realtime scheduling, multi-hour endurance and
arbitrary third-party native callbacks remain unqualified. Sanitizers change
@ -115,7 +124,9 @@ timing and should not be used to measure production realtime performance. See
PERFORMANCE.md for workload measurements, RECOVERY.md for process-crash limits,
and NATIVE_API.md for the trusted extension boundary.
## Transition ownership
---
## Transition Ownership
Source fade configuration is written before publication and immutable during
rendering. The master ramp and stop-observed flag belong to the audio consumer
@ -126,12 +137,15 @@ voices/flags. A completed callback continues silence/holds position until that
deactivation. No callback waits or calls JACK control functions. Immediate
stop/close handles faults or missing callbacks. See AUDIO_QUALITY.md.
## Repeatable development checks
---
`make check-development` combines normal tests, capacity/endurance, deterministic
benchmark-gate tests, static analysis and ASan/UBSan (including scale and soak).
`make test-stress` repeats project/backend/publication faults under CPU contention;
`make test-stress-plugins` adds the original LADSPA failure fixture. The reliability
callback guard now also rejects common host file I/O and blocking mutex/condition
calls. It audits directly linked host code, not arbitrary dynamic-plugin internals.
See PERFORMANCE.md for exact workloads, metrics, profiles and qualification limits.
## Repeatable Development Checks
`make check-development` combines normal tests, capacity/endurance,
deterministic benchmark-gate tests, static analysis and ASan/UBSan (including
scale and soak). `make test-stress` repeats project/backend/publication faults
under CPU contention; `make test-stress-plugins` adds the original LADSPA
failure fixture. The reliability callback guard now also rejects common host
file I/O and blocking mutex/condition calls. It audits directly linked host
code, not arbitrary dynamic-plugin internals. See PERFORMANCE.md for exact
workloads, metrics, profiles and qualification limits.

View file

@ -1,15 +1,15 @@
# Modular routing workflow
# Modular Routing Workflow
Open Modular Graph (Ctrl+3). Select a node or wire to inspect it in the feedback
line. Nodes have stable IDs, role labels and distinct colors: teal sources,
purple gain/bus nodes, blue processors/meters and red output sums. LADSPA nodes
also show the saved plugin label. Track names remain in Sequencer/Mixer; the
Audio mix and MIDI mix graph sources represent their shared mix buffers, not
individual tracks. MIDI mix is the internal sine-instrument mix; its graph
ports carry audio, not MIDI events. Native nodes are trusted extensions.
individual tracks. MIDI mix is the internal sine-instrument mix; its graph ports
carry audio, not MIDI events. Native nodes are trusted extensions.
Wires show arrows from right-hand audio outputs to left-hand inputs. Selecting
a node highlights all its incoming/outgoing wires; selecting a wire shows its
Wires show arrows from right-hand audio outputs to left-hand inputs. Selecting a
node highlights all its incoming/outgoing wires; selecting a wire shows its
endpoint IDs. Inputs sum all connected signals without normalization; fan-out
sends the same signal to each destination. Multiple output nodes sum into the
engine output. Gain/bus nodes are ordinary summing gain processors, not a new
@ -31,6 +31,8 @@ path is not a substitute for stopping playback or muting a track. The graph
models one audio lane per port; stereo compilation processes paired independent
lanes. It does not expose separate left/right, MIDI or control wires.
---
## Commands
| Action | Operation |
@ -58,11 +60,13 @@ allocates a fresh stable node ID and is **one** semantic edit/undo step. It
preserves all other edges. Failure (including capacity, a missing/stale wire or
backend refusal) keeps the previous graph and plan. It introduces no new node
type or project record: v8 persistence already represents the resulting graph.
One-level stereo mix buses are added before shared graph sources (see
MIXER.md). Track templates, folders and independent per-track/bus graph inserts
remain deferred because graph sources still expose shared mixes.
One-level stereo mix buses are added before shared graph sources (see MIXER.md).
Track templates, folders and independent per-track/bus graph inserts remain
deferred because graph sources still expose shared mixes.
## Transactions, cycles and realtime ownership
---
## Transactions, Cycles and Realtime Ownership
The public C `xtc_graph_apply_edit` command constructs a private candidate.
`XTC_EDIT_INSERT_GAIN` uses `data.connection` and returns the fresh ID through
@ -77,34 +81,37 @@ The GUI clones its project and compiles/publishes the exact candidate through
the existing backend before replacing the displayed model/history. When running,
a complete immutable plan is adopted at a block boundary; the control thread
collects acknowledged retired plans. The feedback line distinguishes prepared,
queued and active revisions. A queued graph can appear before it becomes audible.
The single pending slot can reject a busy edit with CAPACITY: wait for adoption
and retry. There is no hidden unbounded edit queue or callback mutation. Normal
graph edits can run during playback; undo/redo uses the existing stopped-plan
installation contract. Recording still blocks semantic graph edits. Plan swaps
may reset oscillator/plugin state and are not crossfaded.
queued and active revisions. A queued graph can appear before it becomes
audible. The single pending slot can reject a busy edit with CAPACITY: wait for
adoption and retry. There is no hidden unbounded edit queue or callback
mutation. Normal graph edits can run during playback; undo/redo uses the
existing stopped-plan installation contract. Recording still blocks semantic
graph edits. Plan swaps may reset oscillator/plugin state and are not
crossfaded.
`xtc_graph_analyze` computes input/output edge counts, longest-path level,
rendered-output reachability and external-input fallback in dense-node order.
It is a bounded, allocation-free **control-thread** API, not a callback service.
It never loads plugins. It honors explicit outputs; legacy terminal fallback
rendered-output reachability and external-input fallback in dense-node order. It
is a bounded, allocation-free **control-thread** API, not a callback service. It
never loads plugins. It honors explicit outputs; legacy terminal fallback
applies only when there are no explicit output nodes and the caller has not
required them. Errors leave result storage/count unchanged. The GUI caches this
analysis when binding a graph; drawing reads cached metadata only. It uses
public C snapshots/commands and never engine internals or callback storage.
## Verification and limits
---
## Verification and Limits
`make test-routing` checks branch reachability, external input, levels, capacity
rollback, exact unity-insertion rendering and complete plan handoff. `make test`
also retains concurrent publication/lifecycle tests.
`make test-reliability` retains the callback-allocation guards.
`make test-gui-plugins` checks selection diagnostics, unique placement/layout,
failed backend publication, atomic insertion undo/redo and keyboard focus.
`make test-gui-jack` inserts/disconnects/restores real routes during live software
playback and checks engine revision acknowledgements and cycle rejection.
also retains concurrent publication/lifecycle tests. `make test-reliability`
retains the callback-allocation guards. `make test-gui-plugins` checks selection
diagnostics, unique placement/layout, failed backend publication, atomic
insertion undo/redo and keyboard focus. `make test-gui-jack`
inserts/disconnects/restores real routes during live software playback and
checks engine revision acknowledgements and cycle rejection.
Physical routing/listening and hardware endurance remain unqualified. This is
an audio-lane DAG editor with a default 64-node project capacity (standalone
C graphs up to 256), no zoom/lasso or persisted layout, no latency compensation
Physical routing/listening and hardware endurance remain unqualified. This is an
audio-lane DAG editor with a default 64-node project capacity (standalone C
graphs up to 256), no zoom/lasso or persisted layout, no latency compensation
and no plugin crash isolation. Unconnected branches are diagnosed, not pruned.

View file

@ -1,8 +1,8 @@
# Track inserts
# Track Inserts
The sequencer's **Create** dropdown groups track creation, WAV import and
instrument loading. **View → Track inspector** opens the optional inspector;
it is hidden when the application starts to leave more room for editing.
instrument loading. **View → Track inspector** opens the optional inspector; it
is hidden when the application starts to leave more room for editing.
Project format 17 stores up to eight effect slots per track, their order,
bypass, wet amount and chain output gain. Formats 1–16 read with empty chains.
@ -17,39 +17,41 @@ chain; plugin identity and layout are checked when preparing playback.
Playback prepares an independent serial graph for each affected track. Audio
clips on the same track sum before its effects; instrument and scheduled MIDI
contributions also pass through their track graph. Effects run before track
and bus gain/pan, and meters observe their output. Wet/dry uses parallel gain
contributions also pass through their track graph. Effects run before track and
bus gain/pan, and meters observe their output. Wet/dry uses parallel gain
branches. Internal effect history resets at timeline discontinuities.
Each renderer owns its plans and preallocated raw buffers (up to 256 KiB per
affected track); processing uses blocks of at most 256 frames, with no host
allocation or I/O. Mono effect adapters require zero reported latency. External
plugin state on seeks follows the existing host contract. Bus inserts and
tracks containing both audio assets and MIDI reject preparation explicitly.
Live unassigned sine monitoring remains a separate master input.
plugin state on seeks follows the existing host contract. Bus inserts and tracks
containing both audio assets and MIDI reject preparation explicitly. Live
unassigned sine monitoring remains a separate master input.
Open **View → Track inserts** (or Ctrl+5 for the selected track) and choose a track. **Add effect** offers
internal Delay/Reverb and the plugin library. The left rack shows signal order;
the graph displays the actual generated graph compiled by playback. Wiring in
this view is read-only: edit the rack to preserve the serial chain. The global
Modular Graph remains independent. A failed plugin probe or playback preparation
preserves the current project; successful additions are undoable.
Open **View → Track inserts** (or Ctrl+5 for the selected track) and choose a
track. **Add effect** offers internal Delay/Reverb and the plugin library. The
left rack shows signal order; the graph displays the actual generated graph
compiled by playback. Wiring in this view is read-only: edit the rack to
preserve the serial chain. The global Modular Graph remains independent. A
failed plugin probe or playback preparation preserves the current project;
successful additions are undoable.
The library searches configured Preferences paths recursively (including VST3
bundle contents), with case-insensitive search and format filters. LADSPA and
VST2 both use `.so`, so those filters share candidates until inspection identifies
the ABI. Listing files executes no plugin code. Only inspect trusted binaries.
VST2 both use `.so`, so those filters share candidates until inspection
identifies the ABI. Listing files executes no plugin code. Only inspect trusted
binaries.
Select a slot in the rack, then use **Up**, **Down**, **Bypass** or **Remove**.
The selection follows reordered effects. Bypassed slots retain their settings
and appear marked in the rack; their nodes are omitted from the compiled graph.
Each successful operation creates one project undo step.
**Inspect / edit** verifies the selected plugin and opens its identity and generic
controls. Reset defaults clears parameter automation on Apply. Cancel leaves the
project unchanged. Internal effects and LADSPA expose controls; modern adapters
currently expose none and run default configurations. Native plugin editor
windows and opaque plugin-state chunks are not implemented.
**Inspect / edit** verifies the selected plugin and opens its identity and
generic controls. Reset defaults clears parameter automation on Apply. Cancel
leaves the project unchanged. Internal effects and LADSPA expose controls;
modern adapters currently expose none and run default configurations. Native
plugin editor windows and opaque plugin-state chunks are not implemented.
**Preset → Save chain / Load chain** reuses the atomic versioned project file
container with one track. Order, bypass, wet amounts, output gain and exposed
@ -67,19 +69,19 @@ insert layouts, latency compensation and gapless live insert edits are future
work. Editing stops transport before preparing the replacement plan.
The CLI `xtc-check --inspect project.xtc` lists insert format, slot order,
bypass/wet settings and missing binary references without loading plugins.
VST3 processing supplies a fixed output-parameter sink (64 IDs, final point per
bypass/wet settings and missing binary references without loading plugins. VST3
processing supplies a fixed output-parameter sink (64 IDs, final point per
ID/block) for plugins that emit meter/parameter notifications; notifications are
not recorded or exposed as controls.
Host qualification on 2026-11-06 used the installed Linux x86_64 ZamEQ2,
ZamAutoSat and ZamDelay VST3 binaries under `/usr/lib/vst3`. All three were
prepared together, rendered with nonzero finite output and exported to WAV.
This verifies those default mono configurations, not arbitrary plugins or
physical hardware. The repository fixtures cover LADSPA, CLAP, VST2 and VST3
insert adoption and VST3 output notification bounds. Internal-plan allocation
failure and callback guards, sanitizers, GUI/JACK lifecycle and install checks
cover host ownership and cleanup.
prepared together, rendered with nonzero finite output and exported to WAV. This
verifies those default mono configurations, not arbitrary plugins or physical
hardware. The repository fixtures cover LADSPA, CLAP, VST2 and VST3 insert
adoption and VST3 output notification bounds. Internal-plan allocation failure
and callback guards, sanitizers, GUI/JACK lifecycle and install checks cover
host ownership and cleanup.
Reproduce focused verification with:

View file

@ -1,36 +1,44 @@
# xtc user manual (0.1.0 development build)
# xtc User Manual (0.1.0 Development Build)
xtc is a bounded Linux music-making prototype. Start with a copy of an important
project and keep backups of its WAV media. Supported limits and unfinished areas
are listed in KNOWN_LIMITATIONS.md; no production/device qualification is implied.
are listed in KNOWN_LIMITATIONS.md; no production/device qualification is
implied.
## Build or install
---
## Build or Install
Follow BUILD.md and LINUX.md. Core: `make all test`; GUI: `make gui`, then
`./build/xtc`. Use an existing JACK-compatible server and reconnect with F6.
Connect `midi_in` to a controller, `input` to mono capture and `output` /
`output_right` to left/right playback using your JACK routing application.
No server, physical routing or system configuration is changed automatically.
`output_right` to left/right playback using your JACK routing application. No
server, physical routing or system configuration is changed automatically.
`xtc --version`, `xtc --help` and `xtc --diagnose` need no display/server.
For a source install stage with `make install-gui DESTDIR="$PWD/build/stage"
PREFIX=/usr`, inspect the stage, and use your normal deployment process. A user
prefix is supported (for example an absolute path to your `.local` folder).
`make uninstall` with the same DESTDIR/PREFIX removes only that version's known
files. See LINUX.md for package extraction, dependencies and removal. Never use
a release built for a different system ABI without rebuilding/testing it.
For a source install stage with
`make install-gui DESTDIR="$PWD/build/stage" PREFIX=/usr`, inspect the stage,
and use your normal deployment process. A user prefix is supported (for example
an absolute path to your `.local` folder). `make uninstall` with the same
DESTDIR/PREFIX removes only that version's known files. See LINUX.md for package
extraction, dependencies and removal. Never use a release built for a different
system ABI without rebuilding/testing it.
## Make a short project
---
## Make A Short Project
1. New/Open starts in Sequencer. Add track; use F2 to name it.
2. Add MIDI clip, select it and open Piano roll (Ctrl+4). Left-click to draw
notes, drag to move, drag a right edge to resize, Delete/right-click to remove.
notes, drag to move, drag a right edge to resize, Delete/right-click to
remove.
3. Alternatively Import WAV (Ctrl+I). It creates/routes an Audio clips source;
supported WAV formats and preload limits are in AUDIO_FILES.md.
4. Drag clips to arrange them and edges to trim. View/Snap sets the timeline
grid; +/- zoom buttons and scrollbars navigate. Ctrl+D duplicates a clip after
its end, retaining its complete source. F2 names clips; right-click offers
duplicate/rename/delete. Alt+Up/Down with timeline focus reorders tracks.
grid; +/- zoom buttons and scrollbars navigate. Ctrl+D duplicates a clip
after its end, retaining its complete source. F2 names clips; right-click
offers duplicate/rename/delete. Alt+Up/Down with timeline focus reorders
tracks.
5. Play/F5 starts; Pause/F7 or Stop/F8 leaves the position for resuming. Home
returns to start while stopped (or stops first); click the ruler to seek.
Recording uses Stop, not Pause. F9 Panic releases the instrument.
@ -38,22 +46,27 @@ a release built for a different system ABI without rebuilding/testing it.
Click to add points, right-click to remove. CONTROL.md explains timing and
live CC7 volume, CC10 pan and CC64 sustain.
7. Save (Ctrl+S), close, reopen and continue editing. Ctrl+Z undoes and
Ctrl+Shift+Z/Ctrl+Y redoes. New/Open resets history after unsaved-change handling.
Ctrl+Shift+Z/Ctrl+Y redoes. New/Open resets history after unsaved-change
handling.
Modular Graph (Ctrl+3) exposes routing. Drag output to input to connect, select a
wire/node and Delete to remove; cycles and invalid ports are rejected. Keep an
Modular Graph (Ctrl+3) exposes routing. Drag output to input to connect, select
a wire/node and Delete to remove; cycles and invalid ports are rejected. Keep an
Output route for playback. GUI changes show queued/active graph status. Graph
positions are session-only; music/routing/automation are saved. Edits that prepare
new plans stop playback; disk/compilation may pause the GUI, never run in audio.
positions are session-only; music/routing/automation are saved. Edits that
prepare new plans stop playback; disk/compilation may pause the GUI, never run
in audio.
## Record and retain media
---
Choose a project folder and save the project there first. Record/Shift+F5 asks for
an unused WAV path. Stop finalizes the take and inserts it. Existing destinations
are protected, including a file created while recording. Undo/removing a take
removes its reference, never the recorded file. Imported external files are also
never deleted. WAVs inside the save folder become relative references; move the
folder and media together. Save As preserves the same source media, not a copy.
## Record and Retain Media
Choose a project folder and save the project there first. Record/Shift+F5 asks
for an unused WAV path. Stop finalizes the take and inserts it. Existing
destinations are protected, including a file created while recording.
Undo/removing a take removes its reference, never the recorded file. Imported
external files are also never deleted. WAVs inside the save folder become
relative references; move the folder and media together. Save As preserves the
same source media, not a copy.
On write failure the GUI reports a retained path. Keep it and recover complete
frames to a different, unused destination:
@ -62,62 +75,74 @@ frames to a different, unused destination:
xtc-check --recover-recording take.wav.tmp.XXXXXX recovered.wav
```
Only xtc's canonical float32 mono temporary format is salvaged. Incomplete trailing
frames are omitted. The source is retained. Import/relink the recovered WAV.
Missing/unreadable media is silent and reported; select the clip and use File/
Relink selected audio. See RECOVERY.md for interruption, disk-full and power-loss
limits. Close/project operations are refused while recording; finish the take first.
Only xtc's canonical float32 mono temporary format is salvaged. Incomplete
trailing frames are omitted. The source is retained. Import/relink the recovered
WAV. Missing/unreadable media is silent and reported; select the clip and use
File/ Relink selected audio. See RECOVERY.md for interruption, disk-full and
power-loss limits. Close/project operations are refused while recording; finish
the take first.
## Trusted effects and sources
---
File/Load LADSPA plugin lists compatible descriptor indices from the chosen `.so`.
Loading executes native code. Select a documented compatible mono plugin, connect
its graph ports, then use Edit/Selected plugin control. Edit/Selected plugin
automation adds/removes tick-based linear or step points. Control names/ranges
appear in the dialogs. Module identity/control values/lanes save with the project;
module binaries and DSP history do not. The initial amp/inverter/sine set and
unsupported layouts are listed in PLUGIN_HOSTING.md. Missing plugins reject GUI
Open with identity/path information and preserve the saved/current documents.
Restore the compatible module at that path and retry. Plugins are not sandboxed.
## Trusted Effects and Sources
## Diagnose a problem
File/Load LADSPA plugin lists compatible descriptor indices from the chosen
`.so`. Loading executes native code. Select a documented compatible mono plugin,
connect its graph ports, then use Edit/Selected plugin control. Edit/Selected
plugin automation adds/removes tick-based linear or step points. Control
names/ranges appear in the dialogs. Module identity/control values/lanes save
with the project; module binaries and DSP history do not. The initial
amp/inverter/sine set and unsupported layouts are listed in PLUGIN_HOSTING.md.
Missing plugins reject GUI Open with identity/path information and preserve the
saved/current documents. Restore the compatible module at that path and retry.
Plugins are not sandboxed.
Run `xtc-check --diagnose` and capture the version, architecture, compiled features,
GUI error text and steps to reproduce. `xtc-check --validate song.xtc` checks
metadata without opening plugins/media; it does not certify playback resources.
Check JACK routing, sample rate/format and xrun/process status in the GUI. After
server loss/rate change stop/reconnect and retry. Keep the original project,
media and retained temporary files before recovery attempts.
---
A native plugin can crash/hang the process. There is no crash-isolation guarantee.
Use the operating system's core-dump/debugger tools if available; xtc does not
install signal handlers, change core limits or upload crash reports. Debugger/
core files may contain media paths and audio memory; share only what you intend.
Report bugs following MAINTENANCE.md, including whether the issue reproduces
without plugins. Physical listening and independent human install/usability
acceptance remain pending.
## Diagnose A Problem
## MIDI composition
Run `xtc-check --diagnose` and capture the version, architecture, compiled
features, GUI error text and steps to reproduce. `xtc-check --validate song.xtc`
checks metadata without opening plugins/media; it does not certify playback
resources. Check JACK routing, sample rate/format and xrun/process status in the
GUI. After server loss/rate change stop/reconnect and retry. Keep the original
project, media and retained temporary files before recovery attempts.
A native plugin can crash/hang the process. There is no crash-isolation
guarantee. Use the operating system's core-dump/debugger tools if available; xtc
does not install signal handlers, change core limits or upload crash reports.
Debugger/ core files may contain media paths and audio memory; share only what
you intend. Report bugs following MAINTENANCE.md, including whether the issue
reproduces without plugins. Physical listening and independent human
install/usability acceptance remain pending.
---
## MIDI Composition
In Piano roll, use the Notes menu for grid (including triplets), channel/pitch
filters, velocity/channel, quantization, transposition and pattern repetition.
Click selects a single note; Ctrl+A selects all filtered notes. Ctrl+D repeats
that selection at a chosen spacing within the existing clip source. Up/Down
transposes by a semitone. Commands are undoable through the main Edit menu.
See [MIDI.md](MIDI.md#composition-commands) for shortcuts, timing and limits.
transposes by a semitone. Commands are undoable through the main Edit menu. See
[MIDI.md](MIDI.md#composition-commands) for shortcuts, timing and limits.
## Audio clip tools
---
## Audio Clip Tools
Select an audio clip, click the ruler at the desired cut while stopped, then
choose Edit / Split clip at cursor (S with timeline focus). Ctrl+D duplicates;
Delete removes the clip reference. Drag the body to move and edges to trim.
Edit / Audio clip gain and fades (A) sets linear gain and fade durations in ticks.
Delete removes the clip reference. Drag the body to move and edges to trim. Edit
/ Audio clip gain and fades (A) sets linear gain and fade durations in ticks.
All are undoable and preserve the WAV file. Waveforms show cached raw source
peaks, before gain/fades; large media can take time to load on Open/import.
See [AUDIO_FILES.md](AUDIO_FILES.md#non-destructive-clip-editing) for
boundary behavior, preview limits and the deferred stretch/pitch tools.
peaks, before gain/fades; large media can take time to load on Open/import. See
[AUDIO_FILES.md](AUDIO_FILES.md#non-destructive-clip-editing) for boundary
behavior, preview limits and the deferred stretch/pitch tools.
## Inspecting and organizing routing
---
## Inspecting and Organizing Routing
In Modular Graph, select a node or wire to inspect its routing in the feedback
line. Arrows show direction; yellow no-output badges identify disconnected
@ -126,48 +151,54 @@ unity gain/bus node as one undoable edit. These commands are also in View/Edit
and the graph context menu. Unwired effect inputs use external audio, as shown
by “ext in”; they do not imply silence. See [ROUTING.md](ROUTING.md).
The Mixer also offers Add bus and a Route choice for ordinary tracks.
Select Track/Bus strips to edit their controls and automation. Master and selected
strip meters show dBFS with 20 dB/s release; Reset peaks clears latched clipping.
See MIXER.md for solo rules, gain staging and meter taps.
The Mixer also offers Add bus and a Route choice for ordinary tracks. Select
Track/Bus strips to edit their controls and automation. Master and selected
strip meters show dBFS with 20 dB/s release; Reset peaks clears latched
clipping. See MIXER.md for solo rules, gain staging and meter taps.
File → Load LADSPA plugin offers a descriptor browser. Select a graph
plugin and Edit → Selected plugin control for identity, values, ranges and
Reset defaults; Apply commits, Cancel discards. Edit also offers replacement,
reset and insertion of an effect on a selected wire. Graph wires determine
processing order; Delete removes a selected plugin and its wires. Changes are
undoable. Replacement keeps compatible audio wiring but resets plugin controls
and automation. See PLUGIN_HOSTING.md for supported formats and failure behavior.
File → Load LADSPA plugin offers a descriptor browser. Select a graph plugin and
Edit → Selected plugin control for identity, values, ranges and Reset defaults;
Apply commits, Cancel discards. Edit also offers replacement, reset and
insertion of an effect on a selected wire. Graph wires determine processing
order; Delete removes a selected plugin and its wires. Changes are undoable.
Replacement keeps compatible audio wiring but resets plugin controls and
automation. See PLUGIN_HOSTING.md for supported formats and failure behavior.
Mixer Interpolation selects Linear or Hold for each point added or replaced. Audio → MIDI controller mapping lists all channel bindings; enter
Mixer Interpolation selects Linear or Hold for each point added or replaced.
Audio → MIDI controller mapping lists all channel bindings; enter
`channel volume_CC pan_CC sustain_CC` (channels 0..15, 128 disables, enabled CCs
must be distinct, 120/123 reserved). Maps are undoable and saved. Controllers
modify live sine voices; arranged track/bus/plugin automation takes precedence
over its static controls and is unaffected by those MIDI bindings. See CONTROL.md.
over its static controls and is unaffected by those MIDI bindings. See
CONTROL.md.
Unavailable main-menu undo/redo actions are disabled, including while recording. Piano roll has Edit/Undo/Redo shortcuts and a right-click note menu;
right-click no longer immediately deletes notes. Graph right-click targets the
node/wire under the pointer. Undo preserves surviving selections. Completed
takes add one undo step; undo removes references and keeps WAV files. See
EDITING.md for grouped commands, failure handling and keyboard/focus behavior.
Unavailable main-menu undo/redo actions are disabled, including while recording.
Piano roll has Edit/Undo/Redo shortcuts and a right-click note menu; right-click
no longer immediately deletes notes. Graph right-click targets the node/wire
under the pointer. Undo preserves surviving selections. Completed takes add one
undo step; undo removes references and keeps WAV files. See EDITING.md for
grouped commands, failure handling and keyboard/focus behavior.
Project troubleshooting: `xtc-check --validate PROJECT.xtc` reports metadata
errors with the expected record and syntax line. `xtc-check --inspect PROJECT.xtc`
lists resolved media/module paths and plugin identities without executing plugins
or starting JACK. Exit codes: 0 = valid metadata and available files, 2 = missing
or unreadable references, 1 = metadata/I/O error. File availability does not
certify WAV contents or plugin compatibility. See [PROJECT_FORMAT.md](PROJECT_FORMAT.md)
for the compatibility and relocation policy.
errors with the expected record and syntax line.
`xtc-check --inspect PROJECT.xtc` lists resolved media/module paths and plugin
identities without executing plugins or starting JACK. Exit codes: 0 = valid
metadata and available files, 2 = missing or unreadable references, 1 =
metadata/I/O error. File availability does not certify WAV contents or plugin
compatibility. See [PROJECT_FORMAT.md](PROJECT_FORMAT.md) for the compatibility
and relocation policy.
**File → Load plugin** supports LADSPA plus an initial Linux
CLAP/VST2/VST3 effect subset. Select the actual module binary (`.clap` or `.so`,
including a VST3 bundle's inner binary). Modern effects require one mono input
and output, float32 and zero latency; they use default configuration. Instruments,
modern parameter editing/automation, opaque state and custom editors are not
implemented. Projects now save v12 and read v1–v12. See PLUGIN_HOSTING.md for the
full contract and test/qualification limits.
**File → Load plugin** supports LADSPA plus an initial Linux CLAP/VST2/VST3
effect subset. Select the actual module binary (`.clap` or `.so`, including a
VST3 bundle's inner binary). Modern effects require one mono input and output,
float32 and zero latency; they use default configuration. Instruments, modern
parameter editing/automation, opaque state and custom editors are not
implemented. Projects now save v12 and read v1–v12. See PLUGIN_HOSTING.md for
the full contract and test/qualification limits.
## Instrument support
---
## Instrument Support
MIDI tracks can bind CLAP, VST2 and VST3 instruments. In the Track menu, select
**Load instrument plugin** for the selected sequencer track; **Use built-in
@ -178,9 +209,9 @@ their own track's instrument. Independent instances feed track gain/pan,
mute/solo, bus routing and automation. Bindings/live target save in v12.
Supply the actual trusted Linux binary (including the inner .so of a VST3
bundle). Supported instruments have zero audio inputs, one mono output,
float32 processing, zero latency and one note/event input. Default configuration
only: no custom editor, opaque state/preset restoration, modern parameter
bundle). Supported instruments have zero audio inputs, one mono output, float32
processing, zero latency and one note/event input. Default configuration only:
no custom editor, opaque state/preset restoration, modern parameter
editing/automation, stereo/multichannel instruments or MIDI output. No arbitrary
third-party instrument is qualified by the original ABI fixture tests.
@ -198,27 +229,32 @@ the callback and old instances are reclaimed on the control thread. Missing
references remain inspectable without loading; failed GUI preparation retains
the current project. Save a backup for applications limited to v11 or earlier.
---
## Workspace
The flat workspace puts New MIDI/audio track, WAV import and instrument loading
in a persistent sidebar, with a musical transport display and selected-track
overview. See [WORKSPACE.md](WORKSPACE.md) for shortcuts and workflow. Open
`modern-session.xtc` in the installed examples for an original four-track arrangement.
`modern-session.xtc` in the installed examples for an original four-track
arrangement.
Use the sequencer Snap dropdown for straight/triplet/bar grids or Off.
Double-click MIDI clips to edit notes, then use the Velocity lane or selected-note
slider for dynamics. Each non-bus track header has an instrument dropdown offering
built-in sine, project instruments and explicit plugin loading. See WORKSPACE.md.
Double-click MIDI clips to edit notes, then use the Velocity lane or
selected-note slider for dynamics. Each non-bus track header has an instrument
dropdown offering built-in sine, project instruments and explicit plugin
loading. See WORKSPACE.md.
Additive clip selection, atomic group commands, phrase clipboard and repeat,
alignment/slip tools, MIDI swing/gate/legato/scale/velocity transforms, batch
audio fades and static mixer A/B recall are also available. See
[MODERN_EDITING.md](MODERN_EDITING.md) for commands, scope and limitations.
See [arrangement and composition workflows](COMPOSITION_WORKFLOWS.md) for markers, MIDI composition, normalization and portable media collection.
See [arrangement and composition workflows](COMPOSITION_WORKFLOWS.md) for
markers, MIDI composition, normalization and portable media collection.
Select an audio clip and open View / Sample editor (or double-click it) to inspect
source samples, choose native frame ranges, trim the clip or export a new WAV.
Processing and one-level local undo remain in the editor until Save full copy and
relink. See [AUDIO_EDITOR.md](AUDIO_EDITOR.md) for all sample tools, transient
slicing, musical-tick rounding and source preservation limits.
Select an audio clip and open View / Sample editor (or double-click it) to
inspect source samples, choose native frame ranges, trim the clip or export a
new WAV. Processing and one-level local undo remain in the editor until Save
full copy and relink. See [AUDIO_EDITOR.md](AUDIO_EDITOR.md) for all sample
tools, transient slicing, musical-tick rounding and source preservation limits.

View file

@ -1,14 +1,17 @@
# Make and reopen a small project
# Make and Reopen A Small Project
The default workspace is Sequencer. New and Open return to this workspace;
Mixer and Modular Graph remain available with Ctrl+2 and Ctrl+3. Start with an
empty project at 120 BPM, 4/4. Clips default to one bar and piano-roll notes use
The default workspace is Sequencer. New and Open return to this workspace; Mixer
and Modular Graph remain available with Ctrl+2 and Ctrl+3. Start with an empty
project at 120 BPM, 4/4. Clips default to one bar and piano-roll notes use
velocity 100; no notes or test oscillators are inserted automatically.
## Create music
---
1. Start your existing JACK-compatible server, launch `xtc`, and check that audio
says ready/stopped. If disconnected, start the server and use Reconnect/F6.
## Create Music
1. Start your existing JACK-compatible server, launch `xtc`, and check that
audio says ready/stopped. If disconnected, start the server and use
Reconnect/F6.
2. Click Add track, then Add MIDI clip. Click Piano roll (Ctrl+4) and click the
grid to draw notes. Drag notes to move them or their right edges to resize.
Close the piano-roll window to return to the arrangement.
@ -21,14 +24,16 @@ velocity 100; no notes or test oscillators are inserted automatically.
5. MIDI creation supplies an instrument-to-output route, and audio import
supplies an audio-to-output route for a new project. No graph editing or
project-file editing is required. Ctrl+3 shows these routes if you want to
inspect or change them. Existing custom routes are respected; see MIDI.md
and AUDIO_FILES.md for routing limits.
inspect or change them. Existing custom routes are respected; see MIDI.md and
AUDIO_FILES.md for routing limits.
6. In your existing JACK routing tool, connect xtc's `output` and `output_right`
ports to left/right playback inputs. Press Play/F5. Mixer/Ctrl+2 shows output
meters and track gain/pan/mute/solo. If meters move but nothing is audible,
check external playback routing and device levels. xtc does not connect
physical ports automatically or start/configure the server.
---
## Transport
| Command | Behavior |
@ -41,27 +46,30 @@ velocity 100; no notes or test oscillators are inserted automatically.
| Click timeline ruler | Seek on the sixteenth-note grid while stopped; during playback the seek is refused. |
| New / Open / Reconnect | Stop and reset the position. New/Open show Sequencer; Reconnect leaves the document intact. |
Pause/Stop completion is asynchronous: the GUI briefly shows “stopping”. Recording
Stop and Home retain immediate deactivation. See [AUDIO_QUALITY.md](AUDIO_QUALITY.md)
for the 5 ms fade policy and timing bounds.
Pause/Stop completion is asynchronous: the GUI briefly shows “stopping”.
Recording Stop and Home retain immediate deactivation. See
[AUDIO_QUALITY.md](AUDIO_QUALITY.md) for the 5 ms fade policy and timing bounds.
The backend sample count drives the playhead; its display refreshes every 100 ms.
Pause/Stop retain the exact sample position even though the ruler displays ticks.
Arrangement, note and mixer edits stop playback; graph edits can be adopted live.
A failed stop leaves playback running and the controls reflect that. A failed
return-to-start seek does not set the cursor to zero. If candidate project
preparation fails after stopping/seeking, the old document stays loaded and the
GUI refreshes to its actual stopped position (which may be zero). A parse failure
occurs before installation and leaves playback alone.
The backend sample count drives the playhead; its display refreshes every 100
ms. Pause/Stop retain the exact sample position even though the ruler displays
ticks. Arrangement, note and mixer edits stop playback; graph edits can be
adopted live. A failed stop leaves playback running and the controls reflect
that. A failed return-to-start seek does not set the cursor to zero. If
candidate project preparation fails after stopping/seeking, the old document
stays loaded and the GUI refreshes to its actual stopped position (which may be
zero). A parse failure occurs before installation and leaves playback alone.
## Save, close and continue
---
## Save, Close and Continue
Use File/Save (Ctrl+S), choose a `.xtc` path and retain external WAV files. The
unsaved title marker clears on success. File/Save As confirms overwrites.
Close the window, launch xtc again and use File/Open (Ctrl+O), or run
unsaved title marker clears on success. File/Save As confirms overwrites. Close
the window, launch xtc again and use File/Open (Ctrl+O), or run
`xtc /path/to/song.xtc`. The arrangement, stable IDs, routing, notes, mixing and
automation are restored; the transport starts stopped at zero. Continue drawing,
importing or moving clips, use Ctrl+Z/Ctrl+Shift+Z for undo/redo, and save again.
importing or moving clips, use Ctrl+Z/Ctrl+Shift+Z for undo/redo, and save
again.
New/Open/Quit ask before discarding unsaved edits. They are refused while
recording: use Stop to finalize the take, then save. Missing media stays silent
@ -70,7 +78,9 @@ selected audio. Failed file operations retain the document and show an error;
check the path, format and permissions. Server loss or rate changes require
Reconnect; this resets position, so seek again before resuming.
## Repeatable acceptance test
---
## Repeatable Acceptance Test
With installed FLTK/JACK development files, Xvfb and an already running
JACK-compatible server:
@ -80,14 +90,15 @@ make all test gui test-gui test-workflow test-gui-jack test-jack-midi
```
`test-workflow` drives real GUI commands and timeline gestures under Xvfb. It
creates a track, draws two notes, arranges the MIDI clip two beats later, imports
a generated WAV and verifies both automatic source routes. It saves and closes,
destroys the first window, reopens in a fresh window and compares the complete
stereo offline render exactly (including silence, imported audio and notes).
It checks stable clip IDs/positions and graph counts, seeks through the ruler,
plays on the real backend, observes nonzero stereo output meters and advancing
playhead, pauses/resumes, stops, returns to start, edits/undoes/redoes, saves and
reopens again. It uses temporary files only and cleans them on success.
creates a track, draws two notes, arranges the MIDI clip two beats later,
imports a generated WAV and verifies both automatic source routes. It saves and
closes, destroys the first window, reopens in a fresh window and compares the
complete stereo offline render exactly (including silence, imported audio and
notes). It checks stable clip IDs/positions and graph counts, seeks through the
ruler, plays on the real backend, observes nonzero stereo output meters and
advancing playhead, pauses/resumes, stops, returns to start,
edits/undoes/redoes, saves and reopens again. It uses temporary files only and
cleans them on success.
`test-gui` injects stop/seek/prepare failures to verify controls, position and
retained document state, plus recording guards. `test-gui-jack` exercises live
@ -98,34 +109,40 @@ JACK support. Core tests use no GUI leak suppression.
These checks cover a minimal composition workflow, not physical listening or
endurance qualification. No new dependency, project-format change, plugin host,
MIDI capture, tempo editor, loop playback, device-routing GUI, or persisted editor
layout was introduced. See GUI.md, MIDI.md, MIXER.md and LINUX.md for details.
MIDI capture, tempo editor, loop playback, device-routing GUI, or persisted
editor layout was introduced. See GUI.md, MIDI.md, MIXER.md and LINUX.md for
details.
## Editing workflow
---
Select a clip and press Ctrl+D (or Edit/Duplicate clip) to duplicate it immediately
after its end. Complete source notes, trim offsets and media references are
retained with fresh stable IDs. No audio file is copied or overwritten. F2 or
Edit/Rename clip or track names the selection. Clip labels show both kind and
user name. Alt+Up/Down with timeline focus reorders its selected track without
changing IDs, clip membership or routing. Right-click a timeline clip/track for
Duplicate, Rename and Delete. Invalid/capacity edits retain the existing model.
## Editing Workflow
View/Snap selects 1, 120, 240 (default), 480 or 960 ticks. It affects ruler seeks,
clip moves and trims; 1 tick is the finest grid. Existing +/- zoom and scrollbars
retain their behavior. Piano roll has its own configurable grid (see MIDI.md); mixer lanes retain their 240-tick
grid. Grid and zoom are application preferences saved on Apply/clean exit;
selection is session state. These controls are not undoable music edits.
Escape cancels a drag; Delete affects the focused editor; text fields keep their
own editing keys. Commands are named in menus/tooltips and timeline focus accepts
keyboard editing; screen-reader and broad accessibility qualification are pending.
Select a clip and press Ctrl+D (or Edit/Duplicate clip) to duplicate it
immediately after its end. Complete source notes, trim offsets and media
references are retained with fresh stable IDs. No audio file is copied or
overwritten. F2 or Edit/Rename clip or track names the selection. Clip labels
show both kind and user name. Alt+Up/Down with timeline focus reorders its
selected track without changing IDs, clip membership or routing. Right-click a
timeline clip/track for Duplicate, Rename and Delete. Invalid/capacity edits
retain the existing model.
View/Snap selects 1, 120, 240 (default), 480 or 960 ticks. It affects ruler
seeks, clip moves and trims; 1 tick is the finest grid. Existing +/- zoom and
scrollbars retain their behavior. Piano roll has its own configurable grid (see
MIDI.md); mixer lanes retain their 240-tick grid. Grid and zoom are application
preferences saved on Apply/clean exit; selection is session state. These
controls are not undoable music edits. Escape cancels a drag; Delete affects the
focused editor; text fields keep their own editing keys. Commands are named in
menus/tooltips and timeline focus accepts keyboard editing; screen-reader and
broad accessibility qualification are pending.
Create/delete/move/trim/duplicate, names/order, graph connects/deletes, notes,
mixer/automation and plugin edits use the 32-edit snapshot history. Ctrl+Z undoes;
Ctrl+Shift+Z or Ctrl+Y redoes. Backend preparation precedes accepting an undo/redo
snapshot; failure retains its cursor/document. Model edits stop playback before
mutation; compilation/file work occurs on the control thread and may pause the
GUI, never block the audio callback. This is not a background-loading editor.
mixer/automation and plugin edits use the 32-edit snapshot history. Ctrl+Z
undoes; Ctrl+Shift+Z or Ctrl+Y redoes. Backend preparation precedes accepting an
undo/redo snapshot; failure retains its cursor/document. Model edits stop
playback before mutation; compilation/file work occurs on the control thread and
may pause the GUI, never block the audio callback. This is not a
background-loading editor.
A successful recorded take adds a reference as one undoable edit. Undo/removing
its region never deletes the WAV; recording failures retain their recovery path.
@ -133,14 +150,16 @@ There is no destructive media-delete operation. New/Open reset history after
unsaved-change handling. The capacity limit and allocation-error reporting in
MIXER.md still apply. Tests: `make test-editing test-gui-plugins test-workflow`.
## Editing audit
---
## Editing Audit
See [EDITING.md](EDITING.md) for the operation-by-operation undo matrix, command
ownership, allocation-failure boundary, external-media behavior and manual
context-menu/focus procedure. History now reserves storage before edits and
publication, preserving redo when commands fail. Recording start has no empty
undo entry; a completed take is one step and undo never deletes its WAV.
Undo preserves surviving selections and an open piano roll; the piano window
also offers Edit/Undo/Redo. Graph context menus select under the pointer and
undo entry; a completed take is one step and undo never deletes its WAV. Undo
preserves surviving selections and an open piano roll; the piano window also
offers Edit/Undo/Redo. Graph context menus select under the pointer and
piano-roll right-click now opens a menu instead of immediately deleting a note.
Load/import/plugin work paints busy feedback while remaining synchronous.

View file

@ -1,21 +1,24 @@
# Flat DAW workspace
# Flat DAW Workspace
The workspace presents the existing xtc editors in a consistent flat layout. It uses
FLTK vector drawing and system fonts: no images, shadows, bevels, 3D rendering,
external theme packages or new runtime dependencies.
The workspace presents the existing xtc editors in a consistent flat layout. It
uses FLTK vector drawing and system fonts: no images, shadows, bevels, 3D
rendering, external theme packages or new runtime dependencies.
The default window is 1280 × 800; the supported minimum is 1220 × 720. The main
editor grows with the window. Use the scrollbars for longer arrangements and
larger track counts. The sidebar stays available across Sequencer, Mixer and
Modular Graph views. Ctrl+1/2/3 switches those views; Ctrl+4 opens the piano roll.
Modular Graph views. Ctrl+1/2/3 switches those views; Ctrl+4 opens the piano
roll.
## Create and arrange
---
- **New MIDI track** (Ctrl+Shift+M) creates a named track and a one-bar MIDI clip
at the cursor. The new clip and mixer track are selected together. **Edit MIDI
notes** opens its piano roll. Creation is one undo step.
- **New audio track** (Ctrl+Shift+A) creates an empty named track. **Import WAV**
imports into the selected track, or creates one when none is selected.
## Create and Arrange
- **New MIDI track** (Ctrl+Shift+M) creates a named track and a one-bar MIDI
clip at the cursor. The new clip and mixer track are selected together. **Edit
MIDI notes** opens its piano roll. Creation is one undo step.
- **New audio track** (Ctrl+Shift+A) creates an empty named track. **Import
WAV** imports into the selected track, or creates one when none is selected.
- **Load instrument plugin** opens the instrument browser for the selected
sequencer track. Unassigned MIDI tracks retain the built-in sine instrument.
- Existing Add track/region/MIDI clip, duplication, trimming, split, rename and
@ -36,16 +39,18 @@ time visible. The transport display reads **bar : beat : tick** (4/4, 960 ticks
per beat); BPM follows the project tempo. These displays are read-only.
The sidebar inspector follows the sequencer selection, showing track name,
gain/pan, mute/solo and assigned instrument. Use the Mixer for controls, routing,
meters and automation; the sidebar is an overview, not another mixer. Status and
errors remain visible below the editor. Existing engine connection, transport,
recording and keyboard safety behavior is retained.
gain/pan, mute/solo and assigned instrument. Use the Mixer for controls,
routing, meters and automation; the sidebar is an overview, not another mixer.
Status and errors remain visible below the editor. Existing engine connection,
transport, recording and keyboard safety behavior is retained.
## Example and verification
---
## Example and Verification
Open `examples/projects/modern-session.xtc` for an original four-track, four-bar
MIDI arrangement with 16 clips and 128 notes. It needs no media or plugins. It is
also installed with the project examples. Connect JACK playback ports before
MIDI arrangement with 16 clips and 128 notes. It needs no media or plugins. It
is also installed with the project examples. Connect JACK playback ports before
listening. Work on a copy if the installed examples are read-only.
GUI tests exercise sidebar creation, selected clip/editor access, undo/redo,
@ -56,22 +61,24 @@ add commercial-DAW features: detachable/resizable docks, mixer channel strips,
automatic plugin scanning, custom plugin editors and new audio/MIDI features
remain outside this workspace. Existing plugin and engine limits still apply.
## Editing controls
---
The **Snap** dropdown above the sequencer selects Off, 1/32, 1/16 triplet,
1/16, 1/8 triplet, 1/8, 1/4, 1/2 or one bar. Existing View/Snap commands update
the same preference. Bar lines are strongest, beats medium, subdivisions subtle.
At low zoom closely spaced lines are coarsened to at least six pixels; snapping
## Editing Controls
The **Snap** dropdown above the sequencer selects Off, 1/32, 1/16 triplet, 1/16,
1/8 triplet, 1/8, 1/4, 1/2 or one bar. Existing View/Snap commands update the
same preference. Bar lines are strongest, beats medium, subdivisions subtle. At
low zoom closely spaced lines are coarsened to at least six pixels; snapping
still uses the selected division. Clip moves and trim boundaries snap to the
nearest absolute grid position (halfway rounds forward), including clips that
start off the grid. Off removes musical quantization; pointer resolution and
integer model ticks still apply. Invalid trims retain the original clip.
Snap is a UI preference, not a project edit or a saved project setting.
integer model ticks still apply. Invalid trims retain the original clip. Snap is
a UI preference, not a project edit or a saved project setting.
Double-click a MIDI clip with the left mouse button to open its piano roll.
The clip is selected and the click does not move/trim it. Audio/silent clips
retain their existing selection/drag behavior. The piano roll also has a Snap
dropdown for its existing note grid; sequencer and piano snap remain independent.
Double-click a MIDI clip with the left mouse button to open its piano roll. The
clip is selected and the click does not move/trim it. Audio/silent clips retain
their existing selection/drag behavior. The piano roll also has a Snap dropdown
for its existing note grid; sequencer and piano snap remain independent.
The piano roll has a fixed **Velocity** lane beneath the keys and a **Note
velocity** slider. Select a note, then drag its bar vertically or release the
@ -82,25 +89,29 @@ the desired note in the keys first; its highlighted velocity bar and slider
remain individually editable. Horizontal scrolling aligns the lane with note
starts; vertical scrolling moves the keys while the lane stays available.
Filters apply to lane visibility and selection. Values save with existing note
metadata and affect built-in synthesis; plugin patches determine velocity response.
This is MIDI velocity, not a per-note audio gain or loudness meter.
metadata and affect built-in synthesis; plugin patches determine velocity
response. This is MIDI velocity, not a per-note audio gain or loudness meter.
Every ordinary track header has an instrument dropdown. Choose **Built-in sine**,
an instrument already assigned elsewhere in the project, or **Load plugin...**
for a trusted CLAP/VST2/VST3 binary. Entries show the format and plugin label.
Each assignment creates an independent track instance, selects that track for
live MIDI and remains undoable; clear/reorder/reopen use stable track IDs. Bus
tracks show their bus label instead. Cancel or failed preparation restores the
model's selection in the dropdown. The list is project-local, not a scanner or
installed-plugin database. Existing mono/default-configuration host limits apply.
Instrument changes stop/reprepare playback and are refused while recording.
Every ordinary track header has an instrument dropdown. Choose **Built-in
sine**, an instrument already assigned elsewhere in the project, or **Load
plugin...** for a trusted CLAP/VST2/VST3 binary. Entries show the format and
plugin label. Each assignment creates an independent track instance, selects
that track for live MIDI and remains undoable; clear/reorder/reopen use stable
track IDs. Bus tracks show their bus label instead. Cancel or failed preparation
restores the model's selection in the dropdown. The list is project-local, not a
scanner or installed-plugin database. Existing mono/default-configuration host
limits apply. Instrument changes stop/reprepare playback and are refused while
recording.
Verification includes off-grid move/trim, snap Off, dropdown labels, double-click,
coincident notes, per-note slider/lane edits, invalid velocities, undo/reload and
instrument reuse/clear/reorder for all three fixture formats. Manual checks use
the original modern-session example and the flat sequencer/piano windows.
Verification includes off-grid move/trim, snap Off, dropdown labels,
double-click, coincident notes, per-note slider/lane edits, invalid velocities,
undo/reload and instrument reuse/clear/reorder for all three fixture formats.
Manual checks use the original modern-session example and the flat
sequencer/piano windows.
## Studio mixer
---
## Studio Mixer
The Mixer tab now presents all tracks and buses as channel strips with vertical
dB faders, flat pan knobs, mute/solo, output routing and stereo meters. Scroll
@ -109,63 +120,68 @@ Select a channel name to edit its gain/pan automation below the bank. Existing
undo, keyboard controls and project persistence remain available. See
[MIXER.md](MIXER.md) for fader units, metering, automation and limits.
---
## Direct editing and navigation
## Direct Editing and Navigation
**Commands** (Ctrl+P) searches active menu actions by case-insensitive substring.
Type part of an action path such as `mixer`, `import` or `preferences`, use
Up/Down to select, then Enter or **Run command**. Double-click also runs an
entry. Escape or Cancel leaves the project unchanged. The palette closes before
executing the existing menu callback; context and recording guards still apply.
**Commands** (Ctrl+P) searches active menu actions by case-insensitive
substring. Type part of an action path such as `mixer`, `import` or
`preferences`, use Up/Down to select, then Enter or **Run command**.
Double-click also runs an entry. Escape or Cancel leaves the project unchanged.
The palette closes before executing the existing menu callback; context and
recording guards still apply.
The transport **BPM** field edits project tempo from 20 to 300 BPM, stored in
milli-BPM. Enter or release commits one undoable change. It stops and rewinds
transport and prepares the project, including converting loop ticks to samples
at the new tempo. Failed validation/preparation retains the previous project;
the field returns to its actual value. Existing track/clip and piano-roll
selection survives. This is project tempo, separate from Preferences' new-project
default. Tempo automation is not added.
selection survives. This is project tempo, separate from Preferences'
new-project default. Tempo automation is not added.
The **Track inspector** now edits the selected track's linear Gain (0..4), Pan
(-1..1), Mute and Solo. In Mixer it follows the selected channel; in Sequencer
and Graph it follows arrangement selection. Controls synchronize with model
changes and undo, while typing is protected from the display refresh timer.
Only the edited field is committed. Unchanged values create no history entry.
These are static values; automation still overrides gain and pan. Recording
prevents edits. Track instruments and routing retain their existing selectors.
changes and undo, while typing is protected from the display refresh timer. Only
the edited field is committed. Unchanged values create no history entry. These
are static values; automation still overrides gain and pan. Recording prevents
edits. Track instruments and routing retain their existing selectors.
The session row shows track/bus count, clip count and the arrangement's bar span.
**Fit song** (Ctrl+0) returns to the start and selects the largest supported zoom
that fits the arrangement plus a beat of padding. Supported zoom remains
24, 48, 96, 192 or 384 pixels per beat; a sufficiently long arrangement requires
scrolling even at the minimum. Empty projects fit one bar.
The session row shows track/bus count, clip count and the arrangement's bar
span. **Fit song** (Ctrl+0) returns to the start and selects the largest
supported zoom that fits the arrangement plus a beat of padding. Supported zoom
remains 24, 48, 96, 192 or 384 pixels per beat; a sufficiently long arrangement
requires scrolling even at the minimum. Empty projects fit one bar.
**Follow playhead** pages the arrangement horizontally during playback, retaining
vertical scroll. It operates only in Sequencer and waits while a mouse drag is
active. It is off by default; Preferences / Apply persists it in
**Follow playhead** pages the arrangement horizontally during playback,
retaining vertical scroll. It operates only in Sequencer and waits while a mouse
drag is active. It is off by default; Preferences / Apply persists it in
`[transport] follow_playhead=0` or `1`. Manual scrolling remains available and
Fit song resets the viewport without editing the project.
Verification covers real FLTK palette filtering, empty searches and cancellation,
tempo callbacks/undo/failed adoption, inspector edits and in-progress numeric
input, fit and follow navigation, and existing core/GUI/live JACK workflows.
Verification covers real FLTK palette filtering, empty searches and
cancellation, tempo callbacks/undo/failed adoption, inspector edits and
in-progress numeric input, fit and follow navigation, and existing core/GUI/live
JACK workflows.
---
## Detachable transport and mixer
## Detachable Transport and Mixer
Use **View → Detach transport** or **View → Detach mixer** to move either panel
into a separate window. Toggle the same menu action, use the floating panel's
**Dock** button, or close its window to return it to the main workspace.
**View → Dock all panels** restores both. **View → Mixer** (Ctrl+2) also raises
the floating mixer. The main arrangement expands when the transport is detached.
The mixer remains the same live view, with its channel selection, faders, routing
and meters; the main Mixer tab offers Show and Dock actions while it is floating.
**Dock** button, or close its window to return it to the main workspace. **View
→ Dock all panels** restores both. **View → Mixer** (Ctrl+2) also raises the
floating mixer. The main arrangement expands when the transport is detached. The
mixer remains the same live view, with its channel selection, faders, routing
and meters; the main Mixer tab offers Show and Dock actions while it is
floating.
The transport owns the playback buttons, position, **BPM**, **Loop** toggle and
**Loop range...** settings in both layouts. Shift-drag on the sequencer ruler
still updates the same loop region. BPM remains an undoable project edit;
loop preferences persist through Preferences / Apply or clean application exit.
Space and menu playback shortcuts work from either floating panel, with normal
still updates the same loop region. BPM remains an undoable project edit; loop
preferences persist through Preferences / Apply or clean application exit. Space
and menu playback shortcuts work from either floating panel, with normal
numeric/text editing protection. Closing the main window closes all panels only
after the existing unsaved/recording/backend shutdown checks succeed.
@ -174,12 +190,15 @@ The initial layout used session-only positions and a transport minimum width of
described below. The floating mixer resizes down to 600 × 420 and retains
horizontal track scrolling.
Verified with `make BUILD_DIR=build/workspace-detach gui test-gui test-gui-instruments
test-gui-jack` (one line), plus the GUI suite under AddressSanitizer/UBSan.
Tests cover repeated reparenting, close-to-dock, resize, shared BPM/loop state,
playback shortcuts, failed shutdown and cleanup.
Verified with
`make BUILD_DIR=build/workspace-detach gui test-gui test-gui-instruments test-gui-jack`
(one line), plus the GUI suite under AddressSanitizer/UBSan. Tests cover
repeated reparenting, close-to-dock, resize, shared BPM/loop state, playback
shortcuts, failed shutdown and cleanup.
## Saved panel layouts
---
## Saved Panel Layouts
Preferences / Apply now saves transport/mixer dock state and window rectangles.
Clean application exit also saves their current positions. Startup restores the
@ -194,41 +213,46 @@ Existing close-to-dock, Dock all and shortcuts retain their shared widget state.
Tests cover narrow control bounds, save/restart, off-screen restoration, invalid
geometry, repeated docking/resizing and shared transport state.
## Editor panels and pinned track headers
---
## Editor Panels and Pinned Track Headers
**View → Detach sequencer** and **Detach modular graph** float each complete
editor, including its toolbar and scroll area. Its main tab offers Show and Dock
buttons. Closing the floating window docks it; View shortcuts also raise floating
editors. Supported dock locations are their original main-window tabs; arbitrary
split regions, tab reordering and drag-to-dock are not implemented. Sequencer
minimum floating size is 1100 × 420; Graph is 1000 × 420.
buttons. Closing the floating window docks it; View shortcuts also raise
floating editors. Supported dock locations are their original main-window tabs;
arbitrary split regions, tab reordering and drag-to-dock are not implemented.
Sequencer minimum floating size is 1100 × 420; Graph is 1000 × 420.
The Piano roll now also has a main-window tab. Select a MIDI clip and open it as
before. **View → Float piano roll** switches its existing widgets between the tab
and a floating window; closing the floating window docks it. The tab's Open
before. **View → Float piano roll** switches its existing widgets between the
tab and a floating window; closing the floating window docks it. The tab's Open
selected MIDI clip button opens the current selection. Default floating behavior
is retained for older preferences. A missing/invalid MIDI selection is reported.
All panels share project/history, preserve editor selections and scroll offsets,
and retain playback shortcuts and text-input protection.
**View → Dock all panels** includes all editors. **Reset workspace layout** docks
all panels, restores default floating rectangles and resets header width. Use
Preferences / Apply to persist either result. Editor dock state, floating window
rectangles and header width use the same validated settings/restore path as the
transport/mixer. Piano content opens lazily for the selected MIDI clip; startup
does not invent a clip or save a project-specific selection in application settings.
**View → Dock all panels** includes all editors. **Reset workspace layout**
docks all panels, restores default floating rectangles and resets header width.
Use Preferences / Apply to persist either result. Editor dock state, floating
window rectangles and header width use the same validated settings/restore path
as the transport/mixer. Piano content opens lazily for the selected MIDI clip;
startup does not invent a clip or save a project-specific selection in
application settings.
Arrangement track headers and instrument selectors now stay pinned during
horizontal scrolling. Drag the divider within the header's final five pixels to
resize from 180 to 360 pixels; Escape cancels the drag. Clip timing coordinates,
snap and horizontal scrolling use the resized origin. Header layout is a UI
preference, not an undoable project edit. Vertical scrolling remains shared with
the track rows. Clips covered by the pinned header cannot be selected through it.
the track rows. Clips covered by the pinned header cannot be selected through
it.
GUI regressions cover repeated detach/dock and close-to-dock, toolbar ownership,
minimum/large resize, shared model/note/clip selection, scrolling, header dragging,
short-clip hit tests, Apply/restart, reset layout and failed/successful shutdown.
ASan/UBSan checks retain only the existing system Fontconfig leak suppression.
minimum/large resize, shared model/note/clip selection, scrolling, header
dragging, short-clip hit tests, Apply/restart, reset layout and
failed/successful shutdown. ASan/UBSan checks retain only the existing system
Fontconfig leak suppression.
Additive clip selection, atomic group commands, phrase clipboard and repeat,
alignment/slip tools, MIDI swing/gate/legato/scale/velocity transforms, batch
@ -237,8 +261,10 @@ audio fades and static mixer A/B recall are also available. See
Arrangement markers and sections, ripple edits, complete track duplication, MIDI
splitting/consolidation and composition tools, peak normalization and portable
media collection are also available. See [COMPOSITION_WORKFLOWS.md](COMPOSITION_WORKFLOWS.md).
media collection are also available. See
[COMPOSITION_WORKFLOWS.md](COMPOSITION_WORKFLOWS.md).
A modal audio source editor provides frame selection, reversible processing,
selection export, copy adoption and transient slicing. See
[AUDIO_EDITOR.md](AUDIO_EDITOR.md) for commands, undo behavior and mono/size limits.
[AUDIO_EDITOR.md](AUDIO_EDITOR.md) for commands, undo behavior and mono/size
limits.

View file

@ -1,18 +1,24 @@
# Decision 0001: Modular engine first
# Decision 0001: Modular Engine First
**Status:** Accepted
---
## Context
The project aims to preserve energyXT's combination of a simple workflow and a
flexible modular routing model.
---
## Decision
Use one unified processing graph for sources, instruments, effects, mixers,
and outputs. The sequencer is a timeline/event source integrated with that
graph. The default GUI is simple and sequencer-oriented, while a graph editor
exposes routing when needed.
Use one unified processing graph for sources, instruments, effects, mixers, and
outputs. The sequencer is a timeline/event source integrated with that graph.
The default GUI is simple and sequencer-oriented, while a graph editor exposes
routing when needed.
---
## Consequences

View file

@ -1,11 +1,15 @@
# Decision 0002: C core with FLTK/C++ GUI
# Decision 0002: C Core with FLTK/C++ GUI
**Status:** Accepted
---
## Context
The project prioritizes a readable, small C implementation while using FLTK
for a lightweight desktop GUI.
The project prioritizes a readable, small C implementation while using FLTK for
a lightweight desktop GUI.
---
## Decision
@ -13,6 +17,8 @@ Keep the engine, DSP, project model, graph, and public core API in C (C99
baseline). Use C++ only for FLTK windows and widgets. Communicate through
documented C headers with `extern "C"` guards.
---
## Consequences
- No C++ classes, STL containers, exceptions, or FLTK types cross the C API.

View file

@ -1,19 +1,25 @@
# Decision 0003: Real-time thread rules
# Decision 0003: Real-Time Thread Rules
**Status:** Accepted
---
## Context
Audio glitches and unsafe graph changes can undermine the whole application.
---
## Decision
The audio callback performs bounded processing using preallocated resources.
It performs no allocation/free, blocking locks, file I/O, logging, GUI calls,
The audio callback performs bounded processing using preallocated resources. It
performs no allocation/free, blocking locks, file I/O, logging, GUI calls,
plugin loading, or waits. Graphs are prepared outside the callback and active
graph resources are retired only after safe reclamation.
---
## Consequences
Thread ownership and publication mechanisms must be documented and tested.
Any exception to these rules requires explicit evidence and review.
Thread ownership and publication mechanisms must be documented and tested. Any
exception to these rules requires explicit evidence and review.

View file

@ -1,7 +1,9 @@
# Decision 0004: JACK backend and bounded plan handoff
# Decision 0004: JACK Backend and Bounded Plan Handoff
**Status:** Accepted
---
## Backend
Use the JACK C client API. The inspected system provides PipeWire's maintained
@ -22,10 +24,12 @@ framework or second driver implementation.
The optional dependency provides actual audio callbacks and port routing.
PipeWire's installed JACK library is MIT licensed (`pipewire-jack/COPYING`).
Traditional JACK client libraries use LGPL licensing; review the actual linked
implementation, transitive libraries and notices when distributing. Nothing
is bundled or installed here. ALSA is still a future MIDI candidate.
implementation, transitive libraries and notices when distributing. Nothing is
bundled or installed here. ALSA is still a future MIDI candidate.
## C99 and atomic operations
---
## C99 and Atomic Operations
Retain C99 and use GCC/Clang `__atomic` builtins. This is a documented compiler
extension, avoiding a language-standard change and keeping the existing core
@ -34,12 +38,15 @@ unsigned-int atomics to be always lock-free, or returns unsupported before
callbacks are installed. Naturally aligned fields reside in calloc-allocated
objects. There is no mutex or libatomic fallback in the callback path.
## Ownership and publication
---
## Ownership and Publication
One serialized control producer and one audio consumer share two pointer slots:
- `pending`: producer publishes a fully compiled plan with release; consumer
exchanges it for NULL with acquire/release at the next eligible block boundary.
exchanges it for NULL with acquire/release at the next eligible block
boundary.
- `retired`: consumer releases its previous active plan into this slot only
after its final access. Producer exchanges with NULL using acquire/release,
then destroys that acknowledged plan on the control thread.
@ -50,40 +57,47 @@ callers never receive plan pointers through the realtime/backend API.
A full pending slot returns capacity without replacing anything. A full retired
slot defers adoption while the current plan continues processing. The consumer
performs at most one adoption per block and never loops waiting for the producer.
Producer collection can overlap processing because the retired plan is no
longer reachable from the consumer. Collection clears the slot before freeing;
consumer may then retire another, distinct plan. Publication never immediately
frees the previously active plan.
performs at most one adoption per block and never loops waiting for the
producer. Producer collection can overlap processing because the retired plan is
no longer reachable from the consumer. Collection clears the slot before
freeing; consumer may then retire another, distinct plan. Publication never
immediately frees the previously active plan.
Submitting collects any already retired plan, so a producer repeatedly submitting
also reclaims memory. If it becomes idle, one pending, one active, and one retired
plan bound retained resources (at most three plans, each with up to 64 MiB of
sample buffers). Periodic collect is recommended. Shutdown closes the JACK client
and synchronizes its callbacks before destroying any active/pending/retired plan.
Standalone realtime-engine callers must stop and join their consumer themselves.
Submitting collects any already retired plan, so a producer repeatedly
submitting also reclaims memory. If it becomes idle, one pending, one active,
and one retired plan bound retained resources (at most three plans, each with up
to 64 MiB of sample buffers). Periodic collect is recommended. Shutdown closes
the JACK client and synchronizes its callbacks before destroying any
active/pending/retired plan. Standalone realtime-engine callers must stop and
join their consumer themselves.
## Status, errors and recovery
---
Unsigned-int status fields use lock-free atomics. Values are individual snapshots,
not one transactional state. Counters wrap naturally; no string formatting,
logging, allocation, GUI operation or blocking synchronization occurs in project
callbacks. JACK's shutdown notification only stores a flag and leaves cleanup to
the control thread. Other notifications store the current format and xrun count.
## Status, Errors and Recovery
Unsigned-int status fields use lock-free atomics. Values are individual
snapshots, not one transactional state. Counters wrap naturally; no string
formatting, logging, allocation, GUI operation or blocking synchronization
occurs in project callbacks. JACK's shutdown notification only stores a flag and
leaves cleanup to the control thread. Other notifications store the current
format and xrun count.
Buffer changes within the configured maximum require no allocation. Larger
blocks are fully silenced and reported as capacity errors; smaller blocks can
resume processing. Sample-rate changes latch a flag and silence processing
rather than replaying an oscillator compiled for a different rate. Close/reopen
and resubmit after a rate change or server loss. There is no automatic reconnect.
JACK-deactivate pauses the retained plan; restart continues its oscillator phase.
Plan replacement starts the newly compiled oscillator at phase zero, without
crossfade. Smooth parameter ramps and click-free graph transitions are future work.
and resubmit after a rate change or server loss. There is no automatic
reconnect. JACK-deactivate pauses the retained plan; restart continues its
oscillator phase. Plan replacement starts the newly compiled oscillator at phase
zero, without crossfade. Smooth parameter ramps and click-free graph transitions
are future work.
No stereo, recording, MIDI, native PipeWire API, GUI engine controls or hardware
configuration dialogs are included.
## Adoption acknowledgement and stopped editing
---
## Adoption Acknowledgement and Stopped Editing
The pending pointer has an associated atomic revision. Producer stores the
revision before release-publishing the pointer. Consumer acquire-loads a
@ -97,10 +111,10 @@ snapshots, not plan pointers or a multi-field transactional snapshot.
While JACK is stopped, a backend submission builds a separate realtime engine,
submits/compiles the graph there, and replaces the retained engine only on
success. Process callbacks cannot run on an inactive client or after successful
deactivation, and notification callbacks do not access the engine pointer.
The control thread can therefore reclaim all old state safely. Counters and
oscillator phases restart for that new engine epoch. The standalone realtime
API keeps its one-pending-slot contract regardless of external stop state.
deactivation, and notification callbacks do not access the engine pointer. The
control thread can therefore reclaim all old state safely. Counters and
oscillator phases restart for that new engine epoch. The standalone realtime API
keeps its one-pending-slot contract regardless of external stop state.
The callback still adopts at most one plan and never allocates/frees. Revision
ordering is tested alongside the plan's rendered gain under 2,000 concurrent

View file

@ -1,4 +1,4 @@
# 0005: Preloaded WAV clips and control-thread recording consumer
# 0005: Preloaded WAV Clips and Control-Thread Recording Consumer
Status: accepted.
@ -11,16 +11,19 @@ codec library needs its own dependency and licensing review.
Compile musical clip ranges and decoded, shared-per-path mono buffers while
stopped. The callback renders the timeline into a preallocated buffer consumed
by an explicit graph source node, preserving graph routing rather than mixing
outside the graph. The old snapshot remains until candidate preparation succeeds.
All track clips currently share one source; per-track routing/mixing is deferred.
outside the graph. The old snapshot remains until candidate preparation
succeeds. All track clips currently share one source; per-track routing/mixing
is deferred.
Use a bounded SPSC ring for callback input capture, with release/acquire index
publication and a GUI/control-thread consumer. This adds no worker-thread
dependency; a two-second ring tolerates normal 100 ms polling but exposes
overflow if the GUI or disk stalls. Sticky errors prevent silent time compression.
Finalization requires callback quiescence. Future long-session/background I/O
work can reuse the ring contract with a dedicated consumer.
overflow if the GUI or disk stalls. Sticky errors prevent silent time
compression. Finalization requires callback quiescence. Future
long-session/background I/O work can reuse the ring contract with a dedicated
consumer.
Store external absolute paths in version 2 metadata and retain version 1 loads.
Missing/unreadable assets are reported silence, with explicit relinking; portable
bundles and content-addressed assets are deferred. See AUDIO_FILES.md for limits.
Missing/unreadable assets are reported silence, with explicit relinking;
portable bundles and content-addressed assets are deferred. See AUDIO_FILES.md
for limits.

View file

@ -1,16 +1,16 @@
# 0006 — Bounded MIDI notes and internal instrument
# 0006 — Bounded MIDI Notes and Internal Instrument
Status: accepted.
Use the existing JACK backend for timestamped MIDI input, avoiding another
backend/dependency. Store source-relative notes in the C project and v3 format;
read v1/v2 without MIDI. Prepare sample spans on the control thread and render
a small sine instrument into its own graph source buffer. Use a piano roll to
meet draw/play/save/reopen acceptance before adding MIDI capture or SMF import.
read v1/v2 without MIDI. Prepare sample spans on the control thread and render a
small sine instrument into its own graph source buffer. Use a piano roll to meet
draw/play/save/reopen acceptance before adding MIDI capture or SMF import.
Keep explicit bounds: 4096 project notes, 32 overlapping scheduled notes, 32 live
voices and 256 events/block. Reject scheduled overcapacity at preparation; steal
oldest live voices. Panic handles event loss, disconnects and callback failures;
Stop/seek resets live state. Immediate note-off and a fixed sine voice keep this
prototype small, at the cost of possible clicks and no expressive controllers.
See ../MIDI.md for ownership, detailed limits and validation.
Keep explicit bounds: 4096 project notes, 32 overlapping scheduled notes, 32
live voices and 256 events/block. Reject scheduled overcapacity at preparation;
steal oldest live voices. Panic handles event loss, disconnects and callback
failures; Stop/seek resets live state. Immediate note-off and a fixed sine voice
keep this prototype small, at the cost of possible clicks and no expressive
controllers. See ../MIDI.md for ownership, detailed limits and validation.

View file

@ -1,4 +1,4 @@
# 0007 — Track mixing, paired stereo buses and snapshot history
# 0007 — Track Mixing, Paired Stereo Buses and Snapshot History
Status: accepted.

View file

@ -1,19 +1,19 @@
# 0008 — Native processors and source release preparation
# 0008 — Native Processors and Source Release Preparation
Status: accepted.
Add a versioned copied C descriptor with control-thread prepare/destroy and
single-owner realtime reset/process. Prove effects/sources in the existing graph,
with two independent mono instances for stereo, and event instruments rendered
through the public instrument-source buffer boundary. Keep trusted code statically
linked. Native project serialization and graph event ports need more contracts;
reject unsupported saves without replacing files. Defer LV2/CLAP/VST3 after a
concrete evaluation rather than linking SDKs speculatively.
single-owner realtime reset/process. Prove effects/sources in the existing
graph, with two independent mono instances for stereo, and event instruments
rendered through the public instrument-source buffer boundary. Keep trusted code
statically linked. Native project serialization and graph event ports need more
contracts; reject unsupported saves without replacing files. Defer LV2/CLAP/VST3
after a concrete evaluation rather than linking SDKs speculatively.
Keep format v4 and test migration plus process-crash/write-failure behavior.
Provide deterministic source exports and optimized release builds, staged
core/GUI installation, exact uninstall and installed C/C++ consumer tests. Use
Python standard-library tools only for packaging/testing; no application runtime
dependency or system changes. Document dependency licenses, Linux routing and
measured offline performance separately from unverified hardware/endurance.
See NATIVE_API.md, PLUGIN_HOSTING.md, RELEASING.md and RECOVERY.md.
measured offline performance separately from unverified hardware/endurance. See
NATIVE_API.md, PLUGIN_HOSTING.md, RELEASING.md and RECOVERY.md.

View file

@ -1,52 +1,55 @@
# 0009 — Parameter processors and bounded LADSPA hosting
# 0009 — Parameter Processors and Bounded LADSPA Hosting
Status: accepted (2026-10-27).
Native ABI 1 remains compatible. ABI 2 adds explicitly parameter-only configuration,
metadata, validation and sample-offset parameter/MIDI ordering. It is a public C
facility; opaque state and automatic native descriptor registration in project
files remain unsupported. Native instrument and gain examples exercise the
interface before adding a dynamically loaded module lifetime.
Native ABI 1 remains compatible. ABI 2 adds explicitly parameter-only
configuration, metadata, validation and sample-offset parameter/MIDI ordering.
It is a public C facility; opaque state and automatic native descriptor
registration in project files remain unsupported. Native instrument and gain
examples exercise the interface before adding a dynamically loaded module
lifetime.
Select LADSPA 1.1 for the first external host. Reconsidering earlier candidates,
LV2 needs RDF/features/URIDs/workers, CLAP needs host scheduling/extension
negotiation, and VST3 needs a separate C++ SDK adapter and release-specific license
review. LADSPA adds a mature Linux C ABI, installed real plugins, straightforward
port negotiation and no RDF/SDK runtime library. Its specification is old and
stable, not an actively expanding instrument/event ecosystem. This trades MIDI,
editors, opaque state and modern automation for a small testable first host.
No general multi-format abstraction is introduced.
negotiation, and VST3 needs a separate C++ SDK adapter and release-specific
license review. LADSPA adds a mature Linux C ABI, installed real plugins,
straightforward port negotiation and no RDF/SDK runtime library. Its
specification is old and stable, not an actively expanding instrument/event
ecosystem. This trades MIDI, editors, opaque state and modern automation for a
small testable first host. No general multi-format abstraction is introduced.
Use the installed LGPL-2.1-or-later header, system dynamic loader (`-ldl`), and
user-selected, trusted module files. Neither the header nor plugin binaries are
vendored. Header absence or `XTC_DISABLE_LADSPA` yields unsupported hosting stubs;
metadata can still be loaded/saved. Plugin licenses are independent of the host
and require review before distribution. See LICENSES.md.
vendored. Header absence or `XTC_DISABLE_LADSPA` yields unsupported hosting
stubs; metadata can still be loaded/saved. Plugin licenses are independent of
the host and require review before distribution. See LICENSES.md.
Support zero/one mono audio input and exactly one mono output, at most 64 ports
and 16 input controls. Graph stereo consists of independent instances, not a
stereo LADSPA layout. Reject incompatible layouts and identities explicitly.
Control outputs are connected to private storage, not exposed as graph events.
LADSPA has no MIDI or opaque state API: save control values only. Parameter
changes rebuild plans on the control thread; sample automation is provided through
parameter processors. Rate-scaled bounds are recalculated at compile. Instance history/tails
reset on recompile, not every transport seek (LADSPA has no realtime reset rule).
Latency compensation is unsupported: the supported set is zero-latency amp,
inverter and sine source, not arbitrary lookahead/delay/routing plugins.
changes rebuild plans on the control thread; sample automation is provided
through parameter processors. Rate-scaled bounds are recalculated at compile.
Instance history/tails reset on recompile, not every transport seek (LADSPA has
no realtime reset rule). Latency compensation is unsupported: the supported set
is zero-latency amp, inverter and sine source, not arbitrary
lookahead/delay/routing plugins.
Discovery, dlopen, descriptor calls, instantiate, connect, activate, deactivate,
cleanup and dlclose are control-thread operations. Only preallocated copies and
run execute in audio; the usual pending/retired plan handoff owns module handles.
Partial initialization unwinds on the control thread. The host cannot enforce
third-party callback behavior, bound CPU time, catch native crashes/hangs, or
undo memory corruption. There is no sandbox or crash recovery inside the audio
process. A fault may kill xtc; previously saved documents/recording recovery files
remain subject to their ordinary durability guarantees. Test only trusted modules.
run execute in audio; the usual pending/retired plan handoff owns module
handles. Partial initialization unwinds on the control thread. The host cannot
enforce third-party callback behavior, bound CPU time, catch native
crashes/hangs, or undo memory corruption. There is no sandbox or crash recovery
inside the audio process. A fault may kill xtc; previously saved
documents/recording recovery files remain subject to their ordinary durability
guarantees. Test only trusted modules.
Project v5 stores plain identity/layout/control metadata. Parsing never opens
modules. Compile failure preserves the source document and active plan. GUI Open
rejects missing/incompatible modules, retains its current document, and reports
saved module identity/path. Restore the compatible module at that path and retry;
a metadata-only C client can preserve/save the document without the module.
No silent replacement or deletion occurs. Same-ID/label/ports do not authenticate
binaries or guarantee unchanged sound across plugin upgrades.
saved module identity/path. Restore the compatible module at that path and
retry; a metadata-only C client can preserve/save the document without the
module. No silent replacement or deletion occurs. Same-ID/label/ports do not
authenticate binaries or guarantee unchanged sound across plugin upgrades.

View file

@ -1,13 +1,14 @@
# 0010 — Bounded CLAP, VST2 and VST3 effect loading
# 0010 — Bounded CLAP, VST2 and VST3 Effect Loading
Accepted. The user's request to support these three formats
supersedes the earlier recommendation to choose only one external format.
Accepted. The user's request to support these three formats supersedes the
earlier recommendation to choose only one external format.
Keep the engine/model/host adapters in C99. CLAP uses pinned MIT headers and VST3
uses the upstream BSD-3-Clause generated C ABI. No SDK runtime library is needed;
Linux dynamic loading and POSIX thread identity are existing platform facilities.
VST2 uses original minimal compatibility declarations, not the discontinued SDK.
See third_party/README.md and LICENSES.md for exact revisions and notices.
Keep the engine/model/host adapters in C99. CLAP uses pinned MIT headers and
VST3 uses the upstream BSD-3-Clause generated C ABI. No SDK runtime library is
needed; Linux dynamic loading and POSIX thread identity are existing platform
facilities. VST2 uses original minimal compatibility declarations, not the
discontinued SDK. See third_party/README.md and LICENSES.md for exact revisions
and notices.
Reuse the existing plugin graph node, paired mono instances and immutable plan
ownership. The common facade routes LADSPA to its existing implementation and
@ -18,25 +19,27 @@ configuration is used; no parameter/controller/editor or opaque-state access is
exposed. These are usable effect loaders, not complete implementations of every
extension in the formats. Unsupported layouts fail explicitly.
Project v11 adds format plus CLAP stable ID or VST3 class ID to each saved plugin.
Older plugins migrate to LADSPA with an empty extra ID. VST2 retains its integer
unique ID. Reopening checks format/index/identity/name/layout; metadata operations
never open modules. This does not authenticate a binary or freeze defaults across
plugin releases. No modified opaque state exists in this initial API to persist.
Project v11 adds format plus CLAP stable ID or VST3 class ID to each saved
plugin. Older plugins migrate to LADSPA with an empty extra ID. VST2 retains its
integer unique ID. Reopening checks format/index/identity/name/layout; metadata
operations never open modules. This does not authenticate a binary or freeze
defaults across plugin releases. No modified opaque state exists in this initial
API to persist.
Discovery and instance creation execute trusted module code on one serialized
main/control thread. A module cache balances CLAP entry init/deinit and Linux
VST3 ModuleEntry/ModuleExit across overlapping graph instances and retired plans.
CLAP start_processing runs on the processing owner; stop_processing runs after
quiescence with the symbolic audio-thread context. CLAP callback requests use
lock-free flags and are serviced by xtc_plugin_idle on main/control (the GUI timer
calls it). Restart/I/O-change requests latch an error for re-preparation; the audio
callback does not reconfigure or wait for GUI work. CLAP process wakeups need no
queue because playback processes every block, including silence.
VST3 ModuleEntry/ModuleExit across overlapping graph instances and retired
plans. CLAP start_processing runs on the processing owner; stop_processing runs
after quiescence with the symbolic audio-thread context. CLAP callback requests
use lock-free flags and are serviced by xtc_plugin_idle on main/control (the GUI
timer calls it). Restart/I/O-change requests latch an error for re-preparation;
the audio callback does not reconfigure or wait for GUI work. CLAP process
wakeups need no queue because playback processes every block, including silence.
Only preallocated buffers and plugin processing occur in audio. Error status
silences the failed block; nonfinite/subnormal samples are sanitized. Plugin code
can still allocate, block, corrupt memory or crash; there is no sandbox, scanner
subprocess or recoverable crash boundary. Modules unload only after destruction
on a quiescent/control owner. Real third-party plugins and hardware timing remain
unqualified; original ABI fixtures demonstrate the bounded contracts.
silences the failed block; nonfinite/subnormal samples are sanitized. Plugin
code can still allocate, block, corrupt memory or crash; there is no sandbox,
scanner subprocess or recoverable crash boundary. Modules unload only after
destruction on a quiescent/control owner. Real third-party plugins and hardware
timing remain unqualified; original ABI fixtures demonstrate the bounded
contracts.

View file

@ -1,27 +1,31 @@
# 0011 — Prepared MIDI track instruments
# 0011 — Prepared MIDI Track Instruments
Accepted. Extend the modern C99 adapters with zero-audio-input,
mono-output instruments and one event input. Keep graph effects separate from
track bindings. Each assigned track owns one prepared instance; the existing
Accepted. Extend the modern C99 adapters with zero-audio-input, mono-output
instruments and one event input. Keep graph effects separate from track
bindings. Each assigned track owns one prepared instance; the existing
instrument source sums their mixed output and the sine output of unassigned
tracks. A stable saved live target prevents accidental broadcast/double playback.
tracks. A stable saved live target prevents accidental broadcast/double
playback.
Bindings are copied metadata in the project/history model, serialized in v12.
Loading metadata never executes modules. GUI edits prepare a candidate before
publication, preserving the previous project on missing/invalid binaries. Existing
quiescent plan retirement owns destruction; no module operation belongs in audio.
publication, preserving the previous project on missing/invalid binaries.
Existing quiescent plan retirement owns destruction; no module operation belongs
in audio.
Preallocate 256-frame audio scratch and 256 incoming events per chunk. Stable
insertion orders reset, scheduled off/on and live events without callback sort or
allocation. VST3 translates into a bounded 2304-event IEventList (2048 possible
held keys plus 256 incoming events); panic expands held keys to note-offs. CLAP
prefers MIDI where supported, otherwise native note events; VST2 uses MIDI ABI
messages. Native CLAP and VST3 currently omit controller mapping. Existing
32 simultaneous scheduled notes and 4096 stored notes remain validation bounds.
insertion orders reset, scheduled off/on and live events without callback sort
or allocation. VST3 translates into a bounded 2304-event IEventList (2048
possible held keys plus 256 incoming events); panic expands held keys to
note-offs. CLAP prefers MIDI where supported, otherwise native note events; VST2
uses MIDI ABI messages. Native CLAP and VST3 currently omit controller mapping.
Existing 32 simultaneous scheduled notes and 4096 stored notes remain validation
bounds.
Discontinuities send all-off and chase spanning notes with fresh note-ons.
Panic suppresses already-started scheduled notes. Neither operation promises
opaque DSP reconstruction or removal of release tails. Overflow/process errors
silence the block and arm reset for the following render. Hosts cannot guarantee
that third-party processing is realtime safe. Qualification covers original ABI
fixtures; arbitrary third-party plugins, physical audio and MIDI remain unverified.
Discontinuities send all-off and chase spanning notes with fresh note-ons. Panic
suppresses already-started scheduled notes. Neither operation promises opaque
DSP reconstruction or removal of release tails. Overflow/process errors silence
the block and arm reset for the following render. Hosts cannot guarantee that
third-party processing is realtime safe. Qualification covers original ABI
fixtures; arbitrary third-party plugins, physical audio and MIDI remain
unverified.