audio-playback
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAudio Playback
音频播放
Audio playback and scheduling with Tone.js.
使用Tone.js实现音频播放与调度。
Quick Start
快速入门
bash
npm install tonejavascript
import * as Tone from 'tone';
// Simple playback
const player = new Tone.Player('/audio/music.mp3').toDestination();
// Must start audio context after user interaction
document.addEventListener('click', async () => {
await Tone.start();
player.start();
});bash
npm install tonejavascript
import * as Tone from 'tone';
// 简单播放
const player = new Tone.Player('/audio/music.mp3').toDestination();
// 必须在用户交互后启动音频上下文
document.addEventListener('click', async () => {
await Tone.start();
player.start();
});Core Concepts
核心概念
Audio Context Initialization
音频上下文初始化
javascript
import * as Tone from 'tone';
// Audio context requires user gesture to start
async function initAudio() {
await Tone.start();
console.log('Audio context started');
}
// Common pattern: init on first click
document.addEventListener('click', initAudio, { once: true });javascript
import * as Tone from 'tone';
// 音频上下文需要用户操作才能启动
async function initAudio() {
await Tone.start();
console.log('音频上下文已启动');
}
// 常见模式:首次点击时初始化
document.addEventListener('click', initAudio, { once: true });Player Basics
播放器基础
javascript
// Create player
const player = new Tone.Player({
url: '/audio/track.mp3',
loop: true,
autostart: false,
onload: () => console.log('Loaded')
}).toDestination();
// Control
player.start();
player.stop();
player.seek(10); // Seek to 10 seconds
player.volume.value = -6; // Volume in dB
// Properties
player.state; // 'started' | 'stopped'
player.loaded; // boolean
player.duration; // in secondsjavascript
// 创建播放器
const player = new Tone.Player({
url: '/audio/track.mp3',
loop: true,
autostart: false,
onload: () => console.log('已加载')
}).toDestination();
// 控制播放
player.start();
player.stop();
player.seek(10); // 跳转到10秒位置
player.volume.value = -6; // 音量(单位:分贝)
// 属性
player.state; // 'started' | 'stopped'
player.loaded; // 布尔值
player.duration; // 时长(单位:秒)Loading Audio
音频加载
javascript
// Single file
const player = new Tone.Player('/audio/music.mp3');
await player.load('/audio/music.mp3');
// Multiple files with Players
const players = new Tone.Players({
kick: '/audio/kick.mp3',
snare: '/audio/snare.mp3',
hihat: '/audio/hihat.mp3'
}).toDestination();
// Access individual player
players.player('kick').start();
// Buffer for programmatic access
const buffer = new Tone.Buffer('/audio/sample.mp3', () => {
console.log('Buffer loaded, duration:', buffer.duration);
});javascript
// 单个文件
const player = new Tone.Player('/audio/music.mp3');
await player.load('/audio/music.mp3');
// 使用Players加载多个文件
const players = new Tone.Players({
kick: '/audio/kick.mp3',
snare: '/audio/snare.mp3',
hihat: '/audio/hihat.mp3'
}).toDestination();
// 访问单个播放器
players.player('kick').start();
// 使用Buffer实现程序化访问
const buffer = new Tone.Buffer('/audio/sample.mp3', () => {
console.log('Buffer已加载,时长:', buffer.duration);
});Transport
传输控制(Transport)
Basic Transport Control
基础传输控制
javascript
// Global transport (master clock)
Tone.Transport.start();
Tone.Transport.stop();
Tone.Transport.pause();
// Position
Tone.Transport.position = '0:0:0'; // bars:beats:sixteenths
Tone.Transport.seconds = 10; // in seconds
// Tempo
Tone.Transport.bpm.value = 120;
// Time signature
Tone.Transport.timeSignature = [4, 4];javascript
// 全局传输控制(主时钟)
Tone.Transport.start();
Tone.Transport.stop();
Tone.Transport.pause();
// 位置控制
Tone.Transport.position = '0:0:0'; // 小节:节拍:十六分音符
Tone.Transport.seconds = 10; // 秒数
// 节拍速度
Tone.Transport.bpm.value = 120;
// 拍号
Tone.Transport.timeSignature = [4, 4];Scheduling Events
调度事件
javascript
// Schedule at specific time
Tone.Transport.schedule((time) => {
player.start(time);
}, '0:0:0');
// Schedule repeating
Tone.Transport.scheduleRepeat((time) => {
synth.triggerAttackRelease('C4', '8n', time);
}, '4n'); // Every quarter note
// Schedule once
Tone.Transport.scheduleOnce((time) => {
console.log('One time event at', time);
}, '4:0:0'); // At bar 4javascript
// 在指定时间调度事件
Tone.Transport.schedule((time) => {
player.start(time);
}, '0:0:0');
// 调度重复事件
Tone.Transport.scheduleRepeat((time) => {
synth.triggerAttackRelease('C4', '8n', time);
}, '4n'); // 每四分音符触发一次
// 调度一次性事件
Tone.Transport.scheduleOnce((time) => {
console.log('一次性事件触发于', time);
}, '4:0:0'); // 在第4小节触发Time Notation
时间表示法
| Format | Description | Example |
|---|---|---|
| Quarter note | One beat at 4/4 |
| Eighth note | Half a beat |
| Sixteenth note | Quarter beat |
| One measure | Full bar |
| Bars:beats:16ths | Start of bar 2 |
| Relative seconds | 0.5s from now |
| Absolute seconds | At 0.5 seconds |
| 格式 | 说明 | 示例 |
|---|---|---|
| 四分音符 | 4/4拍中的1拍 |
| 八分音符 | 半拍 |
| 十六分音符 | 四分之一拍 |
| 1小节 | 完整小节 |
| 小节:节拍:十六分音符 | 第2小节开始 |
| 相对秒数 | 从现在起0.5秒后 |
| 绝对秒数 | 在0.5秒时 |
Effects Chain
效果链
Basic Signal Flow
基础信号流
javascript
// Source → Effects → Destination
const player = new Tone.Player('/audio/track.mp3');
const reverb = new Tone.Reverb(2);
const volume = new Tone.Volume(-6);
player.chain(reverb, volume, Tone.Destination);javascript
// 音源 → 效果器 → 输出目标
const player = new Tone.Player('/audio/track.mp3');
const reverb = new Tone.Reverb(2);
const volume = new Tone.Volume(-6);
player.chain(reverb, volume, Tone.Destination);Common Effects
常用效果器
javascript
// Reverb
const reverb = new Tone.Reverb({
decay: 2.5,
wet: 0.4
});
// Delay
const delay = new Tone.FeedbackDelay({
delayTime: '8n',
feedback: 0.3,
wet: 0.25
});
// Filter
const filter = new Tone.Filter({
frequency: 1000,
type: 'lowpass',
Q: 2
});
// Compressor
const compressor = new Tone.Compressor({
threshold: -24,
ratio: 4,
attack: 0.003,
release: 0.25
});
// Volume/Gain
const volume = new Tone.Volume(-12);
const gain = new Tone.Gain(0.5);javascript
// 混响
const reverb = new Tone.Reverb({
decay: 2.5,
wet: 0.4
});
// 延迟
const delay = new Tone.FeedbackDelay({
delayTime: '8n',
feedback: 0.3,
wet: 0.25
});
// 滤波器
const filter = new Tone.Filter({
frequency: 1000,
type: 'lowpass',
Q: 2
});
// 压缩器
const compressor = new Tone.Compressor({
threshold: -24,
ratio: 4,
attack: 0.003,
release: 0.25
});
// 音量/增益
const volume = new Tone.Volume(-12);
const gain = new Tone.Gain(0.5);Effect Wet/Dry Mix
效果器干湿比
javascript
const reverb = new Tone.Reverb(2);
reverb.wet.value = 0.5; // 50% wet, 50% dry
// Automate wet mix
reverb.wet.rampTo(1, 2); // Ramp to 100% wet over 2 secondsjavascript
const reverb = new Tone.Reverb(2);
reverb.wet.value = 0.5; // 50%湿信号,50%干信号
// 自动调节干湿比
reverb.wet.rampTo(1, 2); // 在2秒内渐变到100%湿信号Playback Patterns
播放模式
Music Player
音乐播放器
javascript
class MusicPlayer {
constructor() {
this.player = new Tone.Player().toDestination();
this.isPlaying = false;
}
async load(url) {
await this.player.load(url);
}
async play() {
await Tone.start();
this.player.start();
this.isPlaying = true;
}
pause() {
this.player.stop();
this.isPlaying = false;
}
setVolume(db) {
this.player.volume.value = db;
}
seek(seconds) {
const wasPlaying = this.isPlaying;
this.player.stop();
this.player.seek(seconds);
if (wasPlaying) this.player.start();
}
get duration() {
return this.player.buffer?.duration || 0;
}
get currentTime() {
return this.player.immediate();
}
}javascript
class MusicPlayer {
constructor() {
this.player = new Tone.Player().toDestination();
this.isPlaying = false;
}
async load(url) {
await this.player.load(url);
}
async play() {
await Tone.start();
this.player.start();
this.isPlaying = true;
}
pause() {
this.player.stop();
this.isPlaying = false;
}
setVolume(db) {
this.player.volume.value = db;
}
seek(seconds) {
const wasPlaying = this.isPlaying;
this.player.stop();
this.player.seek(seconds);
if (wasPlaying) this.player.start();
}
get duration() {
return this.player.buffer?.duration || 0;
}
get currentTime() {
return this.player.immediate();
}
}Sound Effects Manager
音效管理器
javascript
class SFXManager {
constructor() {
this.sounds = {};
}
async load(name, url) {
const player = new Tone.Player(url).toDestination();
await player.load(url);
this.sounds[name] = player;
}
play(name) {
const sound = this.sounds[name];
if (sound) {
sound.stop(); // Stop if already playing
sound.start();
}
}
setVolume(name, db) {
if (this.sounds[name]) {
this.sounds[name].volume.value = db;
}
}
setMasterVolume(db) {
Tone.Destination.volume.value = db;
}
}
// Usage
const sfx = new SFXManager();
await sfx.load('click', '/audio/click.mp3');
await sfx.load('success', '/audio/success.mp3');
sfx.play('click');javascript
class SFXManager {
constructor() {
this.sounds = {};
}
async load(name, url) {
const player = new Tone.Player(url).toDestination();
await player.load(url);
this.sounds[name] = player;
}
play(name) {
const sound = this.sounds[name];
if (sound) {
sound.stop(); // 如果正在播放则先停止
sound.start();
}
}
setVolume(name, db) {
if (this.sounds[name]) {
this.sounds[name].volume.value = db;
}
}
setMasterVolume(db) {
Tone.Destination.volume.value = db;
}
}
// 使用示例
const sfx = new SFXManager();
await sfx.load('click', '/audio/click.mp3');
await sfx.load('success', '/audio/success.mp3');
sfx.play('click');Looping Ambient Layer
循环环境音层
javascript
class AmbientLayer {
constructor(url) {
this.player = new Tone.Player({
url,
loop: true,
fadeIn: 2,
fadeOut: 2
});
this.volume = new Tone.Volume(-12);
this.reverb = new Tone.Reverb(4);
this.player.chain(this.reverb, this.volume, Tone.Destination);
}
async start() {
await Tone.start();
this.player.start();
}
stop() {
this.player.stop();
}
setIntensity(value) {
// 0-1 range
this.volume.volume.value = -24 + (value * 18); // -24dB to -6dB
this.reverb.wet.value = 0.3 + (value * 0.4); // 30% to 70% wet
}
}javascript
class AmbientLayer {
constructor(url) {
this.player = new Tone.Player({
url,
loop: true,
fadeIn: 2,
fadeOut: 2
});
this.volume = new Tone.Volume(-12);
this.reverb = new Tone.Reverb(4);
this.player.chain(this.reverb, this.volume, Tone.Destination);
}
async start() {
await Tone.start();
this.player.start();
}
stop() {
this.player.stop();
}
setIntensity(value) {
// 取值范围0-1
this.volume.volume.value = -24 + (value * 18); // 从-24dB到-6dB
this.reverb.wet.value = 0.3 + (value * 0.4); // 湿信号占比从30%到70%
}
}Crossfading
交叉淡入淡出
javascript
class CrossfadePlayer {
constructor() {
this.playerA = new Tone.Player();
this.playerB = new Tone.Player();
this.crossfade = new Tone.CrossFade();
this.playerA.connect(this.crossfade.a);
this.playerB.connect(this.crossfade.b);
this.crossfade.toDestination();
this.current = 'a';
}
async loadAndCrossfade(url, duration = 2) {
const nextPlayer = this.current === 'a' ? this.playerB : this.playerA;
const targetFade = this.current === 'a' ? 1 : 0;
await nextPlayer.load(url);
nextPlayer.start();
this.crossfade.fade.rampTo(targetFade, duration);
// Stop old player after crossfade
setTimeout(() => {
const oldPlayer = this.current === 'a' ? this.playerA : this.playerB;
oldPlayer.stop();
}, duration * 1000);
this.current = this.current === 'a' ? 'b' : 'a';
}
}javascript
class CrossfadePlayer {
constructor() {
this.playerA = new Tone.Player();
this.playerB = new Tone.Player();
this.crossfade = new Tone.CrossFade();
this.playerA.connect(this.crossfade.a);
this.playerB.connect(this.crossfade.b);
this.crossfade.toDestination();
this.current = 'a';
}
async loadAndCrossfade(url, duration = 2) {
const nextPlayer = this.current === 'a' ? this.playerB : this.playerA;
const targetFade = this.current === 'a' ? 1 : 0;
await nextPlayer.load(url);
nextPlayer.start();
this.crossfade.fade.rampTo(targetFade, duration);
// 淡入淡出完成后停止旧播放器
setTimeout(() => {
const oldPlayer = this.current === 'a' ? this.playerA : this.playerB;
oldPlayer.stop();
}, duration * 1000);
this.current = this.current === 'a' ? 'b' : 'a';
}
}Synced Playback
同步播放
Sync to Transport
与传输控制同步
javascript
// Player synced to transport
const player = new Tone.Player('/audio/track.mp3');
player.sync().start(0).toDestination();
// Now transport controls playback
Tone.Transport.start();
Tone.Transport.pause();
Tone.Transport.stop();javascript
// 与传输控制同步的播放器
const player = new Tone.Player('/audio/track.mp3');
player.sync().start(0).toDestination();
// 现在由传输控制管理播放
Tone.Transport.start();
Tone.Transport.pause();
Tone.Transport.stop();Multiple Synced Players
多播放器同步
javascript
const drums = new Tone.Player('/audio/drums.mp3').toDestination();
const bass = new Tone.Player('/audio/bass.mp3').toDestination();
const melody = new Tone.Player('/audio/melody.mp3').toDestination();
// Sync all to transport
drums.sync().start(0);
bass.sync().start(0);
melody.sync().start(0);
// Set tempo
Tone.Transport.bpm.value = 120;
// Control all with transport
Tone.Transport.start();javascript
const drums = new Tone.Player('/audio/drums.mp3').toDestination();
const bass = new Tone.Player('/audio/bass.mp3').toDestination();
const melody = new Tone.Player('/audio/melody.mp3').toDestination();
// 将所有播放器与传输控制同步
drums.sync().start(0);
bass.sync().start(0);
melody.sync().start(0);
// 设置节拍速度
Tone.Transport.bpm.value = 120;
// 通过传输控制统一管理所有播放器
Tone.Transport.start();Temporal Collapse Patterns
时间同步模式
Countdown Audio Manager
倒计时音频管理器
javascript
class CountdownAudio {
constructor() {
this.ambient = new Tone.Player({ loop: true });
this.tickSound = new Tone.Player();
this.finalTicks = new Tone.Player();
this.celebration = new Tone.Player();
// Effects
this.filter = new Tone.Filter(2000, 'lowpass');
this.reverb = new Tone.Reverb(3);
// Routing
this.ambient.chain(this.filter, this.reverb, Tone.Destination);
this.tickSound.toDestination();
this.finalTicks.toDestination();
this.celebration.toDestination();
}
async loadAll() {
await Promise.all([
this.ambient.load('/audio/cosmic-ambient.mp3'),
this.tickSound.load('/audio/tick.mp3'),
this.finalTicks.load('/audio/final-tick.mp3'),
this.celebration.load('/audio/celebration.mp3')
]);
}
async start() {
await Tone.start();
this.ambient.start();
}
tick(secondsRemaining) {
if (secondsRemaining <= 10) {
// Intense ticks for final 10 seconds
this.finalTicks.start();
} else {
this.tickSound.start();
}
}
setIntensity(value) {
// 0-1, increases as countdown nears zero
this.filter.frequency.value = 500 + (value * 3500);
this.ambient.volume.value = -12 + (value * 6);
}
celebrate() {
this.ambient.stop();
this.celebration.start();
}
}javascript
class CountdownAudio {
constructor() {
this.ambient = new Tone.Player({ loop: true });
this.tickSound = new Tone.Player();
this.finalTicks = new Tone.Player();
this.celebration = new Tone.Player();
// 效果器
this.filter = new Tone.Filter(2000, 'lowpass');
this.reverb = new Tone.Reverb(3);
// 信号路由
this.ambient.chain(this.filter, this.reverb, Tone.Destination);
this.tickSound.toDestination();
this.finalTicks.toDestination();
this.celebration.toDestination();
}
async loadAll() {
await Promise.all([
this.ambient.load('/audio/cosmic-ambient.mp3'),
this.tickSound.load('/audio/tick.mp3'),
this.finalTicks.load('/audio/final-tick.mp3'),
this.celebration.load('/audio/celebration.mp3')
]);
}
async start() {
await Tone.start();
this.ambient.start();
}
tick(secondsRemaining) {
if (secondsRemaining <= 10) {
// 最后10秒播放急促的滴答声
this.finalTicks.start();
} else {
this.tickSound.start();
}
}
setIntensity(value) {
// 0-1范围,倒计时越接近结束强度越高
this.filter.frequency.value = 500 + (value * 3500);
this.ambient.volume.value = -12 + (value * 6);
}
celebrate() {
this.ambient.stop();
this.celebration.start();
}
}Time-Synced Audio Events
时间同步音频事件
javascript
function scheduleCountdownAudio(targetDate) {
const checkInterval = setInterval(() => {
const now = Date.now();
const remaining = targetDate - now;
const seconds = Math.floor(remaining / 1000);
if (seconds <= 0) {
clearInterval(checkInterval);
audio.celebrate();
return;
}
// Tick every second
audio.tick(seconds);
// Increase intensity as countdown progresses
const intensity = Math.max(0, 1 - (seconds / 3600)); // Over 1 hour
audio.setIntensity(intensity);
}, 1000);
}javascript
function scheduleCountdownAudio(targetDate) {
const checkInterval = setInterval(() => {
const now = Date.now();
const remaining = targetDate - now;
const seconds = Math.floor(remaining / 1000);
if (seconds <= 0) {
clearInterval(checkInterval);
audio.celebrate();
return;
}
// 每秒触发一次滴答声
audio.tick(seconds);
// 随着倒计时推进增加强度
const intensity = Math.max(0, 1 - (seconds / 3600)); // 1小时内逐渐增强
audio.setIntensity(intensity);
}, 1000);
}Performance Tips
性能优化技巧
javascript
// 1. Preload audio before needed
await player.load(url);
// 2. Reuse players instead of creating new ones
player.stop();
player.start(); // Reuse same player
// 3. Dispose when done
player.dispose();
// 4. Use buffer for frequently played sounds
const buffer = new Tone.Buffer(url);
// Create players from buffer
const player = new Tone.Player(buffer);
// 5. Limit concurrent sounds
const limiter = new Tone.Limiter(-3).toDestination();
players.forEach(p => p.connect(limiter));javascript
// 1. 在需要前预加载音频
await player.load(url);
// 2. 复用播放器而非创建新实例
player.stop();
player.start(); // 复用同一个播放器
// 3. 使用完毕后销毁
player.dispose();
// 4. 对频繁播放的音效使用Buffer
const buffer = new Tone.Buffer(url);
// 从Buffer创建播放器
const player = new Tone.Player(buffer);
// 5. 限制并发播放的音效数量
const limiter = new Tone.Limiter(-3).toDestination();
players.forEach(p => p.connect(limiter));Reference
参考
- See for FFT and frequency extraction
audio-analysis - See for visual-audio binding
audio-reactive - See for audio domain routing
audio-router
- 查看了解FFT和频率提取
audio-analysis - 查看了解视觉-音频绑定
audio-reactive - 查看了解音频域路由
audio-router