跳转到内容

节奏变更

介绍节奏变更和缓冲状态监控模式。

使用模块: play, stretcher

节奏变更演示

基于 WSOLA 的保持音高时间拉伸功能演示。使用节奏滑块实时调整播放速度。

音源

使用较长的音源来体验保持音高的时间拉伸。

分块缓冲演示

用于观察 WSOLA 拉伸引擎分块缓冲机制的演示。实时监控分块处理状态和事件。

音源

使用较长的音源来观察分块行为。

代码示例

1. 保持音高的节奏变更

由于 preservePitch 默认为 true,因此在正常的 play() 调用中会自动保持音高。如需常规速度变更(音高也会改变),请明确设置 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 playback
playback.setPlaybackRate(1.2); // 1.2x speed, pitch maintained
// To change pitch along with tempo, set preservePitch: false
const playback2 = waa.play(buffer, {
playbackRate: 1.5,
preservePitch: false,
});

WSOLA 算法允许在不改变音高的情况下改变节奏。设置 preservePitch: true(默认)将启用基于 WSOLA 的时间拉伸。

函数 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 playback
playback.setPlaybackRate(1.2);
// To change pitch along with tempo, set preservePitch: false
const playback2 = play(ctx, buffer, {
playbackRate: 1.5,
preservePitch: false,
});

2. 监控缓冲状态

由于 WSOLA 处理是异步的,因此在改变节奏时可能会发生缓冲。

const playback = waa.play(buffer, { playbackRate: 0.8 });
// Monitor buffering start
playback.on("buffering", ({ reason }) => {
console.log(`Buffering... (reason: ${reason})`);
// Show loading UI
});
// Monitor buffering completion
playback.on("buffered", ({ stallDuration }) => {
console.log(`Buffering complete (${stallDuration.toFixed(0)}ms)`);
// Hide loading UI
});
// Check stretcher state via snapshot
const 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)}%`);
}

buffering 事件中的 reason"initial" | "seek" | "tempo-change" | "underrun" 之一。您可以通过 getSnapshot() 中的 stretcher 字段获取详细状态信息。

函数 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)}%`);
}

相关 API