Skip to content

Standard library

Compiled by niminal. These modules are ordinary source, compiled by the server — and offered as presets in the tracker.

The modules under std/ come with niminal. They are ordinary niminal source, read from the program itself, so use std/filters works anywhere and nothing needs installing. They are also the best examples of what functions can do, so read them: they are short.

instr pad(freq: hz) {
out = osc(saw, freq).ladder(cutoff: 900hz, res: 0.4)
out += out.fdn_reverb(time: 2.5sec) * 0.3
}

A function from a module is called like any other, as a method or with the signal first. Parameters marked init are read when a note starts. The ratios and other tables the functions use are public too (drum_modes, fdn_lengths, and so on), so you can use them or write your own.

The filters, resonators and reverbs everything else is made of run natively and need no use; the opcode list has each one.

Opcode What it is
ladder(x, cutoff, res: 0.3, drive: 1) A four-pole low-pass with a tanh in each stage, like a transistor ladder. res near 1 rings and then sings at the cutoff. Loud input, or a higher drive, saturates the stages, as it should.
biquad_lp, biquad_hp, biquad_bp, biquad_notch, biquad_allpass (x, cutoff, q) Two-pole sections. q is the height of the peak at the cutoff; 0.707 is flat.
biquad(x, b0, b1, b2, a1, a2) One biquad section from its coefficients, for building your own.
modal(x, freq, ratios, decay: 300ms) A resonator for each of ratios times freq, in parallel: bars, bells and drums.
delay(x, time, feedback, damp) A delay line; damp darkens each echo, like a tape delay.
fdn_reverb(x, time, damp, size) A feedback delay network of eight lines.
Function What it is
onepole_lp(x, cutoff), onepole_hp(x, cutoff) One pole: 6db an octave.
Function What it is
string(x, freq: init hz, decay: 0.996, damp: 0.5, max: 50ms) A plucked string (Karplus-Strong): comb with the delay tuned to the pitch. Feed it a short, bipolar burst such as noise() * env[1 4ms 0]; a click with only positive values gives a string that only drifts. decay near 1 rings longer, damp is how fast the highs die away, and max is the longest period (the lowest freq is 1 / max).
membrane(x, freq: init hz, decay: 150ms) The first eight modes of a circular membrane.
free_bar(x, freq: init hz, decay: 500ms) A free bar, as in a glockenspiel.
bell(x, freq: init hz, decay: 750ms) A bell, with a hum an octave below the strike note.

These are modal with a set of ratios; modal is an opcode.

use std/physical
instr tom(freq: hz = 110hz) { (impulse(1hz) * 0.5).membrane(freq) }

For echoes at times of your own, add delays: x + x.delay(120ms) * 0.6 + x.delay(250ms) * 0.3. Each is its own delay line, so each tap can have its own time, be modulated (x.delay(250ms + lfo(0.3hz) * 5ms, max: 300ms)), be filtered or be panned.

Function What it is
echoes(x, count: 4, spacing: 120ms, fade: 0.6) x followed by count echoes, spacing apart, each fade times the level of the one before.
tape(x, time, feedback: 0.5, damp: 4khz, max: 2sec) x plus a feedback delay with a low-pass in the loop, so each repeat is darker.

These effects analyze overlapping spectral frames. Each is size samples late (1024 unless listed otherwise); the compiler delays parallel dry paths to match. The frame helpers remain available for writing custom processing.

