Measure Preserve's splice-alignment geometry on low-frequency material

A splice can only relocate by [0.75, 1.25]*window, so periods with no multiple
in that interval never phase-align — at 50 ms, f < 16 Hz and 26.7-32 Hz.
Harness runs by hand; too slow to gate.
This commit is contained in:
2026-08-01 21:11:08 -04:00
parent ae59e9b70d
commit ac653aa581
3 changed files with 725 additions and 0 deletions
+10
View File
@@ -50,6 +50,16 @@ reasampler_test(live_delivery LINK sampler_core)
# both mode shapes share, and the Trigger tail's terminal behaviour.
reasampler_test(staged_envelopes LINK sampler_core)
# Measurement harness for Preserve on low-frequency material: how the splice search's
# reachable relocation interval interacts with a long source period. Written longhand and
# deliberately NOT add_test()'d — it sweeps frequencies, windows and spectra and takes ~2m40s
# in Debug, which does not belong in a gate whose other targets run in seconds. It still
# builds with everything else, so it cannot rot into non-compilation. Run it by hand, in
# Release, when the question is what Preserve does to a given frequency.
add_executable(preserve_low_frequency_tests
${REASAMPLER_TESTS_DIR}/test_preserve_low_frequency.cpp)
target_link_libraries(preserve_low_frequency_tests PRIVATE sampler_core)
# The Preserve read's source-feed schedule — the TIME half beside pitch_shift's PITCH half.
# Header-only (it sits on the per-sample feed), hence INTERFACE.
add_library(time_stretch INTERFACE)
+15
View File
@@ -27,6 +27,21 @@ namespace reasampler::instrument::engine {
// -24 st is reachable from the Pitch knob alone. (The pre-stretch rate-1.0 engine's floor by
// the same inequality is P > 735, ~60 Hz — what this range raises the floor from, not what
// it removes.)
//
// A SECOND, INDEPENDENT limit binds the same material, and no rate bound touches it. A splice
// relocates the tap by the nominal window refined by a search over +/- window/4, so the
// reachable relocation distances are exactly [0.75, 1.25] * window; a phase-aligned splice
// needs a WHOLE NUMBER of source periods inside that one interval. The interval is 0.5*window
// wide, so any period <= window/2 always has a multiple in it — but above that, coverage
// breaks into disjoint bands (n=1 covers periods [0.75, 1.25]*window, n=2 covers
// [0.375, 0.625]*window) and the gap between them is reachable by nothing. Because both the
// interval and the period scale with the sample rate, the unalignable set is fixed in Hz by
// the window's MILLISECONDS: at 50 ms that is f < 16 Hz and 26.7 Hz < f < 32 Hz. Measured
// (Release, 44.1k and 48k) at 30 Hz: the rendered pitch stays correct, but energy outside the
// fundamental is 3.6% at +2 st / rate 1.0 and 15.5% at rate 2.0, against 0.00% at 34 Hz under
// identical conditions; at 29 Hz / rate 2.0 the tone itself lands 7.4% flat. Unlike the
// cadence inequality above, this one is not about how OFTEN a splice fires — a window of at
// least two source periods removes it outright, and nothing else does.
inline constexpr double kStretchRateMin = 0.5;
inline constexpr double kStretchRateMax = 2.0;
inline constexpr int kMaxFeedPerFrame = 2; // ceil(kStretchRateMax)