feat: render bounded effect tails and reset internal history on transport changes

This commit is contained in:
Johannes Findeisen 2026-11-05 23:55:56 +01:00
commit 836cdedc6b
22 changed files with 284 additions and 18 deletions

View file

@ -152,3 +152,39 @@ 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.
## Phase 44 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.
**File → Export WAV** renders the song plus the Preferences / transport
`tail_ms` duration (0..60000, default 3000). Output is stereo float32 WAV at the
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
oscillator/feedback sources early: the configured endpoint always bounds work.
The public `xtc_export_create/step/progress/destroy` API supports incremental
rendering and cancellation by destruction. A failed step must be followed by
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.
Tests compare stereo export against direct rendering in 31-frame blocks, verify
nonzero tails, exact length, cancellation, existing-file retention, tail bounds,
history reset and callback-safe reset. Preview generation and manual listening
procedures remain available; no human listening result is claimed.

View file

@ -66,3 +66,8 @@ Validation covers settings roundtrips, malformed/duplicate/overflow/NUL input,
private permissions, save failure, GUI Apply/Close/reload and plugin listing,
plus JACK callback wrap positions and existing GUI/core regressions. Tests use
temporary configuration paths and do not write the user's configuration.
Phase 44 adds `[transport] tail_ms=3000` (0..60000) and
`stop_after_tail=0` (0/1). 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.

View file

@ -1239,15 +1239,19 @@ documented; human listening is unverified. See BUILTIN_DEVICES.md.
Make discontinuities and effect tails explicit in playback and export.
### Tasks and acceptance criteria
- [ ] Define retained/reset effect history for seek, stop, loop wrap and project/plan replacement.
- [ ] Implement the chosen history policy without callback allocation or unsafe state sharing.
- [ ] Add bounded automatic or user-configurable tail rendering after the arrangement/export endpoint.
- [ ] Expose tail limits and cancellation, including silent input and non-decaying/high-feedback cases.
- [ ] Test seek/loop reproducibility and complete delay/reverb tails across processing block sizes.
- [x] Define retained/reset effect history for seek, stop, loop wrap and project/plan replacement.
- [x] Implement the chosen history policy without callback allocation or unsafe state sharing.
- [x] Add bounded automatic or user-configurable tail rendering after the arrangement/export endpoint.
- [x] Expose tail limits and cancellation, including silent input and non-decaying/high-feedback cases.
- [x] Test seek/loop reproducibility and complete delay/reverb tails across processing block sizes.
### Implementation status
Planned. Seeks currently retain effect history; tails need continued rendering
and do not automatically extend playback/export.
Implemented: constant-cost internal history invalidation on discontinuity/stop,
plus incremental stereo WAV export with 0..60-second tails, progress/cancel and
no-clobber publication. Preferences optionally stop playback after the tail
(with GUI-timer/fade precision); loops/recording retain their existing behavior.
Core, reliability, export/direct partition, GUI and sanitizer checks pass.
External-plugin history remains retained. See BUILTIN_DEVICES.md for limits.
---