Tempo Change
Patterns for tempo changes and monitoring buffering state.
Modules used: play, stretcher
Tempo Change Demo
A demo of WSOLA-based pitch-preserving time-stretch. Use the tempo slider to change playback speed in real-time.
Sound Source
Use a longer sound source to experience pitch-preserving time-stretch.
Waveform
Tempo Change
Chunk Buffering Demo
A demo to observe the chunk buffering mechanism of the WSOLA stretcher engine. Monitor chunk processing states and events in real-time.
Sound Source
Use a longer sound source to observe chunk splitting behavior.
Waveform
Chunk Buffering
Chunk States
Event Log
Code Examples
1. Pitch-Preserving Tempo Change
Since preservePitch defaults to true, pitch is preserved by default in normal play() calls. For regular speed changes (where pitch also changes), explicitly set preservePitch: false.
import { WaaPlayer } from "waa-play";
const waa = new WaaPlayer();await waa.ensureRunning();const buffer = await waa.load("/audio/track.mp3");
// Change tempo while preserving pitch (default behavior)const playback = waa.play(buffer, { playbackRate: 0.8 });
// Change tempo during playbackplayback.setPlaybackRate(1.2); // 1.2x speed, pitch maintained
// To change pitch along with tempo, set preservePitch: falseconst playback2 = waa.play(buffer, { playbackRate: 1.5, preservePitch: false,});The WSOLA algorithm allows you to change tempo without changing pitch. Setting preservePitch: true (default) enables WSOLA-based time stretching.
Function API
import { createContext, ensureRunning } from "waa-play/context";import { loadBuffer } from "waa-play/buffer";import { play } from "waa-play/play";
const ctx = createContext();await ensureRunning(ctx);const buffer = await loadBuffer(ctx, "/audio/track.mp3");
// Change tempo while preserving pitch (default behavior)const playback = play(ctx, buffer, { playbackRate: 0.8 });
// Change tempo during playbackplayback.setPlaybackRate(1.2);
// To change pitch along with tempo, set preservePitch: falseconst playback2 = play(ctx, buffer, { playbackRate: 1.5, preservePitch: false,});2. Monitoring Buffering State
Since WSOLA processing is asynchronous, buffering may occur when changing tempo.
const playback = waa.play(buffer, { playbackRate: 0.8 });
// Monitor buffering startplayback.on("buffering", ({ reason }) => { console.log(`Buffering... (reason: ${reason})`); // Show loading UI});
// Monitor buffering completionplayback.on("buffered", ({ stallDuration }) => { console.log(`Buffering complete (${stallDuration.toFixed(0)}ms)`); // Hide loading UI});
// Check stretcher state via snapshotconst snapshot = waa.getSnapshot(playback);if (snapshot.stretcher) { console.log(`Tempo: ${snapshot.stretcher.tempo}`); console.log(`Buffer health: ${snapshot.stretcher.bufferHealth}`); console.log(`Converting: ${snapshot.stretcher.converting}`); console.log(`Conversion progress: ${(snapshot.stretcher.conversionProgress * 100).toFixed(0)}%`);}The reason in the buffering event is one of "initial" | "seek" | "tempo-change" | "underrun". You can get detailed state information via the stretcher field in getSnapshot().
Function API
import { getSnapshot } from "waa-play/adapters";
const playback = play(ctx, buffer, { playbackRate: 0.8 });
playback.on("buffering", ({ reason }) => { console.log(`Buffering... (reason: ${reason})`);});
playback.on("buffered", ({ stallDuration }) => { console.log(`Buffering complete (${stallDuration.toFixed(0)}ms)`);});
const snapshot = getSnapshot(playback);if (snapshot.stretcher) { console.log(`Tempo: ${snapshot.stretcher.tempo}`); console.log(`Buffer health: ${snapshot.stretcher.bufferHealth}`); console.log(`Converting: ${snapshot.stretcher.converting}`); console.log(`Conversion progress: ${(snapshot.stretcher.conversionProgress * 100).toFixed(0)}%`);}