Phase 12: Improve audio transitions and verify sample-accurate timing
This commit is contained in:
parent
461689dd44
commit
05140c2e93
29 changed files with 795 additions and 33 deletions
|
|
@ -280,3 +280,13 @@ and serialization limits and PERFORMANCE.md for measured workload and budgets.
|
|||
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.
|
||||
|
||||
## Phase 12 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.
|
||||
|
|
|
|||
168
docs/AUDIO_QUALITY.md
Normal file
168
docs/AUDIO_QUALITY.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# Timing, transitions and audio quality
|
||||
|
||||
Phase 12 adds deterministic transition processing and tests. 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.
|
||||
Project format remains v4 (reads v1–v3).
|
||||
|
||||
## 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.
|
||||
|
||||
Every musical boundary is converted from its **absolute** tick:
|
||||
|
||||
```
|
||||
sample = floor(tick * rate * 60000 / (tempo_milli_bpm * 960))
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Changing tempo shifts musical boundaries, including source offsets, on newly
|
||||
compiled plans. WAV samples keep their original playback speed with linear rate
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
nearer boundary the factor is `min(1, d/fade_frames)`. For audio, the end is the
|
||||
earlier of the clip end and the actual loaded media end after source offset/rate
|
||||
conversion; rounded-up imported duration does not expose an unfaded last sample.
|
||||
The envelope is computed from absolute sample position: render partitioning,
|
||||
seeks, overlapping regions and mono/stereo calls do not advance shared envelope
|
||||
state. Very short regions are attenuated throughout; a one-sample region becomes
|
||||
silent. Adjacent regions each fade independently, so this is not a crossfade or
|
||||
seamless-loop implementation. Fade configuration is a host policy, not persisted
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
`xtc_backend_stop`/close remain synchronous immediate operations. Recording Stop
|
||||
uses immediate deactivation before draining/finalizing the input take; recording
|
||||
samples are never faded by the output master. Graceful requests refuse attached
|
||||
recorders. Home/seek, project installation, recording start, panic, errors and
|
||||
server loss retain immediate semantics. Those transitions can click; safety and
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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,
|
||||
external plugins or parallel compensated processing paths. Native extensions can
|
||||
introduce unknown delay, which is neither reported nor compensated. A latency/
|
||||
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
|
||||
|
||||
```
|
||||
make all test gui test-reliability test-workflow test-gui test-gui-jack
|
||||
make test-jack test-jack-audio test-jack-midi test-performance
|
||||
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.
|
||||
|
||||
Phase 12 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**.
|
||||
|
||||
`make quality-preview` writes ignored build artifacts `quality-raw.wav` and
|
||||
`quality-smooth.wav` in BUILD_DIR using `examples/quality_preview.c`. Compare at
|
||||
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.
|
||||
|
|
@ -239,3 +239,10 @@ full verification commands and remaining limits.
|
|||
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.
|
||||
|
||||
## Phase 12 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).
|
||||
|
|
|
|||
|
|
@ -193,3 +193,12 @@ even when the previous document is retained.
|
|||
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.
|
||||
|
||||
## Phase 12 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.
|
||||
|
|
|
|||
|
|
@ -114,3 +114,14 @@ arbitrary third-party native callbacks remain unqualified. Sanitizers change
|
|||
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.
|
||||
|
||||
## Phase 12 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
|
||||
while active; start initializes them only while quiescent. Control publishes an
|
||||
atomic stop request; audio publishes completion after the last ramp sample.
|
||||
Collection acquires completion and deactivates the backend before resetting
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -199,6 +199,18 @@ explicit; physical-device listening and endurance remain unqualified.
|
|||
- Listening tests cover transitions, start/stop behavior, and representative projects.
|
||||
- Known limitations involving latency and synchronization are documented.
|
||||
|
||||
Implemented: documented constant-tempo/fixed-4/4 snapshot policy; absolute timing
|
||||
and sample-offset regression matrix; configurable audio/scheduled-note boundary
|
||||
fades; single-owner gain ramps; GUI 5 ms start and asynchronous Pause/Stop fades
|
||||
with control-thread acknowledgement/deactivation; finite/subnormal output policy;
|
||||
exact endpoint, block-size, stereo, overload and failure/retry tests. Latency and
|
||||
synchronization limits are documented in AUDIO_QUALITY.md. Normal/no-JACK,
|
||||
sanitizer/static-analysis, TSan, GUI/workflow/live JACK and install checks pass.
|
||||
`make quality-preview` generates matched listening WAVs. Human listening and
|
||||
physical-device acceptance remain pending; immediate safety/recording/seek paths
|
||||
and live graph swaps may still click. No tempo/meter map, latency compensation,
|
||||
limiter or project-format change is claimed.
|
||||
|
||||
## Phase 13 — Recording and Recovery
|
||||
|
||||
**Goal:** Protect recorded material and handle recording failures predictably.
|
||||
|
|
|
|||
|
|
@ -34,13 +34,17 @@ velocity 100; no notes or test oscillators are inserted automatically.
|
|||
| Command | Behavior |
|
||||
| --- | --- |
|
||||
| Play / F5 | Prepare the current arrangement and resume at the retained sample position. Already playing is a no-op. |
|
||||
| Pause / F7 | Deactivate playback and hold the sample position. Play resumes from there. Disabled during recording. |
|
||||
| Stop / F8 | Hold the sample position, preserving the established Stop behavior. During recording, finalize and add the take. |
|
||||
| Pause / F7 | Fade out, then deactivate playback and hold the final block-end sample position. Play resumes from there. Disabled during recording. |
|
||||
| Stop / F8 | Fade out and hold the final block-end sample position. During recording, finalize and add the take. |
|
||||
| Space | Toggle Play/Stop; while recording it finishes the take. |
|
||||
| To start / Home | Stop and seek to sample zero; remain stopped until Play. Disabled during recording. Also resets an offline cursor. |
|
||||
| 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.
|
||||
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue