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

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:

ImportWhat it gives you
smallbangThe headless API — parse, render-to-WAV/samples, projects, MIDI, the catalog.
smallbang/webWorkletEngine — live playback in the browser over the Web Audio API.
smallbang/workletThe AudioWorklet processor source, for bundling the live playback worklet.

The headless API

Everything below is importable from smallbang:

FunctionPurpose
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 / decodeWavLow-level WAV codec.
LANGUAGE_CATALOGThe full, machine-readable vocabulary — waves, presets, kits, samples, header keys, instrument params.
PRESETS, KITS, SAMPLESThe 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 midiToTracktrackToMidi → 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

Where to go next