# Audio Playback with Tone.js ## Overview Tone.js provides browser-based MIDI playback with synthesis, allowing users to hear their notation. ## Why Tone.js? - High-level WebAudio API wrapper - Built-in Transport for timing and tempo control - Multiple synthesis methods (samplers, synths) - Scheduling for precise timing - Easy MIDI playback ## Basic Playback Implementation ```typescript import * as Tone from 'tone'; class PlaybackEngine { private sampler: Tone.Sampler; private isPlaying: boolean = false; constructor() { // Load piano samples this.sampler = new Tone.Sampler({ urls: { A0: "A0.mp3", C1: "C1.mp3", // ... more samples across piano range C8: "C8.mp3", }, baseUrl: "https://tonejs.github.io/audio/salamander/", }).toDestination(); } async play(notes: Note[]) { await Tone.start(); // Required for browser autoplay policy const now = Tone.now(); notes.forEach((note, index) => { const time = now + index * 0.5; // 0.5s between notes this.sampler.triggerAttackRelease(note.pitch, note.duration, time); }); this.isPlaying = true; } stop() { Tone.Transport.stop(); this.isPlaying = false; } } ``` ## Playback Controls Component ```typescript export const PlaybackControls: React.FC = () => { const [isPlaying, setIsPlaying] = useState(false); const [tempo, setTempo] = useState(120); // BPM const [currentBeat, setCurrentBeat] = useState(0); const playback = useRef(new PlaybackEngine()); const handlePlay = async () => { const { score } = useNotationStore.getState(); await playback.current.play(score.measures.flatMap(m => m.notes)); setIsPlaying(true); }; const handlePause = () => { playback.current.stop(); setIsPlaying(false); }; const handleTempoChange = (newTempo: number) => { setTempo(newTempo); Tone.Transport.bpm.value = newTempo; }; return (
Beat: {currentBeat} / {totalBeats}
); }; ``` ## Timing with Transport ```typescript // Schedule notes using Transport for better timing control Tone.Transport.bpm.value = 120; notes.forEach((note) => { Tone.Transport.schedule((time) => { sampler.triggerAttackRelease(note.pitch, note.duration, time); }, note.startTime); }); Tone.Transport.start(); ``` ## Visual Sync (Cursor Following Playhead) ```typescript function syncCursorWithPlayback() { Tone.Transport.scheduleRepeat((time) => { const currentPosition = Tone.Transport.seconds; const currentMeasure = positionToMeasure(currentPosition); // Update UI to highlight current measure setActiveMeasure(currentMeasure); }, "16n"); // Update every 16th note } ``` ## Next Steps See [Data Flow](data-flow.md) for state management integration.