Phaser AE

Synth sound and music

Free packv3.0.0-6AudioUpdated

Sound effects, chiptune music, recorded samples and streamed soundtracks on `game.sound`. You define each sound once by name, as a synth spec, a layered set of specs or a file, and play it by name everywhere, while the pack looks after the browser's unlock, the pause and the mix.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 14, 2026 · updated October 24, 2026

Synth sound and music: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

audio

function audio(opts: AudioOptions = {}): Pack<{ sound: SoundApi }>

The synth, as a pack. The kernel owns the unlock moment (input.firstGesture), the chance source (Law 4), and the shell pause (the context suspends with the clock).

Functions

jingleStep

function jingleStep(spec: SoundSpec): number

Seconds a jingle step lasts for this spec.

noteFreq

function noteFreq(name: string): number

Frequency in Hz of a named note — 'C5', 'F#4', 'Bb3' (letter, optional #/b, octave). Equal temperament, A4 = 440. Returns 0 for an unrecognised name.

parseJingle

function parseJingle(notation: string): { events: JingleEvent[]; total: number }

Parse jingle notation: whitespace-separated tokens, each a note (C5), a chord (C4+E4+G4), a hit (x — the voice's own sound, for drums and zaps in time) or a rest (-); a token takes ! to accent or ~ to soften, and a length multiplier (C5:3, -:2, x!:0.5). Unrecognised tokens advance time like a rest, so rhythm survives a typo.

soundLength

function soundLength(sound: Sound): number

Seconds a sound lasts — the longest of its layers. 0 for a URL.

soundTail

function soundTail(sound: Sound): number

Seconds until a sound is completely silent — its length plus the longest echo tail and release.

specLength

function specLength(spec: SoundSpec): number

Seconds one spec sounds for, including its delay, not counting an echo tail.

Types

AudioInput

interface AudioInput

What the pack puts in the registry under ('audio-input', 'audio'), so a pack that plays a media element (video) sends its sound through the mixer: the master and bus volumes and mute then apply to it.

  • name: string — The registry name: 'audio'.
  • route(el: HTMLMediaElement, bus: 'music' | 'sfx'): { gain: number } | null — Route el into bus, or move it there. Returns the route, whose gain (0..1) is the element's own volume, or null where there is no WebAudio, and the element then plays on its own.

AudioOptions

interface AudioOptions

Manifest options for the audio pack.

  • suspendWhenHidden?: boolean — Suspend audio while the page is hidden. Default true; false permits background music when simulation also continues.
  • masterVolume?: number — Master bus gain, 0..1. Default 0.25.
  • sounds?: Record<string, Sound> — Sounds defined at attach, by name; the same as calling defineAll. Default none.

AudioTap

interface AudioTap

What the pack puts in the registry under ('audio-tap', 'audio'), so a recorder can mux the mix into a clip.

  • name: string — The registry name: 'audio'.
  • track(): MediaStreamTrack | null — A live track of the master mix, or null when nothing has played yet and there is no audio graph.
  • stop(): void — Release the tap.

Bus

type Bus = 'master' | 'music' | 'sfx'

A mixer bus. music and sfx feed master.

JingleEvent

interface JingleEvent

One scheduled step of a parsed jingle: simultaneous frequencies (or a HIT — the voice's own freq/sweep/noise, the drum-machine case), start and length in steps, and a loudness multiplier.

  • freqs: number[] — Pitches to sound together; empty for a hit (x), which plays the spec's own tone.
  • start: number — When the event starts, in steps from the start of the jingle.
  • len: number — How long the event lasts, in steps.
  • accent: number — 1 normally; ! accents (1.35), ~ softens (0.45).

MusicOptions

interface MusicOptions

Options for sound.music(url, opts) — looped background music / BGM / audio track.

  • loop?: boolean — Loop the track. Default true.
  • volume?: number — Track volume 0→1 relative to master. Default 1.
  • fadeIn?: number — Fade-in duration in seconds. Default 0 (instant), or crossfade when that is set.
  • crossfade?: number — Cross-fade from the track playing now over this many seconds: it fades out while this one fades in. Default 0 (a cut).

NoiseColor

type NoiseColor = 'white' | 'pink' | 'brown'

Noise colour: white is hiss, pink is rain, brown is rumble.

PlayOptions

interface PlayOptions

How loud, and where: a sound played at a place in the world is panned and fades with its distance from the listener (listener(x, y, z, yaw, pitch)), in world units with y up.

  • volume?: number — Gain 0..1 on top of the sound's own volume. Default 1.
  • at?: { x: number; y?: number; z: number } — Where it plays, in world units; without it the sound is not positional.
  • near?: number — Distance at full volume, world units, above 0. Default 2.
  • far?: number — Distance past which it is not played at all, world units. Default 60.
  • rolloff?: number — How fast it fades past near, 0 to 10: the inverse-distance rolloff. Default 1.

Sound

type Sound = SoundSpec | SoundSpec[] | string

A sound definition: a synthesised SoundSpec, an array of specs played together (a layered/compound sound), or a URL string to an audio file (mp3/ogg/wav).

SoundApi

interface SoundApi

What game.sound presents. Every method is a no-op where WebAudio is unavailable; before the first gesture sounds are dropped and music waits for it.

  • define(name: string, sound: Sound): void — Name a sound once (the art/audio agent's contract); play it by name everywhere. Unknown names are a dev error. A URL sound starts loading at once, through the loader: defined before game.start(), it is preloaded.
  • defineAll(sounds: Record<string, Sound>): void — define every entry of a record; its URL sounds load as one job. Defined before game.start(), they are part of the preload, fetched and decoded under the loading screen and ready for their first play.
  • play(name: string, opts?: PlayOptions): Voice — Play a defined sound. Throws in dev on an unknown name; silent in production. Returns a function that stops it early. { volume } scales it; { at: { x, y, z } } plays it at a place in the world, panned and fading with its distance from the listener (near full volume, default 2; far not played at all, default 60; rolloff how fast it fades, default 1), and the returned voice's move(x, y, z) follows whatever makes it.
  • listener(x: number, y: number, z: number, yaw: number, pitch?: number): void — Where the player's ears are, for positional sounds: world units with y up, the engine's yaw (0 faces +z, turning right as it grows) and pitch (positive looks down). Call it as the camera moves, once a frame.
  • playSpec(spec: SoundSpec | SoundSpec[]): Stop — A one-off spec, for prototyping. Prefer define + play by name.
  • loop(name: string, opts?: PlayOptions): Voice — Play a defined synthesised sound back to back until stopped — a chiptune written in jingle notation becomes the background music. Returns the stop function. One loop at a time is the usual arrangement; the API does not enforce it.
  • length(name: string): number — Seconds a defined sound lasts (its longest layer), 0 for a URL or an unknown name.
  • music(track: string, opts?: MusicOptions): void — Stream a track on the music bus, replacing the current one. Defaults: loop, volume 1, no fade. track is a key the loader holds ({ kind: 'audio', key, url, stream: true }) or the URL of a file it holds under any key, and then plays the file already fetched; any other URL streams from the network, in the background. Throws in dev on a name that is neither a loaded key nor a URL. Asked for before the first gesture, it starts on that gesture. { crossfade: s } fades the current track out while this one fades in.
  • sample(key: string, opts?: PlayOptions & { rate?: number; loop?: boolean; pan?: number }): Voice — Play a SAMPLE the loader fetched (assets.load([{ kind: 'audio', key, url }])). It was decoded inside the load job, so it plays at once; a stream file is decoded on its first play instead, in the background, and plays when that lands. volume 0..1, rate, loop, pan -1..1 or at a place as play takes. Returns a stop function. Throws in dev on a key the loader has not got, saying so when it failed to load; a no-op before the first gesture or headless.
  • stopMusic(opts?: { fadeOut?: number }): void — Stop the music; fadeOut in seconds fades first (default 0).
  • readonly musicTime: number — Seconds into the current music track, as heard: what to time a logo's landing on the music's downbeat by. It wraps to 0 each time a looping track goes round, stands still while the game is paused or hidden and while the track buffers, counts from the start of a fade-in (a fade is only volume), and follows the new track through a cross-fade. The output latency the browser reports is taken off. 0 with no music, after stopMusic, and before the track starts (music asked for before the first gesture waits for it). Headless, with no WebAudio, the track is played on the game clock instead: seconds of game time since music, pausing with the game.
  • readonly musicDuration: number — The current track's length in seconds once the browser has read it, else 0: no music, still loading, a live stream, or headless.
  • volume(bus: Bus, v: number): void — Set a bus gain, 0..1, with no fade.
  • readonly muted: boolean — True after the mute command, until unmute.
  • readonly names: ReadonlyArray<string> — Every defined sound name, in definition order.

SoundEcho

interface SoundEcho

A feedback echo on the sound — caves, space, arcade halls.

  • time?: number — Seconds between repeats. Default 0.25.
  • feedback?: number — How much of each repeat feeds the next, 0→0.9. Default 0.35.
  • mix?: number — Level of the repeats against the dry sound, 0→1. Default 0.3.

SoundFilter

interface SoundFilter

A filter stage in a synthesised sound. Sweep a lowpass over 'noise' for crunchy explosions.

  • type?: BiquadFilterType — Filter shape ('lowpass', 'highpass', 'bandpass', …). Default 'lowpass'.
  • freq?: number — Cutoff frequency in Hz at the start of the sound. Default 1000.
  • freqEnd?: number — Optional cutoff sweep target in Hz — e.g. 2400 → 60 collapses a blast into a rumble.
  • sweep?: 'linear' | 'exponential' — Shape of the cutoff sweep. Default 'exponential'.
  • decay?: number — Seconds the sweep takes. Default: the whole sound. A short one is a filter envelope — the pluck of a bass.
  • q?: number — Resonance (Q). Higher = a whistly peak at the cutoff. Default 1.

SoundSpec

interface SoundSpec

Parameters for a synthesised sound effect (SFX / beep / tone / noise), or a tune in jingle notation.

  • type?: Waveform | OscillatorType — Oscillator waveform, or 'noise'. Default 'square'.
  • noise?: NoiseColor — Colour of 'noise'. Default 'white'.
  • freq?: number — Start frequency in Hz. Ignored for 'noise'. Default 440.
  • freqEnd?: number — Optional frequency sweep target in Hz — creates a zap/pew slide.
  • sweep?: 'linear' | 'exponential' — Shape of the freq → freqEnd slide. 'exponential' sounds pitch-linear to the ear — better lasers, jumps, falls. Default 'linear'.
  • notes?: number[] | string — Pitch sequence (overrides freq/freqEnd). - number[] — Hz steps spread evenly across duration, glued under one envelope (coins, power-up runs). - string — jingle notation: each note gets its own envelope (real articulation — 'ta-daa!' fanfares). Tokens: notes C5/F#4/Bb3, chords C4+E4+G4, hits x (this voice's own sound — drums), rests -; append ! to accent, ~ to soften, :N to stretch (C5:3 = 3 steps). One step lasts step seconds (or a 16th at tempo); total length comes from the notation (duration is ignored).
  • step?: number — Seconds per notation step in jingle mode. Default 0.12, or a 16th note when tempo is set.
  • tempo?: number — Tempo in BPM for jingle mode — a step is a 16th note (120 BPM = 0.125 s). step wins if both are set.
  • gate?: number — How much of each step a note sounds, 0→1: staccato at 0.5, legato at 1. Default 0.92.
  • slide?: boolean — Glide between consecutive notes instead of stepping — 303 lines, sirens, slurs. Default false.
  • duration?: number — Duration in seconds. Default 0.15.
  • volume?: number — Amplitude 0→1 relative to the sfx bus. Default 1.
  • attack?: number — Attack ramp in seconds. Default 0.005.
  • decay?: number — Seconds to fall from the attack peak to the sustain level. Default 0 (no decay stage).
  • sustain?: number — Level held after the decay, 0→1 of volume. Default 1. With decay this is a real ADSR.
  • release?: number — Release ramp in seconds. Default 0.06.
  • curve?: 'linear' | 'exponential' — Shape of the release ramp. 'exponential' = percussive, natural die-away (explosions, thumps, plucks). Default 'linear'.
  • jitter?: number — Random per-play pitch variation 0→1 (0.1 = up to ±10%) — keeps rapid-fire SFX from sounding robotic. Default 0.
  • vibrato?: SoundVibrato — Pitch wobble (LFO): { freq, depth } — sirens, drones, shimmer.
  • tremolo?: SoundTremolo — Loudness wobble (LFO): { freq, depth } — engines, alarms, helicopters.
  • duty?: number — Pulse width 0→0.5 for 'square' (0.5 = even square). 0.25/0.125 give classic thin chip-tune timbres.
  • filter?: SoundFilter — Filter stage: { type, freq, freqEnd, sweep, decay, q }. A swept lowpass over 'noise' is the crunchy-explosion move.
  • distortion?: number — Distortion drive 0→1 — adds grit/crunch (explosions, heavy impacts, dirty lasers). Default 0.
  • crush?: number — Bit-crush 0→1 — fewer bits, more fizz; the lo-fi 8-bit stamp. Default 0.
  • echo?: SoundEcho — Echo: { time, feedback, mix } — space, caves, the arcade hall.
  • pan?: number — Stereo position -1 (left) → 1 (right). Default 0 (centre).
  • delay?: number — Start offset in seconds — sequence the layers of a compound sound (play([...])). Default 0.

SoundTremolo

interface SoundTremolo

Loudness wobble (LFO) — engines, helicopters, a pulsing alarm.

  • freq?: number — Wobble rate in Hz. Default 10.
  • depth?: number — How far the level dips, 0→1. Default 0.5.

SoundVibrato

interface SoundVibrato

Pitch wobble (LFO) for a synthesised sound — sirens, UFO drones, magic shimmer. Ignored for 'noise'.

  • freq?: number — Wobble rate in Hz. Default 8.
  • depth?: number — Wobble depth as a fraction of the base pitch (0.1 = ±10%). Default 0.06.

Stop

type Stop = () => void

Stops a playing sound (or a loop) early. Calling it after the sound has ended is harmless.

Voice

type Voice = Stop & { /** Move a positional sound to world `x, y, z`. */ move(x: number, y: number, z: number): void; }

A sound that is playing: call it to stop it early, and move it to follow what makes it (a no-op unless it was played at a place).

Waveform

type Waveform = 'sine' | 'square' | 'sawtooth' | 'triangle' | 'noise'

Oscillator waveforms, plus 'noise'.