diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b0736d1..4ce9de3 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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. diff --git a/docs/AUDIO_EDITOR.md b/docs/AUDIO_EDITOR.md index 17cae58..a5a970c 100644 --- a/docs/AUDIO_EDITOR.md +++ b/docs/AUDIO_EDITOR.md @@ -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. diff --git a/docs/AUDIO_ENGINE.md b/docs/AUDIO_ENGINE.md index b0fa7d0..b987cfd 100644 --- a/docs/AUDIO_ENGINE.md +++ b/docs/AUDIO_ENGINE.md @@ -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. diff --git a/docs/AUDIO_FILES.md b/docs/AUDIO_FILES.md index d232c10..1fb181f 100644 --- a/docs/AUDIO_FILES.md +++ b/docs/AUDIO_FILES.md @@ -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. diff --git a/docs/AUDIO_QUALITY.md b/docs/AUDIO_QUALITY.md index 9ab1068..15cf344 100644 --- a/docs/AUDIO_QUALITY.md +++ b/docs/AUDIO_QUALITY.md @@ -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. diff --git a/docs/BUILD.md b/docs/BUILD.md index 03372ff..6b73302 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -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. diff --git a/docs/BUILTIN_DEVICES.md b/docs/BUILTIN_DEVICES.md index 9c773c5..160fd9b 100644 --- a/docs/BUILTIN_DEVICES.md +++ b/docs/BUILTIN_DEVICES.md @@ -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, diff --git a/docs/COMPOSITION_WORKFLOWS.md b/docs/COMPOSITION_WORKFLOWS.md index bf127f0..e9854f1 100644 --- a/docs/COMPOSITION_WORKFLOWS.md +++ b/docs/COMPOSITION_WORKFLOWS.md @@ -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. diff --git a/docs/CONTROL.md b/docs/CONTROL.md index cd724dc..2fd7780 100644 --- a/docs/CONTROL.md +++ b/docs/CONTROL.md @@ -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. diff --git a/docs/CREATIVE_WORKFLOWS.md b/docs/CREATIVE_WORKFLOWS.md index a7bb960..b79828e 100644 --- a/docs/CREATIVE_WORKFLOWS.md +++ b/docs/CREATIVE_WORKFLOWS.md @@ -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. diff --git a/docs/EDITING.md b/docs/EDITING.md index bad5fd7..27a9465 100644 --- a/docs/EDITING.md +++ b/docs/EDITING.md @@ -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. diff --git a/docs/GUI.md b/docs/GUI.md index e857f80..8a94750 100644 --- a/docs/GUI.md +++ b/docs/GUI.md @@ -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. diff --git a/docs/IDEA.md b/docs/IDEA.md index 0e3693b..adb66ef 100644 --- a/docs/IDEA.md +++ b/docs/IDEA.md @@ -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. diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index 7410d5d..a6b1b4f 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.md @@ -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. diff --git a/docs/LICENSES.md b/docs/LICENSES.md index 2ff1b8b..2107b62 100644 --- a/docs/LICENSES.md +++ b/docs/LICENSES.md @@ -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 diff --git a/docs/LINUX.md b/docs/LINUX.md index 8d6666a..2c01416 100644 --- a/docs/LINUX.md +++ b/docs/LINUX.md @@ -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. diff --git a/docs/MAINTENANCE.md b/docs/MAINTENANCE.md index 18b0749..f15caff 100644 --- a/docs/MAINTENANCE.md +++ b/docs/MAINTENANCE.md @@ -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 diff --git a/docs/MIDI.md b/docs/MIDI.md index b66e82c..101e101 100644 --- a/docs/MIDI.md +++ b/docs/MIDI.md @@ -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. diff --git a/docs/MIDI_WORKFLOWS.md b/docs/MIDI_WORKFLOWS.md index 00a2c23..ace63d4 100644 --- a/docs/MIDI_WORKFLOWS.md +++ b/docs/MIDI_WORKFLOWS.md @@ -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 diff --git a/docs/MIXER.md b/docs/MIXER.md index ebf8d6a..6f1e825 100644 --- a/docs/MIXER.md +++ b/docs/MIXER.md @@ -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. diff --git a/docs/MODERN_EDITING.md b/docs/MODERN_EDITING.md index 2a00fbf..c25e754 100644 --- a/docs/MODERN_EDITING.md +++ b/docs/MODERN_EDITING.md @@ -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. diff --git a/docs/NATIVE_API.md b/docs/NATIVE_API.md index 9add2d5..f9249f5 100644 --- a/docs/NATIVE_API.md +++ b/docs/NATIVE_API.md @@ -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. diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index f419558..973a5bc 100644 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -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`. diff --git a/docs/PLUGIN_HOSTING.md b/docs/PLUGIN_HOSTING.md index 89e5521..7fc6626 100644 --- a/docs/PLUGIN_HOSTING.md +++ b/docs/PLUGIN_HOSTING.md @@ -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/-linux/.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/-linux/.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. diff --git a/docs/PREFERENCES.md b/docs/PREFERENCES.md index 9f3e478..421da3b 100644 --- a/docs/PREFERENCES.md +++ b/docs/PREFERENCES.md @@ -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. diff --git a/docs/PROJECT_FORMAT.md b/docs/PROJECT_FORMAT.md index abc26b8..0554137 100644 --- a/docs/PROJECT_FORMAT.md +++ b/docs/PROJECT_FORMAT.md @@ -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. diff --git a/docs/RECOVERY.md b/docs/RECOVERY.md index 86f2175..408ec7f 100644 --- a/docs/RECOVERY.md +++ b/docs/RECOVERY.md @@ -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. diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index c7f9479..6acfceb 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -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 diff --git a/docs/RELEASING.md b/docs/RELEASING.md index c0135e5..e351778 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -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. diff --git a/docs/RELIABILITY.md b/docs/RELIABILITY.md index 45b76dd..3bceea6 100644 --- a/docs/RELIABILITY.md +++ b/docs/RELIABILITY.md @@ -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. diff --git a/docs/ROUTING.md b/docs/ROUTING.md index 1ef14ee..c4f7071 100644 --- a/docs/ROUTING.md +++ b/docs/ROUTING.md @@ -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. diff --git a/docs/TRACK_INSERTS.md b/docs/TRACK_INSERTS.md index 2a9852f..20c3d25 100644 --- a/docs/TRACK_INSERTS.md +++ b/docs/TRACK_INSERTS.md @@ -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: diff --git a/docs/USER_MANUAL.md b/docs/USER_MANUAL.md index cacccf2..355ad54 100644 --- a/docs/USER_MANUAL.md +++ b/docs/USER_MANUAL.md @@ -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. diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index 6c351cd..fbadc61 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -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. diff --git a/docs/WORKSPACE.md b/docs/WORKSPACE.md index bf505c7..00e54b4 100644 --- a/docs/WORKSPACE.md +++ b/docs/WORKSPACE.md @@ -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. diff --git a/docs/decisions/0001-modular-engine-first.md b/docs/decisions/0001-modular-engine-first.md index 39bc339..476cad7 100644 --- a/docs/decisions/0001-modular-engine-first.md +++ b/docs/decisions/0001-modular-engine-first.md @@ -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 diff --git a/docs/decisions/0002-c-core-fltk-gui.md b/docs/decisions/0002-c-core-fltk-gui.md index 834a788..fe25ac6 100644 --- a/docs/decisions/0002-c-core-fltk-gui.md +++ b/docs/decisions/0002-c-core-fltk-gui.md @@ -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. diff --git a/docs/decisions/0003-realtime-thread-rules.md b/docs/decisions/0003-realtime-thread-rules.md index 72b35d2..ddb2c0d 100644 --- a/docs/decisions/0003-realtime-thread-rules.md +++ b/docs/decisions/0003-realtime-thread-rules.md @@ -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. diff --git a/docs/decisions/0004-jack-and-plan-handoff.md b/docs/decisions/0004-jack-and-plan-handoff.md index 44060f7..64e333e 100644 --- a/docs/decisions/0004-jack-and-plan-handoff.md +++ b/docs/decisions/0004-jack-and-plan-handoff.md @@ -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 diff --git a/docs/decisions/0005-preloaded-audio-and-recording.md b/docs/decisions/0005-preloaded-audio-and-recording.md index 7107e09..0ca076c 100644 --- a/docs/decisions/0005-preloaded-audio-and-recording.md +++ b/docs/decisions/0005-preloaded-audio-and-recording.md @@ -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. diff --git a/docs/decisions/0006-midi-instrument-and-notes.md b/docs/decisions/0006-midi-instrument-and-notes.md index 869a9bb..a8c91ef 100644 --- a/docs/decisions/0006-midi-instrument-and-notes.md +++ b/docs/decisions/0006-midi-instrument-and-notes.md @@ -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. diff --git a/docs/decisions/0007-track-mixer-automation-and-history.md b/docs/decisions/0007-track-mixer-automation-and-history.md index c48dec4..ba7abc2 100644 --- a/docs/decisions/0007-track-mixer-automation-and-history.md +++ b/docs/decisions/0007-track-mixer-automation-and-history.md @@ -1,4 +1,4 @@ -# 0007 — Track mixing, paired stereo buses and snapshot history +# 0007 — Track Mixing, Paired Stereo Buses and Snapshot History Status: accepted. diff --git a/docs/decisions/0008-native-processors-and-source-releases.md b/docs/decisions/0008-native-processors-and-source-releases.md index eeefc83..3087b11 100644 --- a/docs/decisions/0008-native-processors-and-source-releases.md +++ b/docs/decisions/0008-native-processors-and-source-releases.md @@ -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. diff --git a/docs/decisions/0009-parameter-processors-and-ladspa.md b/docs/decisions/0009-parameter-processors-and-ladspa.md index 0978eeb..f78902a 100644 --- a/docs/decisions/0009-parameter-processors-and-ladspa.md +++ b/docs/decisions/0009-parameter-processors-and-ladspa.md @@ -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. diff --git a/docs/decisions/0010-modern-plugin-effects.md b/docs/decisions/0010-modern-plugin-effects.md index 5a734b6..2f3bdb3 100644 --- a/docs/decisions/0010-modern-plugin-effects.md +++ b/docs/decisions/0010-modern-plugin-effects.md @@ -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. diff --git a/docs/decisions/0011-midi-track-instruments.md b/docs/decisions/0011-midi-track-instruments.md index 6c5fd4e..a65cb15 100644 --- a/docs/decisions/0011-midi-track-instruments.md +++ b/docs/decisions/0011-midi-track-instruments.md @@ -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.