Live
Send code to a running daemon from VS Code, the REPL or a socket. Changes land on the beat you choose, and every input is logged by sample.
live-coding audio language
niminal is a language and a DSP engine built together. Describe instruments, patterns and scenes, hear every edit land on the beat, then render the very same file to audio.
// The basic synth from the spec, playing a short phrase.tempo 100bpm
instr saw_lead(freq: hz, amp: db = -6db) { level = env[0 5ms 1 200ms 0.6 | 300ms 0] osc(saw, freq) .lpf(cutoff: 2khz, res: 0.2) .gain(amp) * level}
saw_lead(freq: a3) for 1beatat 1beat saw_lead(freq: c4) for 1beatat 2beats saw_lead(freq: e4, amp: -9db) for 1beatat 3beats saw_lead(freq: a4) for 2beatsSend code to a running daemon from VS Code, the REPL or a socket. Changes land on the beat you choose, and every input is logged by sample.
The same file renders to audio with one command. Sessions replay from their log to identical samples.
Write opcodes and instruments in the language. They get types, units, hover docs and diagnostics like the built-ins.
Rational time, mini-notation, clips, scenes and arrangements. One pattern cycle is one bar.
what it sounds like
instruments and buses
Instruments are plain expressions with units that are checked at compile time: hz, db, ms, deg. Buses and tracks wire up effects, and declaration order doesn't matter.
tempo 110bpm
bus space
track room { echoes = space.delay(time: 3/16beat, feedback: 0.4).lpf(cutoff: 3khz) out = (echoes + space.reverb(room: 0.8, damp: 0.4)).gain(0.8)}
track lead { instrument = pluck(bright: 0.6) out = it.gain(0.8)}
instr pluck(freq: hz, amp: db = -6db, bright: 0..1 = 0.5) { level = env[0 2ms 1 600ms.exp 0] out = osc(saw, freq) .lpf(cutoff: freq * (2 + bright * 6), res: 0.3) .gain(amp) * level space += out.gain(-9db)}
lead(freq: c4) for 1/2beatat 1/2beat lead(freq: eb4) for 1/2beatat 1beat lead(freq: g4) for 1/2beatat 3/2beat lead(freq: c5) for 2beatsat 0beat pluck(freq: c2, amp: -3db) for 4beatspatterns, clips and scenes
Mini-notation for rhythm, functions like every, euclid and transpose for variation, and scenes you launch quantized to the bar.
config { channels: stereo }tempo 110bpm
bus space: stereo
instr pluck(freq: hz, bright: 0..1 = 0.5, az: deg = 0deg) { level = env[0 2ms 1 400ms.exp 0] voice = osc(saw, freq).lpf(cutoff: freq * (2 + bright * 8), res: 0.25) * level out = voice.pan(azimuth: az) space += out.gain(-12db)}
track hall { out = space.reverb(room: 0.8, damp: 0.4).gain(0.8) }track bass { instrument = pluck(bright: 0.2) }track lead { instrument = pluck(bright: 0.8) }
clip riff { notes: [c2 ~ c2 eb2 ~ g1 bb1 ~] gain: [0db -6db -3db] bright: [0.2 0.5 0.8]}
theme = [c4 e4 g4 b4].every(2, it.reverse)answer = theme.transpose(+5st).thin(25%)spread = [c5].euclid(hits: 4, steps: 8)
scene verse { bass: riff, lead: theme }scene chorus { bass: riff.transpose(+5st), lead: answer }scene outro { bass: ~, lead: spread }
launch verseat bar 5 launch chorusat bar 9 mute bassat bar 11 unmute bassat bar 13 launch outroat bar 15 hush
launch verselaunch chorus @ next 4 barsmute bassunmute bass @ next barunsolo leadlaunch outro @ next 2 barshush
husharrangements and samples
Drum kits load from a folder, sections play in order, and niminal render can bounce the whole song or just bars 5 to 9.
config { channels: stereo }tempo 110bpm
kit drums = "drums"bus space: stereo
instr pluck(freq: hz, bright: 0..1 = 0.5) { voice = osc(saw, freq).lpf(cutoff: freq * (2 + bright * 6), res: 0.25) * env[0 2ms 1 400ms.exp 0] out = voice.pan(azimuth: 0deg) space += out.gain(-12db)}
track hall { out = space.reverb(room: 0.8, damp: 0.4).gain(0.8) }track beat { instrument = drums }track hats { instrument = drums(gain: -8db) }track bass { instrument = pluck(bright: 0.2) }track lead { instrument = pluck(bright: 0.7) }
bassline = [c2 ~ c2 eb2 ~ g1 bb1 ~]groove = [kick ~ ~ kick snare ~ kick ~ kick ~ ~ kick snare ~ ~ snare]tune = [c4 e4 g4 b4].every(2, it.reverse)
scene intro { bass: bassline, beat: ~, hats: ~ }scene verse { bass: bassline, beat: groove, hats: [hat hat hat hat hat hat hat hat], lead: ~ }scene chorus { bass: bassline.transpose(+5st), beat: groove, hats: [hat hat hat hat hat hat hat hat], lead: tune }scene outro { bass: bassline, beat: ~, hats: ~, lead: ~ }
arrangement song { sections: [intro.over(2 bars) verse.over(4 bars) chorus.over(4 bars) verse.over(4 bars) outro.over(2 bars)]}the loop
# play sound and listen for code
$ niminal daemon examples/groove.nml --midi
# change the music, landing four bars from now
$ niminal send 'launch chorus' --quantize "next 4 bars"
# later: replay the whole session, sample for sample
$ niminal render --replay ~/.niminal/sessions/1760112000.log --out set.wavHighlighting, evaluate block or file, diagnostics, and a scenes panel you can launch with Cmd+1..9.
Completion, hover, go to definition and an outline from niminal lsp, for any editor.
Bind a control to a knob with ctl x = midi.cc(21) or an OSC address. Inputs are logged and replay too.
Compilation happens at eval time; landing a change only moves things. The engine builds for WebAssembly.