Fix cross-rate Preserve splices, re-stamp on host-rate change, name the rate refusal

The splice crossfade now counts output frames and stops before the ring's far end (this also fixes a lap at matched rate). The search strides by the rate ratio. The editor takes its time base from its own decode. Same-rate output is pinned to hashes from before the change.
This commit is contained in:
2026-09-25 14:17:11 -04:00
parent ec60cf78c6
commit 64f314a6a4
24 changed files with 592 additions and 183 deletions
+12 -3
View File
@@ -178,15 +178,24 @@ voice reads SOURCE frames and emits OUTPUT frames, and `SampleData::sourceRateRa
the shifter's tap at note-on.
- **Source frames:** the PCM, the loop and its crossfade, the start point, the Trigger span, the
spline phase, the detected period, and the Preserve ring (`preserveWindowFrames`).
spline phase, the detected period, and the Preserve ring (`preserveWindowFrames`) with every
splice distance in it.
- **Output frames (the host's):** every stage time in `SampleData::play` (resolved at
`SampleData::renderRate`), the filter solve, the live ramps and the declick. A source-offset
envelope converts through `stageFitRate`, which carries the ratio.
`SampleData::renderRate`), the filter solve, the live ramps, the declick, and the shifter's
splice crossfade (`PitchShifter::setSourceRateRatio`, which also strides the splice search to
a matched rate's cost). A source-offset envelope converts through `stageFitRate`, which
carries the ratio.
- **Rejected alternative: resample at load.** It would round every loop point into the host
domain (detuning a single-cycle loop), force the editor and the bank intrinsics through a
conversion at every edge, and need a band-limited resampler in the core. The cost of the
chosen shape is that a mismatched capture's ROOT note is interpolated like any other repitched
note already is.
- **That cost is asymmetric.** Nothing band-limits the read, so a file ABOVE the host rate
folds whatever it holds over the host's Nyquist into the audible band even at its root — the
aliasing a matched capture only meets when transposed up. A file BELOW the host rate is read
slower than one frame per frame and only images, the milder direction. Offline captures
render at the project rate (`shell/capture` pins `RENDER_SRATE`), so the exposed case is an
ingested high-rate file, or a host running below the rate a capture was made at.
### Live parameter delivery — a knob moves the note already sounding (settled 2026-07-30)