Phase 22: add non-destructive audio splitting, clip gain and fades, and cached waveforms

This commit is contained in:
Johannes Findeisen 2026-10-27 15:46:41 +01:00
commit 01d8c1d661
23 changed files with 650 additions and 18 deletions

View file

@ -137,3 +137,82 @@ Recordings remain RIFF mono float32, bounded to roughly 4 GiB; the preloaded
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.
## Phase 22: 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.
**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
attenuate a short clip without extending it. MIDI clips reject these operations;
silent metadata regions support them for later audio relinking.
These operations are one undo step each, stop playback before model edits and
are refused while recording. Main Edit / Undo and Redo restore the model and
prepare its matching stopped engine plan; Play uses the latest settings.
Splitting and duplication allocate metadata IDs only: no media copies. All
operations preserve original WAV bytes, including Delete and undoing recording.
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.
The renderer maps tick boundaries by the existing absolute floor conversion,
linearly resamples the source and never reads beyond its samples. Its audible
end is the earlier of the trimmed end and actual WAV end (imports round metadata
length up to a tick). A fade uses `age / fade_in_frames` at the start and
`remaining_frames / fade_out_frames` at the end, capped at 1; overlapping fades
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.
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.
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
arrangement aid, not a sample editor or loudness meter.
Loading/rebuilding is synchronous on the GUI/control thread during project
binding or edits that introduce media paths. Large files/slow disks can delay
that UI operation. Draw/redraw, scrolling and playback never load a WAV or build
peaks; the audio callback does not use this cache at all. Missing/invalid assets
show no waveform and keep the existing playback issue reporting. Failures are
cached, removed paths are evicted, and Open rebuilds caches, including changed
external files. No persistent disk cache, file watcher or background loader is
claimed. Source files should remain unchanged during a session; reopen after
external replacement. Cache allocation failure omits previews without changing
music metadata.
`make test-audio-editing` verifies exact split/gain/fade playback, odd blocks,
seeks, shortened media tails, one-frame files, source/project limits, capacity
rollback, duplication/fade inheritance, persistence/invalid metadata, undo, peak
bins after source removal and byte-identical source preservation. GUI tests
cover short-clip selection, shared previews, split/duplicate/delete and undo.

View file

@ -21,7 +21,7 @@
zoom/selection and graph positions are session UI state. Undo retains 32 edits
plus baseline; allocation failures are reported, not silently hidden. Media is
never removed by undo/delete. Broad screen-reader/accessibility testing is pending.
- Format v7 reads v1–v6 and uses relative local media references. External absolute
- Format v8 reads v1–v7 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
@ -33,3 +33,8 @@
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.
- Waveform previews load synchronously on the control thread and cache at most
64 paths/1024 bins each. Reopen after external media replacement. Audio cuts
use tick resolution; GUI safety fades can create a short dip at split edges.
Time stretching, independent pitch shifting and crossfades are deferred.

View file

@ -1,4 +1,4 @@
# Project format v7 (reads v1–v6)
# Project format v8 (reads v1–v7)
Phases 5–15 use a bounded text format implemented in C99 without a parser library.
The extension is `.xtc`. File operations run exclusively on the control thread.
@ -151,7 +151,7 @@ discrete-mode compatibility are checked on control-thread compilation. Versions
## Phase 18: version 7 and project folders
Current writer emits v7; readers migrate v1–v6. Record ordering and field bounds
Phase 18 introduced v7; 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
@ -189,3 +189,29 @@ truncation and deterministic byte mutations, malformed plugin metadata, interrup
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.
## Phase 22: version 8 audio clip settings
Current writer emits v8 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:
```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.
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
format change (`make test test-portability test-audio-editing`).

View file

@ -516,6 +516,23 @@ Provide focused, non-destructive audio editing tools for arranging and preparing
- Clip boundaries do not produce unintended clicks from invalid offsets or discontinuities.
- Editing and waveform generation never perform blocking work in the real-time audio callback.
Implemented: transactional audio/silent-clip splitting with stable IDs and source
offsets; persisted linear clip gain and explicit in/out fades in v8 (v1–v7 migrate
to unity/no explicit fades); undoable sequencer menu/context/keyboard controls;
immutable bounded source-peak summaries shared across clips and drawn from
cached memory. Existing move/trim/duplicate/delete remain supported; very short
clip hit testing now matches drawing. See AUDIO_FILES.md and PROJECT_FORMAT.md.
Stretch/pitch processing and crossfades remain future work; waveform loading is
synchronous on the control thread, and physical listening remains pending.
Acceptance evidence:
- [x] Core/GUI regressions cover split/duplicate/delete and snapshot undo.
- [x] Exact byte comparison verifies source files survive all metadata edits.
- [x] Boundary tests cover invalid offsets, short tails, one-frame media, explicit
fade endpoints, gain, block partitions, seeks and capacity rollback.
- [x] Waveform construction stays on control-thread paths; redraw reads cached
peaks and audio rendering retains immutable/preloaded callback storage.
## Phase 23 — Modular Workflow and Routing Refinement
### Goal

View file

@ -105,3 +105,14 @@ 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#phase-21-composition-commands) for shortcuts, timing and limits.
## Audio clip tools
Select an audio clip, click the ruler at the desired cut while stopped, then
choose Edit / Split audio 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.
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#phase-22-non-destructive-clip-editing) for
boundary behavior, preview limits and the deferred stretch/pitch tools.