1
0
Fork 0
forked from hanez/xtc

Phase 5: Add project persistence and sequencer foundation

This commit is contained in:
Johannes Findeisen 2026-10-27 00:10:45 +01:00
commit 65640c4aad
28 changed files with 1307 additions and 84 deletions

View file

@ -68,3 +68,20 @@ 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
Phase 5 introduces a C-owned project containing 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.
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.

View file

@ -181,3 +181,25 @@ or publish audio plans. Empty graphs and graphs without explicit outputs are
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.
## Phase 5 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.
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
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.
Silent clip metadata is control-thread data, never traversed by the callback.
Audio/MIDI scheduling will be added in later phases through compiled event/clip
plans rather than direct callback access to the editable project.

View file

@ -109,8 +109,22 @@ 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.
## Next milestone
## Phase 5 verification
Phase 5 adds versioned project persistence and the sequencer foundation, stable
track/clip IDs and timeline editing. Physical-device playback/endurance remains
unverified; graph parameters are fixed GUI presets and undo/zoom are not present.
```sh
make clean all test gui debug test-gui test-gui-jack
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
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.
Phase 6 will add audio assets and recording. Silent arrangement regions, fixed
tempo/4/4, no undo, no persisted GUI layout, and unverified physical-device
playback/endurance remain explicit limits.

View file

@ -26,17 +26,15 @@ 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. **Space:** toggle.
These are engine controls, not timeline playback or transport positioning.
These advance/pause the sample-counted playhead; regions are currently silent.
- **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. Editors remain clearly
labeled placeholders for sequencer/mixer; Modular Graph is implemented below.
modular graph. Tabs provide the same view switching. The mixer remains a placeholder; 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. Unsupported New/Open/Save actions are
visibly disabled. 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
@ -78,10 +76,10 @@ 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 zoom, multiselection, undo, project persistence or external routing UI yet.
is no graph zoom, multiselection, undo or external routing UI yet.
The editor supports 64 nodes on a fixed 2000x4000 canvas.
Every semantic action becomes a C graph command. The window owns the graph
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.
@ -99,3 +97,36 @@ audio thread. There is no automatic retry/hidden edit queue; users retry a
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.
## Phase 5 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
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.
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 1/16-note snapping; Escape cancels. There is no drag preview, undo,
multiselection, rename or tempo editor yet. 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. Regions contain no audio or MIDI and cannot
produce sound yet; modular graph processing continues as before. 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.

View file

@ -1,18 +1,75 @@
# Project format
# Project format v1
Project persistence is not implemented yet. Before implementation, define a
versioned format with stable IDs for tracks, nodes, clips, connections, and
parameters.
Phase 5 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 arrangement regions; audio files,
recording and MIDI content arrive in subsequent phases.
Requirements:
- reject malformed, truncated, unsupported, or unreasonable input safely;
- validate sizes before allocation;
- store external audio assets separately from project metadata;
- define missing-asset behavior;
- never execute code during loading;
- perform file I/O only off the audio thread;
- save atomically using a temporary file and a safe rename;
- add explicit migration code when the format changes.
## Schema
Choose a simple format after evaluating dependency, robustness, and human
inspectability trade-offs. Document the schema before treating it as stable.
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:
```text
XTC_PROJECT 1
APP 0 1 0
TEMPO 120000
IDS 1 1
GRAPH 1 2 2 1
NODE 0 1 3e800000
NODE 1 4 00000000
EDGE 0 1
TRACKS 1
TRACK 0 547261636b
CLIPS 1
CLIP 0 0 3840 2880 960 3840 526567696f6e
END
```
- `APP`: producer major/minor/patch; informational, never executed.
- `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
compiled state are serialized.
- `NODE`: ID, type (1 gain, 2 oscillator, 3 meter, 4 output), 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. Mono port directions are implied
by node type; endpoints, duplicates and cycles are validated by the graph API.
- `TRACK`: ID and hex-encoded name bytes.
- `CLIP`: ID, track ID, start tick, length tick, source offset tick, source
length tick, hex-encoded name bytes. Regions are silent metadata. Trimming
preserves source bounds, so a region can re-extend only within its original
length. Overlaps are allowed; no audio mixing or scheduling is implied.
- Names decode to nonempty valid UTF-8 of at most 63 bytes; ASCII control
characters and embedded NULs are forbidden. Hex avoids quoting ambiguity.
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, 1 MiB input, 511 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. No implicit migration is attempted; future revisions require explicit
migration code and roundtrip tests.
## 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
validates the project, creates a mode-0600 temporary in the destination
directory, writes, flushes, fsyncs, closes and renames over the destination.
Failures before rename retain the previous destination and remove the temporary.
The parent directory is not fsynced: survival of the rename through sudden power
loss is not guaranteed. 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 and graph, including ID allocation state.
Graph node positions, timeline zoom/scroll/selection, transport position and DSP
oscillator phase are GUI/session state and are not saved. There are no external
asset references yet; missing-asset recovery belongs to Phase 6.

View file

@ -54,6 +54,14 @@ mono-only processing, fixed canvas and no persistence/undo/zoom remain limits.
Versioned persistence, stable IDs, tracks, clips, timeline, selection, zoom,
scrolling, and transport synchronization.
Implemented: bounded version-1 project persistence with transactional loads and
temporary-file/fsync/rename saves; stable track/clip/node IDs; silent regions;
track and region creation/deletion, selection, move/trim, timeline zoom/scroll;
sample-counted playhead, pause/resume and stopped seek. Project/graph arrangement
roundtrips, malformed-input, GUI rollback/gesture and live transport tests pass.
See PROJECT_FORMAT.md and GUI.md. Constant tempo/4/4, silent metadata regions,
no undo, asset playback, MIDI or persisted GUI layout are explicit limits.
## Phase 6 — Audio clips and recording
Audio import/playback, recording through bounded buffers, missing-asset
handling, no file I/O in callbacks.