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.