Function What it is
band_keep(x, lo: 0, hi: 0.25, size: 1024) Keeps the part of the spectrum from lo to hi, as fractions of the way to half the sample rate.
robot(x, size: 1024) Keeps every magnitude and zeroes every phase: a buzz at the frame rate.
blur(x, amount: 0.9, size: 1024) Mixes each spectrum with the ones before it: a long wash.
denoise(x, threshold: 0.01, size: 1024) Silences the bins quieter than threshold: a spectral noise gate.
spectral_envelope(re, im, detail: 32) Smooths real FFT magnitudes in the log domain using a triangular cepstral lifter; returns an envelope array for use inside a frame callback. Adds no frame latency itself.
envelope_transfer(carrier, donor, amount: 1, detail: 32, size: 1024) Applies the donor’s broad spectral shape to the carrier, retaining the carrier’s phase and fine structure.
phase_frequency(phase, size: 2048, hop: 512) Stateful phase unwrapping: real-FFT phases in radians to instantaneous frequencies in cycles/sample. Call once per frame with the matching FFT size and hop.
phase_accumulate(frequency, size: 2048, hop: 512) Stateful integration of cycles/sample into wrapped synthesis phases in radians. Call once per frame with the matching size and hop.
spectral_pitch(x, shift: 0, size: 2048, overlap: 4, formant: 0, preserve: 0, transients: 1, detail: 32) Peak-locked pitch shifting with independent spectral-envelope control and local attack resets. Shift and formant are semitones (−48 to +48).

spectral_pitch delegates to the native spectral_warp opcode. It moves complete peak regions, sums overlapping regions and preserves relative phases around each peak. transients controls local phase resets on spectral growth (0 off, 1 full); FFT windowing can still smear attacks. preserve: 1 retains the original broad spectral envelope as pitch changes, while formant shifts that envelope independently. Envelope correction is limited to 32× gain per bin, so leave headroom. Stereo channels are independent. Neutral settings reconstruct the source with the declared latency.

The built-in spectral_memory captures a fixed ring of spectral frames, then holds or scans them when recording stops. It shares pitch, formant and preservation controls with spectral_warp. See Spectral processing for controls, storage limits and examples.

Additional native spectral tools need no import: partial_morph pairs persistent tonal tracks and glides their pitches across different FFT regions; spectral_morph blends two sources’ magnitudes and frequencies independently; spectral_thin retains the strongest bins; spectral_arp focuses a moving spectral region; spectral_hold freezes magnitude and frequency separately; spectral_shift adds Hz above a cutoff; and spectral_partials tracks peaks into an interpolated sine bank. See Spectral processing for controls, latency, limitations and musical examples.

For envelope transfer, amount: 0 returns the carrier delayed by one frame, and 1 applies the full transfer. The donor’s level affects the result; a silent donor silences full transfer. Per-bin gain is capped at 32× (about 30 dB), and log magnitudes are floored at size * 0.000001 to keep quiet bins stable. Leave output headroom. This shapes existing carrier energy; it cannot supply partials absent from the carrier or separate pitch from formants perfectly.

detail is a compile-time integer: larger values retain narrower spectral features, smaller values give a smoother envelope, and values below 1 act as 1. Its audible bandwidth depends on the sample rate; start around 32 at 48 kHz. FFT size also controls frequency resolution and latency. This is frame-based processing, so sharp attacks can smear.

The envelope-transfer example introduces a warm carrier and a rhythmic noise breath, then plays their hybrid through an A-minor/F-major progression, without external samples.

