audio-playback

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Audio Playback

音频播放

Audio playback and scheduling with Tone.js.
使用Tone.js实现音频播放与调度。

Quick Start

快速入门

bash
npm install tone
javascript
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 tone
javascript
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 seconds
javascript
// 创建播放器
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 4
javascript
// 在指定时间调度事件
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

时间表示法

FormatDescriptionExample
'4n'
Quarter noteOne beat at 4/4
'8n'
Eighth noteHalf a beat
'16n'
Sixteenth noteQuarter beat
'1m'
One measureFull bar
'2:0:0'
Bars:beats:16thsStart of bar 2
'+0.5'
Relative seconds0.5s from now
0.5
Absolute secondsAt 0.5 seconds
格式说明示例
'4n'
四分音符4/4拍中的1拍
'8n'
八分音符半拍
'16n'
十六分音符四分之一拍
'1m'
1小节完整小节
'2:0:0'
小节:节拍:十六分音符第2小节开始
'+0.5'
相对秒数从现在起0.5秒后
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 seconds
javascript
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
    audio-analysis
    for FFT and frequency extraction
  • See
    audio-reactive
    for visual-audio binding
  • See
    audio-router
    for audio domain routing
  • 查看
    audio-analysis
    了解FFT和频率提取
  • 查看
    audio-reactive
    了解视觉-音频绑定
  • 查看
    audio-router
    了解音频域路由