Skip to main content

Songs

Songs are JSON-serializable scores: instruments (Tone.js synth presets plus an effects chain), tracks, and clips placed in beats at a given BPM. Creator’s Music mode authors .song.json files that match the SongDefinition schema; at runtime you play them with createSongPlayer.

Tone.js is loaded on demand the first time you create a song player or call renderSong. Games that never play music never download it.

User gesture and ensureAudio​

Browsers block audio until the user interacts with the page. Before the first play(), call ensureAudio() from a click or key handler. It loads Tone.js (once) and starts the AudioContext. Later calls are cheap; use isAudioReady() to see whether the context is running.

import { createSongPlayer, ensureAudio, type SongDefinition } from '@zylem/game-lib/audio';

button.addEventListener('click', async () => {
await ensureAudio();
const player = createSongPlayer(mySong);
await player.ready;
await player.play();
});

loadTone() fetches Tone without touching the context—useful for prefetching after a gesture elsewhere.

Song shape​

A SongDefinition includes:

FieldRole
bpm, timeSignature, lengthBarsTempo and grid
instrumentsSynth presets, envelopes, effects
tracksInstrument or audio lanes (mute, solo, pan, volume)
clipsNote clips (kind: 'notes') or sample clips (kind: 'audio' with a url)
loop, masterVolumeOptional loop region and master fader (dB)

Validate documents with SongDefinitionSchema from @zylem/game-lib/audio or the published song schema.

Song player​

createSongPlayer(definition, options?) builds a Tone.js transport graph. Only one active player exists at a time; creating a new player disposes the previous one.

Method / propertyPurpose
readyResolves when Tone and audio clip URLs are loaded
play(fromBeat?), pause(), stop(), seek(beat)Transport control
position(), seconds()Playhead
setBpm, setLoop, track mute/solo/volume/panLive mix
preview(trackId, pitch, …)Audition a note on a track
update(definition)Hot-swap the score while keeping playhead and state
on('bar' | 'beat' | 'complete' | 'state', …)Timing hooks

Options:

  • resolveUrl(url) — map relative asset paths before loading audio clips
  • destination — route master output to a Tone input instead of speakers

getActiveSongPlayer() returns the current instance, if any.

Scheduling helpers​

song-math exports beat/second conversion, scheduleSong, clipsAt, MIDI helpers, and related types—useful for editors, sequencers, or custom render paths without starting the transport.

Pitfalls​

  • First play() without a gesture — context may stay suspended; retry ensureAudio() on the next interaction.
  • Shared transport — global Tone transport; one song at a time per page.
  • Audio clip URLs — failed loads log a warning; playback continues for other clips.

API reference​

See Rendering and export to bounce a song offline.