Claude-skill-registry audio-playback
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
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/audio-playback" ~/.claude/skills/majiayu000-claude-skill-registry-audio-playback && rm -rf "$T"
manifest:
skills/data/audio-playback/SKILL.mdsource content
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 |
|---|---|---|
| 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 |
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
for FFT and frequency extractionaudio-analysis - See
for visual-audio bindingaudio-reactive - See
for audio domain routingaudio-router