smallBANG — the engine
Let’s create a Big Bang together.
smallBANG is the engine; .track is the language it plays. This page is about the
engine — the library you call from code. If you want to learn the language itself, start with
the tutorial and the reference.
smallBANG is a zero-dependency TypeScript library that parses .track text and turns it
into sound — offline to a WAV file or raw samples, and live in the browser through an
AudioWorklet. Its output is seed-deterministic: the same source and seed render to
byte-identical audio everywhere, on any machine, every time.
What it does
- Parse
.tracktext into a typed intermediate representation (IR), with precise, line-numbered errors. - Render offline to a 16-bit WAV (
Uint8Array) or to raw stereoFloat32Arraysamples — no audio hardware required, so it runs on a server or in a test. - Play live in the browser via an
AudioWorklet, with step callbacks for visualisation. - Resolve multi-file songs (
use "..."imports) and stack whole songs + audio clips into multi-layer projects. - Convert to and from MIDI with a pair of pure functions.
Install
npm install smallbang
The package ships TypeScript types and has no runtime dependencies.
Quick start — render a song to WAV
import { parseTrack, renderSongToWav } from "smallbang";
const { ir, errors } = parseTrack(`
bpm 120
steps 16
kick x...x...x...x...
hat ..x...x...x...x.
`);
if (errors.length) {
throw new Error(errors.map((e) => `line ${e.line}: ${e.message}`).join("\n"));
}
const wav: Uint8Array = renderSongToWav(ir!); // 16-bit stereo WAV bytes — write to a file or stream
parseTrack always returns { ir, errors }; check errors before rendering. Every error names
the 1-based line and what went wrong.
Entry points
The package exposes three import paths:
| Import | What it gives you |
|---|---|
smallbang | The headless API — parse, render-to-WAV/samples, projects, MIDI, the catalog. |
smallbang/web | WorkletEngine — live playback in the browser over the Web Audio API. |
smallbang/worklet | The AudioWorklet processor source, for bundling the live playback worklet. |
The headless API
Everything below is importable from smallbang:
| Function | Purpose |
|---|---|
parseTrack(text) | Parse one file → { ir, errors }. |
resolveSong(text, resolver) | Parse a song that uses use "..." imports; resolver(name) returns the imported source (or null). |
buildTimeline(ir) | Expand the arrangement into a flat list of timestamped triggers (Frame[]). |
renderSongToSamples(ir, opts?) | Offline render → [Float32Array, Float32Array] (left/right). |
renderSongToWav(ir, opts?) | Offline render → Uint8Array (16-bit stereo WAV). |
renderTrackToWav(text, opts?) | Convenience: parse + render a single string straight to WAV. |
parseProject(text) / renderProject(spec, resolve, opts?) | Parse + render a multi-layer project (see below). |
midiToTrack(bytes) / trackToMidi(text) | Convert between Standard MIDI Files and .track source. |
encodeWav / decodeWav | Low-level WAV codec. |
LANGUAGE_CATALOG | The full, machine-readable vocabulary — waves, presets, kits, samples, header keys, instrument params. |
PRESETS, KITS, SAMPLES | The built-in sound banks. |
renderSongToWav / renderSongToSamples take an optional RenderOptions (e.g.
{ sampleRate: 22050 }); the default sample rate is 44100.
Live in the browser
smallbang/web provides a WorkletEngine that plays a song through the Web Audio API and calls
you back on every step (handy for a playhead or step grid):
import { parseTrack } from "smallbang";
import { WorkletEngine } from "smallbang/web";
const { ir } = parseTrack(source);
const engine = new WorkletEngine();
await engine.start(ir!, { onStep: (info) => updatePlayhead(info) });
// ... later
engine.stop();
Determinism
All randomness in .track (? maybe, [a|b] choice, humanize) is driven by the song’s
seed. Given the same source and seed, renderSongToWav produces byte-identical output on
every platform — so you can snapshot-test audio and trust that what you hear in the browser is
what a server will render.
Multi-layer projects
A project stacks several .track songs and audio clips into one mix. A project file is its
own little grammar, so you parse it with parseProject (not parseTrack) and render it with
renderProject, passing a resolver that turns a layer reference into song text or decoded audio:
import { parseProject, renderProject } from "smallbang";
const { spec, errors } = parseProject(projectSource);
const [left, right] = await renderProject(spec!, resolve, { sampleRate: 44100 });
See §17 of the reference for the project file syntax
(layer / mix / master).
MIDI import / export
midiToTrack and trackToMidi are pure converters — bytes in, text out (and back). They are
round-trip faithful: note pitch, timing, velocity, pitch bend, controllers, and tempo
survive a midiToTrack → trackToMidi → re-parse cycle within tolerance.
import { midiToTrack, trackToMidi } from "smallbang";
const trackText = midiToTrack(midiBytes); // Uint8Array/ArrayBuffer SMF → .track source
const midiBytes = trackToMidi(trackText); // .track source → SMF bytes