Code module
The Code module lets you create custom modules using a simple expression language - a high-performance audio-processing script for real-time synthesis and DSP.
Overview
The Expression Engine allows you to define audio signal processing chains using a C-like scripting language. Scripts are compiled to a bytecode or executed via an AST interpreter optimized for SIMD.
You can use it for custom math processing or to build audio synthesizers, audio effects, modulators, pitch processors etc..
The engine supports:
- Vectorized or per sample processing: Operations are performed on buffers of audio samples (fast, almost like native Drambo), per sample: you can write totally custom DSP code, like filters. (about 2-5x slower).
- Stateful nodes: Oscillators, filters, and envelopes maintain state between blocks.
- Control flow: ternary operator or if / else structures for conditional processing.
Please note
- You don't need to use ; at the end of lines :)
- Scientific notation of numbers (e.g. 1e-9) also shortened notation like .1 is not supported yet.
- Don’t break a single expression across multiple lines.
- The code module processes each voice and channel separately, so there is no direct access to stereo channels in the signal (yet).
- Try generating the code using AI (Your favourite agent). Include the link to this page as part of the prompt, or attach a PDF version of the page. It works!
Language syntax
Variables and Data Types
Variables are implicitly declared upon assignment. All variables are floating-point numbers (scalars) or buffers of samples (vectors). In per-sample processing mode you may use a variable declared later as a feedback.
// Assignment
frequency = 440
amp = 0.5
// Output calculation
out = oscSin(frequency) * amp
Functions
In Drambo’s Code module, you work with a set of built-in functions (a “standard library”) to build DSP chains quickly - everything from basic math (e.g. sin, abs, pow, clamp ...) to audio primitives like oscillators (oscSin, oscSaw..), filters (lpf,bpf ...), envelopes (envAD,envD...), and delays (delay,delayTuned...). Some functions, like oscillators, filters or delays are stateful, they keep internal state between processing blocks.
Inputs, parameters and outputs
Special Input/Output functions define external connectivity to it's Drambo modules.
input(type, name [optional]) Declares read-only input buffers provided by the host.
output(variable name, type [optional], name [optional] ) Declares the final output of the script. Variable name is the variable that will be sent to the output.
You can't use expression instead of variable name.
Input and output types:
- audio - audio signal
- gate - gate signal (0 gate off - key up, 1 gate on - key down)
- time - time in seconds
- pitch - pitch in Hz
- velocity - gate velocity (combined with gate)
param(type, name) Declares controllable parameters.
Param types
- value - value in a range 0-1
- pitch - pitch in Hz in a range 20-20000
- time - time in seconds in a range 0-20
Examples:
// creates audio input and assigns it to in variable
in = input(audio)
// creates gate input named "My gate" - names are optional
in2 = input(gate, My gate)
// creates pitch knob named "Tune" and assigns it to pitch variable
pitch = param(pitch, Tune)
// pitch variable is represented in Hz 20-20000 Hz
out = oscSin(10)
output(out, audio)
// creates output of type audio and sends out variable to it
Per-sample processing mode
#persample
Used in the first line switches the engine from block processing (default) to per-sample processing.
In per-sample mode, you can read a variable before it is assigned in the current cycle, allowing for single-sample feedback loops. Initial variable value is 0.
#persample
// Simple feedback filter
out = input - last * 0.9
last = out
or simpler without intermediate variable
#persample
// Simple feedback filter
out = input - out * 0.9
System variables
You may use two variables in your code:
- sampleRate - contains the current system sample rate
- tempo - contains the current tempo
Operators
The language supports standard arithmetic and comparison operators:
+ Addition
- Subtraction
* Multiplication
/ Division
> Greater Than
< Less Than
>= Greater or Equal
<= Less or Equal
== Equal
!= Not Equal
? : Ternary Conditional (condition ? trueVal : falseVal) e.g. a = b>c?d:e
Control Flow
You can use if and else blocks to control signal flow.
if (gate > 0.5) {
env = 1.0
} else {
env = 0.0
}
Important: In block processing (default), the condition is typically evaluated for the entire block (based on the first sample) unless #persample mode is active.
Comments
Standard C-style comments are supported.
// Single line comment
/*
Multi-line
Block comment
*/
Standard Library
Math Functions
Standard mathematical functions apply element-wise to signals.
sin(x)
cos(x)
tan(x)
tanh(x)
abs(x)
log(x)
exp(x)
sqrt(x)
pow(a, b)
floor(x)
ceil(x)
fract(x)
and(a,b) or(a,b) xor(a,b)
min(a,b) max(a,b)
mix(a,b,m) - out = m*a + (1-m)*b
clamp( x, min, max)
mod(x,y)
Oscillators
oscSin(freq, phase [[optional]], sync [[optional]])
oscSaw(freq)
oscTri(freq)
oscPulse(freq, width)
noise()
Filters
lpf(input, cutoff, res) (12dB/oct) - SVF low pass filter
hpf(input, cutoff, res) (12dB/oct) - SVF high pass filter
bpf(input, cutoff, res) - SVF band pass filter
notch(input, cutoff, res) - SVF notch filter
Please note: SVF resonance (res) range is from 0 (no resonance) to 1 (ringing), filter damping coefficient is calculated as 2.0 - 2.0 * (1-res)^4.
lpf6(input, cutoff) (6dB/oct) - one pole low pass filter
hpf6(input, cutoff) (6dB/oct) - one pole high pass filter
apf6(input, cutoff) (6db/oct) - one pole allpass filter
comb(input, frequency, feedback gain) - comb filter
Envelopes
envAD(gate, attack time, decay time) - Attack/Decay envelope - triggered by rising edge, continues to the end
envD(gate, decay time) - Decay envelope - triggered by rising edge, continues to the end
envAHD(gate,attack time,hold time, decay time) - Attack/Hold/Decay envelope - triggered by rising edge, continues to the end
envAR(gate, attack time, decay time) - Attack/Release envelope. Release phase starts immediately from current state when gate goes to zero.
Delays
delay(input, timeInSeconds)
delayTuned(input, frequency) (Useful for Karplus-Strong and physical modelling)
Utility
seq(index, value1, value2, value3 ..... ) - Returns the value of the element at position index. Index is wrapped when exceeds range. May be used as a substitute of array.
edge(input) - detects input gate positive edge and emits 1 sample long unity value.
cvToFreq(freq) - Converts linear frequency to Drambo CV (1/8 per octave)
freqToCv(cv) - Converts Drambo CV to linear frequency.
(Optional. Use only if you need it, code module already has signals converted)
sh(input,gate) - sample and hold (S&H)
sign(input) - returns 1 when signal value is greater or equal to zero, otherwise -1
integrator(input) - TPT style signal integrator
differentiator(input) - TPT style signal differentiator
Examples
A simple waveshaper/saturator FX
// declare audio input and amount param
s=input(audio)
amount = param(value,Amount)
// tanh - hyperbolic tangent - soft clipper function
s=tanh(s*(1+amount*3))
// output s
output(s,audio)
Karplus-Strong (basic)
// per sample processing - we need instant feedback
#persample
// inputs
p = input(pitch)
g = input(gate)
// trigger signal - basic, noise -> envelope
n = noise()
n *= envD(g,0.05)
// feedback loop and basic sample average - filter
ns = n - delayTuned(s,p)*0.995
s = (s+ns)*0.5
output(s,audio)
// inputs
p = input(pitch)
g = input(gate)
// trigger signal - basic, noise -> envelope
n = noise()
n *= envD(g,0.05)
// feedback loop and basic sample average - filter
ns = n - delayTuned(s,p)*0.995
s = (s+ns)*0.5
output(s,audio)
Tempo synchronized feedback delay FX (basic)
// per sample, because we need instant feedback
#persample
i=input(audio)
i=input(audio)
// compute beatDuration in seconds from current tempo (system variable)
beatDuration = 60.0/tempo
feedback = 0.9
feedback = 0.9
s=i-delay(s,beatDuration)*feedback
output(s,audio)
output(s,audio)
Pitched noise - synth
// pitch and gate inputs
p = input(pitch)
g = input(gate)
g = input(gate)
// Attack and Decay paramters
attack = param(time,Attack)
decay = param(time,Decay)
s = noise()
attack = param(time,Attack)
decay = param(time,Decay)
s = noise()
// Filter resonance - fixed
r=0.96
r=0.96
// Double 12db band pass filters = 24db
s = bpf(s,p,r)
s = bpf(s,p,r)
s = bpf(s,p,r)
s = bpf(s,p,r)
// Scale output
s = s *0.1
s = s *0.1
// Apply AD envelope
s = s * envAD(g,attack,decay)
s = s * envAD(g,attack,decay)
// Output s - default: audio
output(s)
output(s)
Envelope EOC (detect envelope end of cycle)
env = envAD(gate)
// we turn falling envelope into negative and offset by 0.01 to cross zero
eoc = edge(0.01-env)
// trigger next envelope on eoc
nextEnv = envAD(eoc)
One More Thing: Share Your Code :)
Built something cool with the Code module? Share it with us!
If your patch is useful, original, or just plain fun, we’d love to feature it as an official Drambo preset, credited under your name. Send your code (and a quick note on what it does, plus any required controls/macros) to info@beepstreet.com or our community/presets channel, and you might see it included in a future preset pack.