Skip to content

How to add music to a web game ​

There are four common ways to add music to a browser game: play audio files (usually through Howler), render a song with a tiny tracker like ZzFXM, synthesize it with Tone.js, or synthesize it with plonk, which we make. Files sound like anything you can record and cost the most bytes; the code-based options are small and can follow the game, but only make the sounds a synthesizer can.

Short answer ​

Use audio files when the music needs real instruments, vocals or a composer's finished mix. Use ZzFXM for size-limited jams like js13k. Use Tone.js when music is the product: a sequencer, a generative piece, anything that needs serious synthesis. Use plonk when you want small game music in code that reacts to the game: chiptune and synth styles, with tempo, layers and in-key sound effects built in.

At a glance ​

Audio files + HowlerZzFXMTone.jsplonk
What you shipA library plus the music filesA song array and a tiny rendererA library, songs in codeA library, songs as text
Library, brotli*8.8 kB (howler 2.2.4)Not on npm, so not measured here51.0 kB (tone 15.1.22)3.8 kB (plonkjs 0.2.0)
A 1-minute loopAbout 720 kB at 96 kbpsA few hundred bytes of song dataYour codeA few hundred bytes of text
SoundAnything you can recordZzFX-style synth instrumentsFull synthesis, samplers and effectsSynth voices, noise drums, echo, reverb
Reacting to the gameCrossfade separate files (stems)Re-render or swap songsAnything you programtempo(), layer(), sfx() in the song's key
TimingOne buffer, no per-note timingOne rendered bufferAhead-of-time schedulingAhead-of-time scheduling (measured)
AuthoringA DAW, a composer, a stock libraryIts tracker tool, or by handCodeText patterns, the playground
Licence of the musicWhatever you bought or commissionedYoursYoursYours

*A minimal app that plays one loop, as a Vite production build, brotli. From bench/music-size. The loop size is arithmetic: 96 kbps is 12 kB a second.

Audio files ​

The default for most games, and the right one whenever the music is a recording. Export a loop from your DAW (or buy one), and play it with Howler or plain Web Audio:

ts
import { Howl } from "howler";

const theme = new Howl({ src: ["theme.ogg"], loop: true, volume: 0.6 });
theme.play();

The cost is size. Compressed music runs at about 12 kB a second at 96 kbps, so a one-minute loop is about 720 kB, often more than the rest of a small web game put together. On portals and mobile, that's load time before the first note. Streaming helps the first play but not the total download.

Music that reacts to the game means stems: separate files for drums, bass and melody that play in sync, faded in and out. That works well, and it multiplies the download by the number of stems. Changing the tempo of a recording changes its pitch unless you time-stretch, which costs CPU and can sound smeared.

ZzFXM: a tiny tracker ​

ZzFXM by Keith Clark is a music generator for tiny JavaScript productions, built on Frank Force's ZzFX sound generator. A song is an array of instruments and patterns, loosely like a MOD file, and ZzFXM renders it into audio data that then plays as one buffer. It's made for size limits like js13k's 13 kB, and it has a tracker tool for writing songs.

Rendering happens before the music plays, so a long song costs some time at startup, and changing the music means rendering again. For a jam entry where every byte counts, it's hard to beat.

Tone.js: a full music framework ​

Tone.js is a framework for interactive music in the browser: synthesizers, samplers, effects, and a Transport that schedules notes ahead on the audio clock, so timing holds when frames drop.

ts
import * as Tone from "tone";

const synth = new Tone.Synth().toDestination();
new Tone.Sequence((time, note) => synth.triggerAttackRelease(note, "16n", time), ["C4", "E4", "G4"], "16n").start(0);
Tone.getTransport().start();

It can do far more than a game soundtrack needs: FM and granular synthesis, convolution, samplers that play recorded instruments. That range is why it's 51 kB for a minimal app. If you're building a music toy, a sequencer or a generative piece, it's the mature choice.

plonk: music made in code, for games ​

plonk is our music engine for web games. A song is data: instruments, and patterns of notes written as text, arranged in sections. It synthesizes them live with Web Audio, scheduling ahead on the audio clock like Tone.js, in 3.8 kB.

ts
import { Player } from "plonkjs";

const player = new Player();
player.play({
  bpm: 112,
  instruments: {
    lead: { wave: "square", decay: 0.14, sustain: 0.35, echo: 0.3 },
    kick: { wave: "sine", drop: 24, decay: 0.12, sustain: 0 },
  },
  sections: { a: { lead: "C5 . E5 G5 . E5 C5 .", kick: "x . . . x . . ." } },
  order: ["a"],
});
player.tempo(1.12); // speed up without a jump
player.layer("kick", 0); // fade the drums out

It's built around what game music does: tempo() without a jump when time runs out, layer() to bring tracks in and out with the game's mood, sfx() for sound effects in the song's key and on its beat, and (from plonkjs 0.3) player.step to animate in time. Songs are text, so a coding agent can write and edit them, and notes() checks them without speakers.

The limits are a synthesizer's: no recorded instruments or vocals, and a chiptune-and-synth palette. We think it's the best fit for small games with a code-drawn look; for an orchestral score, use files.

What every approach has to handle ​

Autoplay. Browsers don't play audio until the player interacts with the page (MDN). Start the music, or resume the AudioContext, from the first click, tap or key press, like a "press any key" title screen.

Hidden tabs. When the player switches tabs, pause the game and the music together. Listen for visibilitychange and stop or suspend on document.hidden. Browsers also slow down timers in hidden tabs, which starves any music scheduled from code.

A mute button and a music volume. Players expect both, separate from sound effects. Route music through its own gain node (or Howler's per-sound volume) and remember the setting.

Loops without a gap. MP3 encoders pad the start and end of a file with a little silence, so an MP3 loop can gap or click at the seam. Use Ogg Vorbis or Opus for loops, or let a synthesized song loop itself.

What this doesn't cover ​

Spatial audio, dialogue and voice, and sound-effect design are their own topics. The library sizes are for a minimal app on one day's versions; real games add their own code, and ZzFXM isn't measured because it isn't published as a package.

Make a loop in the plonk playground, copy it as code, and paste it into your game.