Claude Code Plugins

Community-maintained marketplace

Feedback

Audio playback using Tone.js including players, transport, scheduling, and loading audio. Use when implementing background music, sound effects, audio synchronization, or timed audio events. Essential for any audio-enabled web application.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name audio-playback
description Audio playback using Tone.js including players, transport, scheduling, and loading audio. Use when implementing background music, sound effects, audio synchronization, or timed audio events. Essential for any audio-enabled web application.

Audio Playback

Audio playback and scheduling with Tone.js.

Quick Start

npm install tone
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();
});

Core Concepts

Audio Context Initialization

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 });

Player Basics

// 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

Loading Audio

// 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);
});

Transport

Basic Transport Control

// 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];

Scheduling Events

// 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

Time Notation

Format Description Example
'4n' Quarter note One beat at 4/4
'8n' Eighth note Half a beat
'16n' Sixteenth note Quarter beat
'1m' One measure Full bar
'2:0:0' Bars:beats:16ths Start of bar 2
'+0.5' Relative seconds 0.5s from now
0.5 Absolute seconds At 0.5 seconds

Effects Chain

Basic Signal Flow

// 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);

Common Effects

// 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);

Effect Wet/Dry Mix

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

Playback Patterns

Music Player

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

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');

Looping Ambient Layer

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
  }
}

Crossfading

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';
  }
}

Synced Playback

Sync to Transport

// 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();

Multiple Synced Players

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();

Temporal Collapse Patterns

Countdown Audio Manager

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();
  }
}

Time-Synced Audio Events

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);
}

Performance Tips

// 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));

Reference

  • See audio-analysis for FFT and frequency extraction
  • See audio-reactive for visual-audio binding
  • See audio-router for audio domain routing