Live
Send code to a running server 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 server 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 functions and instruments in the language, down to the sample. 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 fx declarations wire up effects, and declaration order doesn't matter.
tempo 110bpm
bus space
fx 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)}
fx lead_fx { out = lead_fx_audio.gain(0.8)}bus lead_fx_audio: monoinstr lead_voice(freq: hz) { voice = pluck(bright: 0.6, freq: freq) lead_fx_audio += voice}clip lead { instrument: lead_voice }
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, spread_evenly and transpose for variation, and scenes you launch quantized to the bar.
config { channels: stereo }tempo 110bpm
bus space: stereofx space_return { out = space.reverb(room: 0.8, damp: 0.4).gain(0.8)}
instr pluck(freq: hz, bright: 0..1 = 0.5, az: deg = 0deg) { level = perc(decay: 400ms) voice = osc(saw, freq).lpf(cutoff: freq * (2 + bright * 8), res: 0.25) * level out = voice.pan(angle: az) space += out.gain(-18db)}
clip bass { instrument: pluck(bright: 0.2) notes: [c2 ~ c2 eb2 ~ g1 bb1 ~] gain: [0db -6db -3db] bright: [0.2 0.5 0.8]}
clip lead { instrument: pluck(bright: 0.8) notes: [c4 e4 g4 b4].every(2, reverse)}
clip bass_chorus = bass.transpose(+5st)
answer = [c4 e4 g4 b4].every(2, reverse).transpose(+5st).drop(25%)spread = [c5].spread_evenly(hits: 4, steps: 8)
clip lead_chorus { instrument: pluck(bright: 0.8) notes: answer}
clip lead_outro { instrument: pluck(bright: 0.8) notes: spread}
scene verse { bass, lead }scene chorus { bass_chorus, lead_chorus }scene outro { lead_outro }
arrangement song { at +0bars verse.start at +4bars verse.stop at +4bars chorus.start at +8bars bass_chorus.mute at +10bars bass_chorus.unmute at +12bars chorus.stop at +12bars outro.start at +14bars hush}
song.startarrangements and samples
Drum kits load from a folder, sections play in order, and niminal-live render can bounce the whole song or just bars 5 to 9.
config { channels: stereo }tempo 110bpm
drums = kit("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(angle: 0deg) space += out.gain(-12db)}
fx hall { out = space.reverb(room: 0.8, damp: 0.4).gain(0.8) }bassline = [c2 ~ c2 eb2 ~ g1 bb1 ~]groove = [kick ~ ~ kick snare ~ kick ~ kick ~ ~ kick snare ~ ~ snare]tune = [c4 e4 g4 b4].every(2, reverse)
clip beat { instrument: drums, notes: groove }clip hats { instrument: drums(gain: -8db), notes: [hat hat hat hat hat hat hat hat] }clip bass { instrument: pluck(bright: 0.2), notes: bassline }clip bass_chorus = bass.transpose(+5st)clip lead { instrument: pluck(bright: 0.7), notes: tune }
scene intro { bass }scene verse { bass, beat, hats }scene chorus { bass_chorus, beat, hats, lead }scene outro { bass }
arrangement song { sections: [intro.over(2bars) verse.over(4bars) chorus.over(4bars) verse.over(4bars) outro.over(2bars)]}the loop
# start the audio server
$ niminal server
# in another terminal: play a piece, and listen for changes
$ niminal-live live examples/groove.nml --midi
# change the music, landing four bars from now
$ niminal-live send 'chorus.start' --quantize "next 4bars"
# later: replay the whole session, sample for sample
$ niminal 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-live 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.