2026-11-06 15:20:20 +01:00
|
|
|
|
# Timing, Transitions and Audio Quality
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
2026-11-06 14:12:22 +01:00
|
|
|
|
Deterministic transition processing and tests are included. Musical time still
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
Project format remains v4 (reads v1–v3).
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Timing Policy
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
`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
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Boundary Fades and Smoothing
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
The GUI configures `xtc_backend_set_transition_frames(rate/200)`: approximately
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Graceful and Immediate Stopping
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
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,
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
`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.
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
## Numerical and Channel Policy
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-11-06 15:20:20 +01:00
|
|
|
|
lookahead/delay buffer, so they add no timeline delay; they do attenuate
|
|
|
|
|
|
attacks.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Verification and Listening
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
`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.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
2026-11-06 14:12:22 +01:00
|
|
|
|
Automated checks pass warning-enabled core/GUI and no-JACK builds,
|
2026-10-27 03:37:19 +01:00
|
|
|
|
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.
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Physical-device listening and human listening acceptance are **not yet
|
|
|
|
|
|
verified**.
|
2026-10-27 03:37:19 +01:00
|
|
|
|
|
|
|
|
|
|
`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
|
2026-11-06 15:20:20 +01:00
|
|
|
|
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.
|