12. daslang strudel vs strudel.cc — Feature Comparison
This page compares the daslang strudel library
(modules/dasAudio/strudel/) to its inspiration, the strudel.cc
live-coding system. It is the only documentation page that names
strudel.cc explicitly — all other pages should reference this one for
anything comparative.
Read this before porting a strudel.cc pattern to daslang. The
Claude-assistant porting workflow lives in the repository at
skills/strudel_port.md. For the
generated reference of every strudel symbol, see
Strudel (Live Coding). For the tutorial series, see
daStrudel (Live-Coding) Tutorials.
12.1. Scope and philosophy
The daslang port is feature-focused, not code-ported. We implement the musical primitives of strudel.cc on top of daslang’s compiler, pattern matcher, and audio engine — but with no JavaScript runtime, no web UI, no REPL, and no external DSP graph. Everything runs in one daslang context, with a per-voice synthesis pipeline and per-orbit effect busses.
Two consequences of that design:
The core pattern algebra, mini-notation, scales, SF2/MIDI, and live-reload are first-class. For the vast majority of strudel.cc patterns you can rewrite the code almost verbatim — same names, same mini-notation, same combinators.
The synth engine and effect routing are daslang-native and sometimes diverge. Per-voice FX, orbit busses, and ADSR defaults all have specific semantics that do not match strudel.cc exactly. Those cases are enumerated below.
12.2. What we have
Parity or near-parity with strudel.cc:
Feature area |
daslang module |
Notes |
|---|---|---|
Mini-notation |
|
|
Pattern algebra |
|
|
Time combinators |
|
|
Per-voice combinators |
|
|
Choice combinators |
|
|
Euclidean rhythms |
|
|
Scales |
|
|
Signals |
|
|
Synthesis |
|
oscillators ( |
Samples |
|
WAV / MP3 / FLAC / OGG loading via |
SF2 soundfonts |
|
Full SF2 parser (format 0/1 meta-events, generators, modulators), per-voice envelope / LFO / biquad, GM drum map compatibility, expression and mod-wheel CCs |
MIDI playback |
|
Format 0/1 parser, merged-track playback, GM preset mapping, integration with synth or SF2 backend |
Live-reload |
|
Persistent state across source reload via
|
Effects (send busses) |
|
Per-orbit |
Effects (per-voice) |
|
|
Pattern-valued setters |
|
Every scalar setter ( |
Numeric arguments |
|
Every scalar modifier accepts |
12.3. What we don’t have (yet)
Features that are present in strudel.cc and absent in the daslang port:
``jux_rev`` and other one-letter convenience wrappers — the parametric forms (
jux(pat, fn),stutter, etc.) are public; a few bare-name convenience wrappers were not ported. Notechopandsliceare available (granulation via the samplebegin/endwindow), as areeuclidRotand the mini-notation"bd(3,8)"Euclidean form.Web sample packs — strudel.cc streams sample packs from the web at runtime; daslang loads local samples from disk (via
strudel_load_sample_dir). Bring your own sample library.REPL UI — strudel.cc has a browser REPL with visualisation; daslang uses
daslang-livefor hot-reload and a separate visualiser example (examples/daStrudel/strudel_visualizer/).OSC / tidalcycles output — the daslang port renders audio directly. There is no equivalent of strudel.cc’s SuperDirt / OSC backend.
Hydra-style visual patterns — out of scope; daslang is audio-only.
Some mini-notation modifiers — a handful of exotic forms (e.g. some of the richer polymeter notations) may be missing; check
strudel_mini.dasfor the authoritative list of supported tokens.
12.4. Behavioural differences
Cases where a primitive with the same name behaves differently in the daslang port. Before porting a pattern that uses one of these, read the table — the output may differ even if the code looks identical.
12.4.1. ADSR defaults are tempo-aware
Divergence: daslang scales the default attack / decay / sustain / release values with the current CPS so that a default envelope feels right at any tempo. strudel.cc uses fixed-millisecond defaults.
Why: When you change tempo with strudel_set_cps mid-pattern,
you don’t want the envelope to suddenly overpower the note or shorten
to a click. The defaults are defined so that a whole-note envelope
fills one cycle at the current tempo.
How to apply:
For patterns that relied on strudel.cc’s fixed ADSR, pass explicit values:
pat |> attack(0.01) |> release(0.5).If you want the daslang behaviour in strudel.cc, multiply the attack / release by the period (1 / CPS).
See the adsr-defaults branch history for the full introduction.
12.4.2. Per-voice vs per-orbit FX
daslang splits effects into two groups, and this matches strudel.cc’s model closely:
Per-voice —
lpf/hpf/bpf,phaser,tremolo,compressor,shape,crush,coarse,djf, plusgain/pan/speed/fm/vowel. These are baked into each playing voice (theVoiceFXstruct) and run before the voice is mixed into its orbit. strudel.cc likewise applies these per event — daslang does not diverge here.Per-orbit (send bus) —
room/roomsize(reverb),delay/delaytime/delayfeedback, andchorus. One shared instance per orbit number; voices send wet/dry into it.
How to apply: because the first group is per-voice, two stacked
voices stay independent — each transformed copy from jux /
superimpose carries its own FX chain. For shared reverb/delay,
route patterns to the same orbit number (see below); for
independent bus FX, split orbits.
12.4.3. Orbit bus model
Divergence: daslang has an explicit OrbitBus per orbit
number. room sends to that orbit’s reverb, delay to that
orbit’s delay, and chorus to that orbit’s chorus — each FX
instance is allocated lazily on first send. strudel.cc routes
reverb/delay through SuperDirt differently.
How to apply: if your strudel.cc pattern mixes two orbits to
share a reverb, in daslang they need the same orbit number.
If you want independent reverbs/choruses per voice, split orbits.
12.4.4. Reverb quality
Extension (no strudel.cc equivalent): the convolution reverb has a
per-orbit quality tier, selected with roomquality:
"high"(default) — two decorrelated impulse responses, one full partitioned convolution per channel. Most expensive."medium"— a single mono impulse response convolved once, then split into stereo by a Schroeder allpass cascade per channel. Roughly half the per-block convolution cost. The cascade depth defaults to five stages but is adjustable from one to eight withroomstages— the allpass phase response is non-monotonic in depth (some depths are wide in stereo yet cancel in mono), so there is no single “best” value; pick one by ear."low"— a Freeverb-style algorithmic reverb (eight damped comb filters plus four series allpasses per channel). No FFT, so it is by far the cheapest tier (roughly 6x cheaper than"high"per block, and with no per-block convolution burst its peak CPU is far lower too). The trade is a less natural, more “metallic” tail; per-comb feedback still tracksroomsizeand the tail brightness still tracks the lowpass, so it responds to the same controls as the other tiers.
// a cheaper reverb on this orbit, with a 6-stage allpass decorrelation
note("c2 e2 g2") |> s("supersaw") |> room(0.6) |> roomsize(6.0)
|> roomquality("medium") |> roomstages(6) |> orbit(2)
Quality (and roomstages) is chosen when the orbit’s reverb is first
allocated and is re-applied if it changes; like roomsize, set it once
per orbit.
12.4.5. Mini-notation parsing
Divergence: mini-notation strings are only parsed by
parse_pattern / s / n / note / seq — not by
any implicit string coercion. pat = "bd sd" is a string
literal, not a Pattern.
Why: daslang is statically typed; implicit coercion from
string to Pattern would require custom conversion at every
call site. Explicit constructors are clearer and play better with
type-dispatched combinators.
How to apply: always wrap mini-notation in s("bd sd"),
n("0 2 4"), note("c4 e4 g4"), or parse_pattern("...").
12.4.6. fast at non-integer ratios
Divergence: both engines allow fractional factors (fast(1.5)),
but the underlying hap-slicing uses floor-division in daslang’s
splitSpans helper. Edge cases where the speedup causes a hap to
straddle a cycle boundary are resolved by keeping the earlier half;
strudel.cc may keep the later half.
How to apply: for rhythms near integer factors, behaviour is
identical. For oddly fractional fast values, compare audibly
and adjust.
12.4.7. Scheduler and voice pool
Divergence: daslang has a fixed voice-pool size (set at
Scheduler init); when exceeded, the oldest voice is stolen.
strudel.cc allocates voices dynamically per hap.
How to apply: very dense polyphony may drop notes in daslang.
Raise the pool size at Scheduler construction or thin the
pattern.
12.5. Extensions over strudel.cc
daslang adds a few primitives strudel.cc does not have. These ride
on top of the underlying audio_boost engine, so the existing
strudel.cc syntax keeps working — the extensions only activate when
you reach for them.
12.5.1. HRTF positional override
Extension: per-event hrtf_azimuth / hrtf_elevation (and
the combined hrtf(az, el)) replace the equal-power stereo
pan render path with a binaural-stereo HRTF: each input
channel becomes a virtual source at azimuth -/+ 30° (the standard
ITU listening triangle), each through its own ma_hrtf
instance, summed at the output. Pan and HRTF become orthogonal —
pan shapes the inherent stereo width / image of the source,
hrtf positions the source in 3D. Same CIPIC-based dataset
audio_boost uses; baked into the binary at compile time, no
extra setup needed.
API:
pat |> hrtf_azimuth(deg) // numeric, -180..180
pat |> hrtf_elevation(deg) // numeric, -90..90
pat |> hrtf(az, el) // combined numeric
// pattern-valued (animated):
pat |> hrtf_azimuth(sine() |> range(-90.0, 90.0) |> slow(4.0lf))
Setting any of the three flips the event’s hrtf_active flag.
The two HRTF outputs are summed and re-used for the
reverb / delay / chorus sends, so the entire wet path follows the
spatialised source (full physical accuracy) before being scaled
into the orbit effect buses.
Cost: each HRTF voice carries two ma_hrtf instances
(one per input channel, both allocated lazily on first render).
Practical for a handful of simultaneous voices; reach for plain
pan when you don’t need externalisation, depth, or front/back
disambiguation.
See also:
- Tutorial: STRUDEL-17 — HRTF: 3D Positional Override for Pan
- Demo: examples/daStrudel/hrtf/
- Engine reference: ma_hrtf
12.5.2. Host-driven / adaptive control
Extension: daStrudel runs in-process inside a host program (a game, a tool), not only a browser REPL - so the host can drive any pattern parameter from live runtime state.
The key primitive is signal(fn : lambda<(t : double) : float>): a control
signal built from an arbitrary time-to-value function. Point it at a host
variable and the value is read live on each cycle query, so flipping the
variable re-shapes the running pattern. Combined with the pattern-valued
setters (gain, lpf, …), this gates or morphs layers straight from
host state:
var DRUMS_ON = false // host flag, flipped at runtime
drums |> gain(signal(@(t : double) => DRUMS_ON ? 1.0 : 0.0))
One continuous stack whose layers are gated by host flags is the whole of
vertical-layering adaptive music - a switch is one variable write, no
crossfade machinery. The flag is sampled per query (at each event’s onset), so
a flip takes effect on the next scheduler query as upcoming events are
scheduled - sub-cycle, not aligned to an exact cycle boundary. Whole patterns
can also be added or dropped live with
strudel_add_track / strudel_remove_track. The host drives playback
from its own loop with strudel_tick (main thread), or bakes a fixed render
offline (no audio device) via the examples/daStrudel/features harness
(--wav / --duration).
The result is the basis for interactive / game-adaptive audio: the host emits
state, control signals read it, and the single running pattern adapts - all in
one daslang context, no REPL and no external sequencer. See the
examples/daStrudel/strudel_sf2_live/ demo and the state-driven game music
in examples/games/river_run/rr_audio.das.
12.6. Naming map (quick reference)
For porting at speed: the daslang name is the same as strudel.cc for ~95% of primitives. When it differs:
strudel.cc |
daslang |
Notes |
|---|---|---|
|
|
identical |
|
|
identical |
|
|
identical; for scale-degree use |
|
|
same name; bare int / float / double all accepted. Use the
pipe |
|
|
daslang expects a typed lambda — use |
|
|
both aliases exist |
|
|
top-level function, not a method |
|
|
same name; rotates the Euclidean pattern left by |
|
|
mini-notation Euclid form is parsed; |
|
|
sample granulation via the |
|
|
sets oscillator frequency in Hz directly, overriding |
For everything else, assume the name is identical. Use the MCP
list_module_api tool on strudel_pattern /
strudel_synth / strudel_scales to confirm.
See also
Strudel (Live Coding) — generated reference for every strudel symbol.
daStrudel (Live-Coding) Tutorials — 15-tutorial numbered series covering patterns, mini-notation, effects, synthesis, samples, MIDI, and live-reload.
STRUDEL-01 — Hello Pattern — start here for a tour.