Skip to content

undertoneProcedural sound for games and music

A TypeScript synth engine built directly on the Web Audio API — one-shot sound effects, looped grooves, and whole multi-section songs, all from the same pattern. Zero required runtime dependencies. MIT licensed.

Sixty seconds of undertone

bash
bun add @liminal-hq/undertone
# or: npm install @liminal-hq/undertone

A one-shot game sound effect and a looped, polyphonic track, from the same API:

ts
import { note, rev, stack } from '@liminal-hq/undertone';

// One-shot SFX — call it from a click handler (autoplay policy):
note('c5').sound('triangle').decay(0.1).gain(0.5).play();

// Looped music — chords, a melody with a stereo shimmer, a euclidean bassline:
const handle = stack(
  note('<[c3,e3,g3] [a2,c3,e3] [f2,a2,c3] [g2,b2,d3]>').sound('sine').sustain(0.8),
  note('c5 [e5 g5] <b5 a5> ~').sound('triangle').every(2, rev).jux(rev).gain(0.4),
  note('c2(3,8)').sound('square').lpf(400)
).loop({ bpm: 110 });

// ...later:
handle.stop();

Every constructor returns an immutable Pattern; every chained call returns a new one. Nothing touches audio until .play() or .loop() — patterns are cheap to build, branch, and reuse.

Where to go from here

Undertone's vocabulary is Strudel-flavoured, but it's a clean-room, MIT-licensed implementation built from the published TidalCycles papers, standard music theory, and Strudel's public documentation — never from its AGPL source or data. Ship it in anything.