Finished instruments with a few controls each, for when you want a sound and not a function. use std/instruments.{kick, bass}, then play them by name (instrument: kick, or kick(freq: c2) for 1beat). Each is complete on its own, so you can copy one into your file and change it. The tracker offers the same list under ▾ beside + in its instruments. std/instruments, std/drums and std/fx are shelves: you name what you take, and only that is added. niminal presets lists everything on them with a description (--kind fx narrows it), niminal presets kick_808 prints one to paste and change, and the editor completes the names inside use std/drums.{. Type the start of any preset name elsewhere in a file and the editor offers it twice: the name, which adds it to your use lines, and (copy source), which pastes its definition (with the functions it needs) for you to change. The tracker’s preset browser shows the same list. A comment line // folder: drums/kicks between definitions files the ones after it under that folder.

Instrument What it is
kick(freq: 55hz, punch, decay) A sine that drops in pitch, with a click on the front.
snare(freq: 180hz, snap, decay) A short triangle thump under band-passed noise.
hat(open) High-passed, crushed noise; open goes from a tick to a wash. The note’s pitch is ignored.
bass(freq, cutoff: 600hz, res) A saw with a sine an octave down, through a low-pass.
pluck(freq, tone, decay) A plucked string that is bright at the start and dulls as it dies away.
pad(freq, attack, release, detune) Three slightly detuned saws, softened by a low-pass, with a slow envelope.
lead(freq, cutoff: 2500hz, vibrato) A saw with vibrato, through a low-pass.
bell(freq, decay) A struck bell from two sines, one bending the phase of the other.

A drum machine voice and twenty-one presets of it. drum is a function with the controls of a drum voice: an oscillator (wave sine, tri, square or saw; freq, attack, decay) with a pitch modulation (mod_mode drop, wobble or random; mod_amount in semitones, mod_rate), a noise source with its own filter (noise_filter low, band or high; noise_freq, noise_q) and envelope (noise_shape soft, linear or burst; noise_attack, noise_decay), a mix between the two, drive, one EQ band (eq_freq, eq_gain) and amp, and a velocity: vel (how hard the note is played) with osc_vel, noise_vel and mod_vel saying how much it changes the oscillator, the noise and the pitch modulation. The choices shape the instrument and are fixed when it is compiled; the numbers stay live. The output is soft-clipped, so every setting is bounded.

use std/drums.{kick_808, snare_tri}, then play a preset by name, or call drum(...) inside an instrument of your own. Each preset takes the note as a tuning (c3 plays it as designed), takes the note’s gain as its velocity, and exposes the same numeric controls, so any lane can reshape any sound. The hats share a choke group, so a closed hat ends an open one.

Presets
kick_deep, kick_808, kick_punch, kick_click, kick_dirty Kicks from a long sub to a hard, driven one.
snare_tri, snare_tight, snare_fat Snares with a triangle body and band-passed noise.
clap, clap_room Noise chopped into bursts, dry and with a long tail.
hat_closed, hat_open, hat_tick Hi-hats from high-passed noise.
tom A tom; play different notes for a set.
rim, cowbell Square-wave percussion: a rim shot and a driven cowbell.
zap, blip, wobble_bass, sweep, crash Effects: a laser, random-pitch bubbles, a slow wobble, a rising noise band, a cymbal.

Keep a preset’s name different from the choice words above (a preset called wobble would quietly stop a file playing), and from the instruments in std/instruments. The tracker lists these under ▾ beside + in its instruments; adding one copies the drum function into the project with it, so the copy stands alone.

std/fx is a shelf, not a module to take whole: use std/fx alone is an error, and use std/fx.{echo, room_reverb} adds just those two effects to your piece (any module can be used this way; functions and constants always come with it). Effects that read in: lowpass, highpass, bandpass, band_cut, equalizer, echo, room_reverb, glitch, ensemble, swirl (a phaser), tremolo, compressor, overdrive, lofi and soft_clip (rounds off peaks with no delay). Each has typed, ranged parameters and, where it makes sense, a mix. The ones that move on their own keep to the beat: the filters’ sweep_beats, the beats of ensemble, swirl and tremolo are the length of one cycle in beats, so a tempo change speeds them up or slows them down without them losing their place. compressor has a knee and a link (at 1 the loudest channel decides the squeeze for all, so a stereo image stays put), overdrive runs at twice the sample rate so it doesn’t alias. There is no limiter among them: a lookahead limiter delays the sound, so it belongs once, at the end, and the master already has one. glitch chains six glitches on the same sound, each switched on by its own control: a stutter that repeats the last piece (and rolls faster the longer it is held, when stutter_shrink is below 1; stutter_gate cuts each repeat short, stutter_reverse plays it backwards, stutter_shape fades it out or swells it in, stutter_damp darkens each repeat more than the last, stutter_attack and stutter_bend shape each repeat’s envelope, and stutter_accent softens every second repeat), a rewind that plays it backwards, a slow-down like a stopping tape, a scatter that cuts the last bar into scatter_slices and plays them shuffled (a new shuffle each pass, the same scatter_seed giving the same shuffles every time, scatter_reverse, scatter_repeat, scatter_drop and scatter_detune the chance a slice runs backwards, is swapped for another, drops out, or is pitched up or down), a freeze that hangs the last freeze_length of sound in the air as a cloud of grains (freeze_spread smears it, freeze_pitch and freeze_detune bend it), and a bit-and-rate crush. scatter_length and freeze_length are lengths that snap to a musical division of your tempo, a bar by default for the scatter. The gate control starts open: a row of steps for it in a clip is a trance gate (x lets the sound through, . cuts it), rounded by gate_attack and gate_release. A gate is best drawn in a gesture clip, which has a step for every sixteenth whatever else is playing, so it chops a held sound too. With every control at zero the sound passes through untouched. The pieces are the opcodes repeat_last, rewind_last, slow_down, scatter and freeze (x.repeat_last(hold:, length:), see the opcode list), and the function apply, which needs no use and runs any function on a sound as much as a control says (x.apply(fn(s) { s.bitcrush(bits: 5) }, when: crush)), so a filter, a reverb throw or a gain dip can be a stage in the chain too. Each fades in and out over blend (3ms by default) so a hold starts and ends without a click. Like the instruments, each stands alone: read the module for the controls of each one. A clip draws when each control is on with lanes for an effect. The tracker lists them under + Add effect… and beside + in its effects library.

use std/fx

Each of these is a whole piece: the code is the file that made the sound.

Struck things. A drum, a bar and a bell from std/physical, and a sixteen-mode glass written by hand with one vector line for all the modes.

modal.nml
// Struck things (a short noise burst has little energy for a narrow resonator, hence the big gains). The drum, bar and bell come from the standard library; the glass is a bank of
// resonators written here in a dozen lines, using one vector line for all sixteen modes.
// Render with: niminal-live render examples/modal.nml --out modal.wav
use std/physical
config { channels: stereo }
tempo 92bpm
bus space: stereo
fx space_return {
out = space.reverb(room: 0.7, damp: 0.5)
}
/// Sixteen resonators in parallel, at the ratios of a stiff glass rod. `freq` is read once per
/// note, so everything that depends only on it is worked out when the note starts.
const rod = [1, 2.76, 5.4, 8.93, 13.34, 18.64, 24.8, 31.9, 39.9, 49.3, 60.2, 72.6, 86.5, 101.9, 118.9, 137.4]
fn glass(x, freq: init hz, decay: 0..1 = 0.9996) {
state s1 = zeros(16)
state s2 = zeros(16)
w = 2 * pi * freq / sample_rate * rod
y = x * (2 * (1 - decay) * sin(w)) + 2 * decay * cos(w) * s1 - decay * decay * s2
s2 = s1
s1 = y
y.sum
}
/// A short noise burst, which is what strikes all of them.
fn strike(length: sec) {
noise() * env[1 length 0]
}
instr tom(freq: init hz = 110hz) {
tail = 3sec
out = strike(2ms).membrane(freq, decay: 200ms).gain(20db)
space += out.gain(-14db)
}
instr marimba(freq: init hz) {
tail = 3sec
out = strike(3ms).free_bar(freq, decay: 300ms).gain(26db)
space += out.gain(-10db)
}
instr bell_hit(freq: init hz) {
tail = 5sec
out = strike(1ms).bell(freq, decay: 1sec).gain(34db)
space += out.gain(-8db)
}
instr tinkle(freq: init hz) {
tail = 4sec
out = strike(1ms).glass(freq: freq).gain(30db)
space += out.gain(-8db)
}
tom(freq: 90hz) for 1beat
at +1beat tom(freq: 120hz) for 1beat
at +2beats tom(freq: 150hz) for 1beat
at +3beats tom(freq: 90hz) for 1beat
at +4beats marimba(freq: c4) for 1beat
at +5beats marimba(freq: e4) for 1beat
at +6beats marimba(freq: g4) for 1beat
at +7beats marimba(freq: c5) for 1beat
at +8beats bell_hit(freq: g3) for 1beat
at +10beats tinkle(freq: d5) for 1beat
at +11beats tinkle(freq: a5) for 1beat

Delay lines. A plucked string into echoes and into taps placed by hand, and a comb written with taps and darkened by a 16-tap fir.

multitap.nml
// Delay lines. A plucked string goes into echoes three ways: `echoes` makes a regular train in one line,
// taps added by hand sit wherever you put them and can each move or be filtered, and `comb_cloud` below is
// a delay line written from scratch with `taps` and `fir`. Start with the first two; the last is for when
// you want control of every sample.
// Render with: niminal-live render examples/multitap.nml --out multitap.wav
use std/physical
use std/delays
config { channels: stereo }
tempo 100bpm
bus space: stereo
fx space_return {
out = space.reverb(room: 0.6, damp: 0.5)
}
/// The advanced route: a comb of eight taps at related times, each a little quieter, summed and then
/// darkened by a moving average over the last sixteen samples. The times are in samples (331 is about 7ms at
/// 48khz). `taps` reads the delay line at all eight times in one step; `fir` is the sixteen
/// multiplications and the sum, also in one.
const spacings = [331, 662, 993, 1324, 1655, 1986, 2317, 2648]
const falloff = [1, 0.8, 0.64, 0.51, 0.41, 0.33, 0.26, 0.21]
const box = (0..<16).map(1 / 16)
fn comb_cloud(x, mix: 0..1 = 0.4) {
state line = zeros(4096)
state soft = zeros(32)
state pos = 0
state at = 0
line[pos] = x
cloud = line.taps(spacings, pos).dot(falloff)
soft[at] = cloud
darker = soft.fir(box, at)
pos = (pos + 1).mod(4096)
at = (at + 1).mod(32)
x + darker * mix
}
/// A regular train of echoes: four, 180ms apart, each about half as loud as the one before.
instr pluck(freq: init hz, decay: 0..1 = 0.997) {
tail = 4sec
plucked = (noise() * env[1 3ms 0]).string(freq, decay: decay).gain(-6db)
out = plucked.echoes(count: 4, spacing: 180ms, fade: 0.55)
space += out.gain(-12db)
}
/// Taps placed by hand. Each `delay` is its own tap, so one drifts in time and one is darkened.
instr drift(freq: init hz, decay: 0..1 = 0.997) {
tail = 4sec
plucked = (noise() * env[1 3ms 0]).string(freq, decay: decay).gain(-6db)
wobble = osc(sine, 0.4hz) * 6ms
out = plucked + plucked.delay(250ms + wobble, max: 300ms) * 0.4 + plucked.delay(500ms).lpf(cutoff: 1.5khz) * 0.35
space += out.gain(-12db)
}
instr clicks(freq: hz) {
tail = 2sec
out = (osc(square, freq) * env[1 1ms 0]).comb_cloud(mix: 0.6).gain(-10db)
space += out.gain(-10db)
}
pluck(freq: e3) for 1beat
at +1beat pluck(freq: b3) for 1beat
at +2beats pluck(freq: g4, decay: 0.994) for 1beat
at +3beats pluck(freq: e4) for 1beat
at +4beats clicks(freq: 800hz) for 1/8beat
at +5beats clicks(freq: 1200hz) for 1/8beat
at +6beats clicks(freq: 500hz) for 1/8beat
at +7beats drift(freq: d4) for 1beat
at +8beats drift(freq: a3, decay: 0.994) for 1beat

Spectral effects. A short harmonic miniature with a custom FFT tilt and moving spectral blur, quiet band_keep/robot accents, and a captured detail that holds as the piece dissolves.

spectral.nml
// A short spectral miniature: clear harmony blooms into a wash, airy pulses enter, a detail freezes.
// Evaluate the whole file; all + times are relative to when you submit it.
// niminal-live render examples/spectral.nml --out /tmp/spectral.wav
use std/spectral
config { channels: stereo }
tempo 100bpm
bus space: stereo
fx space_return { out = space.reverb(room: 0.3, damp: 0.75) }
// A custom FFT tilt: full at DC, fading to zero at Nyquist. Latency is declared automatically.
const tilt = (0..<513).map(1 - it / 513)
fn darken(x) {
x.frames(1024, fn(w) {
s = fft(w)
ifft(s[0] * tilt, s[1] * tilt)
})
}
// Here amount can move while the chord sounds: each frame blends into the preceding magnitudes.
fn bloom(x, amount: 0..1) {
x.frames(1024, fn(w) {
state held = zeros(513)
s = fft(w)
magnitude = sqrt(s[0] * s[0] + s[1] * s[1])
phase = atan2(s[1], s[0])
held = held * amount + magnitude * (1 - amount)
ifft(held * cos(phase), held * sin(phase))
})
}
instr pad(freq: hz = a3, third: 1..2 = 1.189207, wash: 0..1 = 0.85) {
tone = osc(tri, [freq, freq * third, freq * 1.498307]).sum * 0.12
voice = tone.lpf(cutoff: env[4khz 4beats 700hz]).darken().bloom(amount: env[0 3beats wash])
out = voice * env[0 100ms 1 | 450ms 0]
space += out.gain(-24db)
}
// Robotization is a quiet rhythmic accent here: zeroing phases gives the short noise pulse a grain.
instr shimmer() {
texture = noise(seed: 53).band_keep(lo: 0.08, hi: 0.3).robot()
out = texture * perc(attack: 10ms, decay: 90ms) * 0.025
}
instr held() {
tone = osc(tri, [a3, c4, e4]).sum * 0.12
source = tone.lpf(cutoff: env[700hz 200ms 3khz 300ms 900hz]) * env[0 45ms 1 450ms 0.5 100ms 0]
captured = spectral_memory(source, record: env[1 1beat 1 1ms 0], position: 0.65, size: 2048, overlap: 8)
out = captured * env[0 40ms 1 | 700ms 0]
space += out.gain(-24db)
}
pad() for 4beats // A minor opens clearly and softens into a wash.
at +4beats pad(freq: f3, third: 1.259921, wash: 0.93) for 4beats // F major answers more diffusely.
at +5beats shimmer() for 1/4beat
at +6beats shimmer() for 1/4beat
at +7beats shimmer() for 1/4beat
at +8beats shimmer() for 1/4beat
at +9beats held() for 4beats // Capture once, then let the held detail dissolve.

Filters and a waveshaper. The ladder swept and made to sing, two band-passes as a vowel, and a Chebyshev table on a sine.

library.nml
// The filters in the standard library, and a waveshaper from a table. The ladder is swept by an
// envelope and made to sing; a band-pass picks out a vowel-like formant; a Chebyshev table adds
// harmonics to a sine.
// Render with: niminal-live render examples/library.nml --out library.wav
use std/filters
tempo 108bpm
/// Adds the 2nd and 3rd harmonics of whatever goes in, as the table says.
const warmth = cheby([1, 0.4, 0.2])
instr acid_ish(freq: hz, res: 0..1 = 0.6) {
level = env[1 | 400ms 0]
sweep = env[1 40ms 0.15 | 300ms 0]
out = (osc(saw, freq).ladder(150hz + sweep * 3khz, res: res) * level).gain(-9db)
}
instr vowel(freq: hz, ah: 0..1 = 0.5) {
level = env[0 30ms 1 | 300ms 0]
source = osc(saw, freq)
formants = source.biquad_bp(700hz + ah * 400hz, q: 6) + source.biquad_bp(1100hz + ah * 800hz, q: 8) * 0.6
out = (formants * level).gain(-6db)
}
instr bass(freq: hz) {
level = env[0 8ms 1 200ms 0.7 | 150ms 0]
out = (osc(sine, freq) * 0.8).waveshape(warmth).onepole_lp(900hz) * level * 0.45
}
acid_ish(freq: a2) for 1/2beat
at +1/2beat acid_ish(freq: a2) for 1/2beat
at +1beat acid_ish(freq: c3, res: 0.85) for 1/2beat
at +3/2beats acid_ish(freq: e3) for 1/2beat
at +2beats acid_ish(freq: a2, res: 0.9) for 1beat
at +4beats vowel(freq: a3, ah: 0.2) for 2beats
at +6beats vowel(freq: c4, ah: 0.8) for 2beats
at +8beats bass(freq: a1) for 1beat
at +9beats bass(freq: c2) for 1beat
at +10beats bass(freq: e2) for 2beats

Anything in these files can be copied and changed. A module you write goes next to your project or in lib/; see Modules. If it turns out well, tell us: the library grows by what people need.