Time-stretching & pitch-shifting#
TimeStretchProcessor is the block-based, backend-agnostic facade for
time-stretching (tempo change) and pitch-shifting. It delegates to one of two
engines behind a common Engine interface:
Time-domain backend (always available) - a built-in WSOLA-style synchronous overlap-add engine that also pitch-shifts through resampling.
Bungee backend (only when
YUP_ENABLE_BUNGEE) - wraps the external Bungee library, grain-based and pull driven.
Backend::automatic (the default) picks Bungee when compiled in, otherwise
the time-domain backend. getAvailableBackends() and isBackendAvailable()
report what the current build supports, and setBackend() can switch at
runtime (reinitializing the engine).
Basic usage#
using namespace yup;
TimeStretchProcessor processor;
TimeStretchProcessor::ProcessSpec spec;
spec.inputSampleRate = 48000.0;
spec.outputSampleRate = 48000.0; // 0 = same as input
spec.maximumBlockSize = 512;
spec.numChannels = 2;
processor.prepare (spec); // NOT realtime-safe; call at init
processor.setTimeRatio (1.5); // 1.5x slower (output/input length)
processor.setPitchRatio (1.0); // no pitch shift
const int outputFrames = processor.getExpectedOutputFrameCount (512);
auto result = processor.process (inputPointers, 512, outputPointers, outputFrames);
timeRatio— output length / input length (1.0= none,> 1.0slows down,< 1.0speeds up). Non-positive values are clamped to1.0.pitchRatio— frequency multiplier (1.0= none,> 1.0shifts up).process (…)takes an explicit output frame count and returns the number of frames actually written viaResultValue<int>(may differ by ±1 depending on the backend’s rounding).processUsingTimeRatio (…)derives the output count from the storedtimeRatioinstead.AudioBuffer<float>overloads wrap the raw-pointer versions.
Input models#
The processor accepts input in two ways:
Direct pointers -
process (inputChannels, inputFrameCount, …)copies the provided block. Input may not exceedmaximumBlockSize.InputProvider - a callback-based pull model used by the granular backends:
processor.setInputProvider (
[](int64 beginFrame, int numFrames, float* const* destChannels,
int channelStride, int& muteHead, int& muteTail)
{
// fill destChannels[ch] with numFrames starting at beginFrame;
// may set muteHead/muteTail to zero-fill block edges
});
When a provider is set, the inputChannels/inputFrameCount arguments of
process are ignored. getMaxInputFrameCount() reports the largest request
the provider may receive.
Controls and introspection#
setInputPosition (int64)— seek to an input frame position.setParameters ({ timeRatio, pitchRatio })/setTimeRatio/setPitchRatio— realtime-safe updates.getExpectedOutputFrameCount (inputFrames)—round (inputFrames × timeRatio).getLatencyInFrames ()— backend-reported latency in input frames.getBackend ()/getBackendName ()("Time Domain","Bungee","None"when unprepared).reset ()— clears state and latency (not guaranteed realtime-safe).
Note
prepare, reset, and setBackend are not realtime-safe; call them during
initialization or while the audio thread is paused. process, the parameter
setters, setInputPosition, and setInputProvider form the realtime path.
The backends#
Time-domain backend#
TimeDomainTimeStretchBackend is the built-in engine (backend name
"Time Domain"). It splits the input into analysis sequences
(defaultSequenceLengthMs = 82 ms), cross-fades 12 ms overlaps, and finds
the best overlap position within a 14 ms seek window using a hierarchical
quickScanOffsets table (coarse-to-fine cross-correlation summed over all
channels). Pitch-shifting (pitchRatio ≠ 1) is implemented by
time-stretching first and then resampling with a ResamplerFloat — this is
why pitch shifts change duration unless the time ratio compensates.
Tempo (1 / timeRatio) is clamped to [1, 4], so the effective stretch
ratio is at least 0.25 (4× speed-up). Unity tempo takes a direct-copy fast
path. Latency is reported as the input/output FIFO backlog difference
(max (0, input − output)).
Bungee backend#
BungeeTimeStretchBackend (backend name "Bungee", compiled only under
YUP_ENABLE_BUNGEE) drives Bungee::Stretcher<Bungee::Basic> with a
grain-based pull loop: it requests grains with specifyGrain, fetches input
from the InputProvider (which is required — without one, process
returns 0 frames), and analyses/synthesises grains until the requested output
is produced. It sends speed = 1/timeRatio and pitch = pitchRatio with
resampleMode_autoOut, and prerolls on seek. Latency is derived from the
input/output grain positions.