The fixed-cycle splice stretcher. More...
#include <cyclic_stretch.hpp>
Public Member Functions | |
| void | prepare (double sample_rate) |
| Sizes the capture ring for kCaptureMsMax and the accumulator for the largest grain the parameter ranges permit, so no later set_* can reallocate. | |
| void | reset () |
| Never allocates. A zero-initialised instance is already in this state. | |
| void | set_cycle_hz (double hz) |
| void | set_grain_periods (int periods) |
| void | set_crossfade_pct (double pct) |
| void | set_crossfade_shape (double shape) noexcept |
| void | set_stretch_ratio (double ratio) noexcept |
| void | set_capture_ms (double ms) |
| void | set_mix (double percent) noexcept |
| void | set_output_db (double db) noexcept |
| void | set_regime (const CyclicStretchRegime ®ime) |
| Apply one of the two documented regimes in a single call. | |
| int | cycle_samples () const noexcept |
| L — cycle length in samples. | |
| int | grain_samples () const noexcept |
| N — grain length in samples. | |
| int | crossfade_samples () const noexcept |
| X — splice overlap in samples. | |
| int | hop_samples () const noexcept |
| S — synthesis hop in samples. | |
| double | flutter_hz () const noexcept |
| The metallic flutter fundamental, fs / S, in Hz. | |
| long long | capture_window_samples () const noexcept |
| The active capture window in samples — what the falling-behind clamp folds against, which is the configured capture_ms, not the allocation. | |
| double | sample_rate () const noexcept |
| double | stretch_ratio () const noexcept |
| double | crossfade_shape () const noexcept |
| int | latency_samples () const noexcept |
| The initial fill, reported exactly (series law 5). | |
| double | worst_case_gain () const noexcept |
| The proven bound on |out| / max|in| — see the file doc block. | |
| long long | schedule_input_position (long long out_pos) const noexcept |
| The splice schedule at a given OUTPUT position — no clamping, no buffer state, a pure function of the resolved lengths and the ratio. | |
| long long | grain_input_position (long long g) const noexcept |
| The schedule at grain index g, valid while the resolved lengths hold still — out_pos(g) = g·S only as long as S has not moved. | |
| long long | grain_count () const noexcept |
| Number of grains emitted since reset(). | |
| long long | next_grain_out_pos () const noexcept |
| The next grain's output position, in absolute output samples. | |
| long long | read_position () const noexcept |
| The live read cursor, in absolute INPUT samples — where the last grain actually read from after the bounded-ring fold. | |
| long long | total_captured () const noexcept |
| Input samples captured since reset(). | |
| void | process (const SampleType *in, SampleType *out, int n) |
| One block. | |
Static Public Attributes | |
| static constexpr double | kCycleHzMin = 20.0 |
| The assumed cycle pitch — the user-tuned "cycle" of the control surface. | |
| static constexpr double | kCycleHzMax = 2000.0 |
| static constexpr double | kCycleHzDefault = 200.0 |
| static constexpr int | kGrainPeriodsMin = 1 |
| Whole cycles per grain. [design parameter] default 1, range 1 .. 16. | |
| static constexpr int | kGrainPeriodsMax = 16 |
| static constexpr int | kGrainPeriodsDefault = 1 |
| static constexpr double | kCrossfadePctMin = 1.0 |
| Splice overlap as a percentage of the grain. | |
| static constexpr double | kCrossfadePctMax = 50.0 |
| static constexpr double | kCrossfadePctDefault = 25.0 |
| static constexpr double | kCrossfadeShapeDefault = 1.0 |
| 0 = equal-gain (linear), 1 = equal-power (cosine), blended in between. | |
| static constexpr double | kStretchRatioMin = 0.25 |
| Output duration over input duration. | |
| static constexpr double | kStretchRatioMax = 4.0 |
| static constexpr double | kStretchRatioDefault = 1.0 |
| static constexpr double | kCaptureMsMin = 250.0 |
| Capture ring capacity in ms. | |
| static constexpr double | kCaptureMsMax = 8000.0 |
| static constexpr double | kCaptureMsDefault = 2000.0 |
| static constexpr double | kMixDefault = 100.0 |
| Dry/wet in percent; the dry path is delayed by N so the blend is phase-honest. | |
| static constexpr double | kOutputDbMin = -24.0 |
| Post-gain trim, in dB. | |
| static constexpr double | kOutputDbMax = 12.0 |
| static constexpr double | kOutputDbDefault = 0.0 |
| static constexpr int | kMinCycleSamples = 4 |
| Floor on L. | |
| static constexpr int | kMinHopSamples = 2 |
| Floor on S. | |
| static constexpr double | kWorstCaseGain = 1.41421356237309504880 |
| The proven splice bound. | |
The fixed-cycle splice stretcher.
|
inlinenoexcept |
The active capture window in samples — what the falling-behind clamp folds against, which is the configured capture_ms, not the allocation.
|
inlinenoexcept |
X — splice overlap in samples.
|
inlinenoexcept |
|
inlinenoexcept |
L — cycle length in samples.
|
inlinenoexcept |
The metallic flutter fundamental, fs / S, in Hz.
|
inlinenoexcept |
Number of grains emitted since reset().
|
inlinenoexcept |
The schedule at grain index g, valid while the resolved lengths hold still — out_pos(g) = g·S only as long as S has not moved.
The live path uses the running cursor below instead, for the reason given at grain_out_pos_.
|
inlinenoexcept |
N — grain length in samples.
|
inlinenoexcept |
S — synthesis hop in samples.
|
inlinenoexcept |
The initial fill, reported exactly (series law 5).
This is the only meaningful latency a time-scaling process has: for r ≠ 1 there is no steady-state constant group delay to report, because the output is not a delayed copy of the input at all.
|
inlinenoexcept |
The next grain's output position, in absolute output samples.
|
inline |
Sizes the capture ring for kCaptureMsMax and the accumulator for the largest grain the parameter ranges permit, so no later set_* can reallocate.
The only method that allocates.
|
inline |
One block.
Allocation-free, lock-free, and independent of block size: every decision is driven by absolute counters, so splitting a render into different block sizes cannot change a sample of it.
|
inlinenoexcept |
The live read cursor, in absolute INPUT samples — where the last grain actually read from after the bounded-ring fold.
Exposed because the fold is the one part of the design a rendered output cannot show directly: "the stretch is still stretching after ten seconds" and "the stretch quietly became 1× four seconds ago" produce very similar-looking audio, and only the cursor distinguishes them.
|
inline |
Never allocates. A zero-initialised instance is already in this state.
|
inlinenoexcept |
|
inlinenoexcept |
The splice schedule at a given OUTPUT position — no clamping, no buffer state, a pure function of the resolved lengths and the ratio.
Exposed so the snap rule can be asserted against its closed form directly, rather than inferred from a render where the bounded-ring clamps have already folded it.
|
inline |
|
inline |
|
inlinenoexcept |
|
inline |
|
inline |
|
inlinenoexcept |
|
inlinenoexcept |
|
inline |
Apply one of the two documented regimes in a single call.
|
inlinenoexcept |
|
inlinenoexcept |
|
inlinenoexcept |
Input samples captured since reset().
|
inlinenoexcept |
The proven bound on |out| / max|in| — see the file doc block.
Returns the bound for the CURRENT shape; kWorstCaseGain is the bound over all shapes and is what a registry cites.
|
staticconstexpr |
|
staticconstexpr |
|
staticconstexpr |
Capture ring capacity in ms.
Bounds how far a long stretch may fall behind before it folds forward, and therefore bounds the memory. [design parameter] default 2000, range 250 .. 8000, log.
|
staticconstexpr |
|
staticconstexpr |
|
staticconstexpr |
Splice overlap as a percentage of the grain.
The 50 % ceiling is structural, not taste: above it three grains would overlap at once, which breaks both the two-term OLA and the √2 bound derived from it. [design parameter] default 25, range 1 .. 50.
|
staticconstexpr |
0 = equal-gain (linear), 1 = equal-power (cosine), blended in between.
[design parameter] default 1, range 0 .. 1.
|
staticconstexpr |
|
staticconstexpr |
|
staticconstexpr |
The assumed cycle pitch — the user-tuned "cycle" of the control surface.
Not tracked, never inferred: setting it away from the material's fundamental is what produces the detune buzz, so tracking it would delete the effect. [design parameter] default 200 Hz, range 20 .. 2000 Hz, log.
|
staticconstexpr |
|
staticconstexpr |
|
staticconstexpr |
Whole cycles per grain. [design parameter] default 1, range 1 .. 16.
|
staticconstexpr |
Floor on L.
The smallest cycle length that leaves the splice (minimum X = 1) and the grain interior as distinct, non-degenerate regions. [design parameter, fixed] 4 samples — an engineering floor, not a tuned value, so it carries no range.
|
staticconstexpr |
Floor on S.
The smallest synthesis hop for which consecutive out_pos grid points are distinguishable samples. [design parameter, fixed] 2 samples — likewise an engineering floor.
|
staticconstexpr |
Dry/wet in percent; the dry path is delayed by N so the blend is phase-honest.
[design parameter] default 100, range 0 .. 100.
|
staticconstexpr |
|
staticconstexpr |
|
staticconstexpr |
Post-gain trim, in dB.
Its positive half exists so a patch can pull back the +3.01 dB equal-power splice boost without pretending it is not there. [design parameter] default 0, range −24 .. +12 dB.
|
staticconstexpr |
|
staticconstexpr |
|
staticconstexpr |
Output duration over input duration.
1.0 is NOT a bypass — grains are still spliced on the grid, so identity still carries the flutter, which is faithful to a machine whose 100 % setting was never transparent. A true bypass is mix = 0. [design parameter] default 1.0, range 0.25 .. 4.0, log.
|
staticconstexpr |
The proven splice bound.
See the file doc block for the derivation; this is not a measured maximum.