Skip to content

strudel

Strudel is a live-coding music environment that runs entirely in a browser. You type short functional expressions that describe rhythms and melodies as patterns, and it plays them, live, editing-while-it-sounds. It is the official JavaScript port of TidalCycles (Alex McLean’s Haskell system), built by Felix Roos and Alex McLean and presented at the 7th International Conference on Live Coding (Utrecht, 2023). No install, runs on a phone; in-editor visualizations highlight the currently-sounding event so learners see what the code is doing.

The important correction to make when this comes up: Strudel is not AI music generation. It generates music algorithmically from code the operator writes, with zero machine learning. It is a programmable instrument. That is exactly why it belongs in this corpus: it is a working, performed instance of music-as-code-as-data, which is the KN-90T Toneline’s entire premise (kn90t-toneline.md §1).

How it works (studied from docs + source structure)

Section titled “How it works (studied from docs + source structure)”

Four ideas stacked on top of each other.

The whole engine, and it is deeply Lispy. A Pattern is a function that takes a time span (a fraction-based interval measured in cycles) and returns the set of events (“haps”) occurring in that span. Each hap is { value, begin, end } with begin/end as rational fractions of cycle time. You query a pattern over [0,1) and it tells you what happens. Their technical manual is explicit: “the query function … is the pattern itself.” Patterns are immutable and opaque; every transform returns a new pattern that wraps the old one, rewriting the query going in and post-processing haps coming out. There is no mutable timeline and no in-memory tracker grid. It is functional-reactive composition over time, functions all the way down.

Querying sequence("c3", ["e3","g3"]) over one cycle yields c3: 0→1/2, e3: 1/2→3/4, g3: 3/4→1.

Time is measured in cycles (default one per second). Everything fits inside the cycle; add more events and each event gets shorter, the cycle length is fixed. Rhythm is subdivision of a fixed container, so tempo and note-density decouple.

3. Mini-notation — a terse string DSL for rhythm

Section titled “3. Mini-notation — a terse string DSL for rhythm”

A compact rhythm grammar lives inside quoted strings:

SyntaxMeaning
"c e g b"sequence: 4 events share one cycle
"c [e g]"[ ] subdivide: the group takes one event’s slot
"<c e g>"< > alternate: one element per cycle, cycling across cycles
"c*2" / "c/2"* speed up within slot / / stretch across cycles
"c ~ e"~ (or -) rest / silence
"[c,e,g]", stack: polyphony / chords (simultaneous)
"c@3 e"@ elongate: weight a step’s duration
"c!2 e"! replicate: repeat without speeding up
"c?" / "c?0.1"? random drop (50% / 10%)
"c | e | g"| random choice per cycle
"bd(3,8,0)"Euclidean: 3 hits spread over 8 steps, offset 0

Euclidean rhythms are the standout for constrained chiptune hardware: two integers generate a musically even distribution of hits. bd(3,8) is a whole groove from four characters.

4. A combinator library — the power tier

Section titled “4. A combinator library — the power tier”

Patterns compose through a large library of higher-order functions. Complexity is built by wrapping, never by editing a grid:

FunctionEffect
fast n / slow ncompress / stretch in time
revreverse each cycle
iter nrotate the starting point each cycle
every n fapply transform f only on every n-th cycle
jux fsplit L/R, apply f to one side (stereo divergence)
off t foverlay a time-shifted, transformed copy
ply nrepeat each event n times
euclid k nEuclidean structuring
degrade / degradeBy prandomly thin events
sometimes fapply f to a random subset of events
palindromeforward then backward
struct "x ~ x x"drive a sound from a boolean rhythm
stack / cat / seqlayer / concatenate-by-cycle / sequence
segment, zoom, linger, compress, swing, rangeresample, window, loop-a-slice, groove, scale values

Music is written as a base pattern (note(...), n(...), s(...)) then method-chained with transforms and effects: s("bd sd, hh*8").fast(2).every(4, rev).lpf(800).room(.4).

Architecture (the portable-paradigm lesson)

Section titled “Architecture (the portable-paradigm lesson)”

pnpm-workspace monorepo split into small packages, so the paradigm travels independently of the audio:

  • corePattern, Hap, TimeSpan, Fraction; the query engine and combinators.
  • mini — the mini-notation parser (a PEG grammar that builds a Pattern from seq/stack/… calls).
  • transpiler — parses the operator’s JS, then rewrites it: double-quoted strings become mini-notation patterns, bare numbers/strings get “patternified,” and source locations are injected so the editor can highlight the sounding event. This is how plain JS gains its live-coding syntax sugar.
  • webaudio (superdough) — default synth + sampler output.
  • soundfonts / midi / osc / csound — alternate outputs.
  • tonal — music theory (scales, chords) via tonal.js.
  • repl / codemirror — editor, highlighting, eval loop.

The split that matters for us: core + mini are pure logic with no audio dependency. The pattern algebra is a self-contained idea, re-hostable on any output, including a 3-voice YM2149 PSG or a 6-voice YM2612.

Strudel is the shipping reference implementation of the Toneline’s headline claim, and it is the single most important prior art the concept doc was missing. Where line is the terse-grammar reference (a phrase is a text string that maps one-to-one onto a Fe s-expression), Strudel is the pattern-algebra reference: it proves that the transform verbs the Toneline lists as “the standard list library” (reverse, rotate, shuffle, append, repeat) are a complete, performable composition language once patterns are first-class values. Together line and Strudel bracket the Toneline’s design: line supplies the entry grammar, Strudel supplies the combinator vocabulary and the query model underneath it.

Concrete inputs to the Toneline sequencer design:

  • Adopt the pattern-as-function-of-cycle model as the runtime representation. The Toneline already says a phrase is a list and LAMBDA is a reusable phrase. Strudel shows the tier above the list: a pattern is a function cycle-span → events, which composes under a rich algebra. In Fe that is a lambda returning a list of haps. This gives the Toneline a principled scheduling model (query the next cycle, get the events, emit them) rather than only a static arrangement.
  • Steal the combinator vocabulary wholesale. fast, slow, rev, every, jux, off, ply, degrade, sometimes, iter, struct, stack, cat are the generative/performance layer the Toneline needs, and they are exactly list/function operations. The Toneline’s LINK-port two-deck jam and its knob-automation control phrases both want this composition tier.
  • Euclidean rhythms are the highest-value, lowest-cost import for any Deckline audio. For a handful of voices, (euclid 3 8) / (euclid 5 8) / (euclid 7 16) across the channels gives instant, musical, generative grooves from two integers each. Bjorklund’s algorithm is roughly fifteen lines. This is the cheapest possible route to lively pattern audio on either device.
  • The three-front-ends claim is validated. The Toneline (§1, §4) proposes one phrase representation viewed three ways: control surface, terse text prompt, structural nEmacs editing. Strudel’s REPL is a working existence proof of the terse-text front end over an immutable pattern value; our nEmacs is the structural one; the control surface is the physical one. Same phrase data, three views.
  • The mini-notation is optional sugar, and the honest Lisp move skips it. Strudel needs a transpiler to bolt a string DSL onto JavaScript. KEC Lisp does not: nested lists are the syntax. (fast 2 (euclid 3 8 'bd)), (stack a b c), (every 4 rev phrase) reads cleanly and satisfies “a phrase is a list” directly, so the Toneline gets Strudel’s expressive power without a parser layer.

The Deckline + LISP angle (why this belongs alongside line)

Section titled “The Deckline + LISP angle (why this belongs alongside line)”

line made the point that a phrase is an s-expression in disguise. Strudel makes the stronger point: a whole composition language collapses into function-and-list manipulation once patterns are first-class. That is the argument for the Toneline not shipping a separate sequencer DSL and a separate scripting language. There is one language, KEC Lisp, and the music is data and functions in it:

Strudel: s("bd(3,8)").every(4, rev).fast(2)
KEC Lisp: (fast 2 (every 4 reverse (euclid 3 8 'bd)))
Strudel: stack("c3 e3 g3", "bd*4")
KEC Lisp: (stack (seq c3 e3 g3) (fast 4 'bd))
Strudel: a Pattern = query: span -> [hap]
KEC Lisp: a pattern = (lambda (begin end) -> list of (value start dur))

There is a clean integration seam with the runtime that already ships on the KN-86, so this is not only a Toneline idea. The KN-86 already has a PSG “tracker” (music-define / music-play, a row is (channel reg val dwell)) that flattens rows into register writes for the coprocessor. A Fe pattern layer compiles down to that same row stream: query one cycle of combinators, flatten the haps to rows, hand them to the existing music path. The audio path underneath is untouched; the pattern layer just generates the rows instead of an author typing all 224 of them (as neongrid’s “Grid Drift” does today). That makes a small near-term KN-86 win (generative attract/idle audio, a “beat from three numbers” cart helper) as well as the large Toneline win.

Strudel’s own UI is a text editor plus behind-code visualizations, so the amber-on-black grid renders the operator surface natively (same as line). The one element to translate is the playing-pattern visualization (Strudel highlights the sounding event and draws the pattern’s structure). On the Deckline grid, carry that with the established toolkit: inversion for the current step, position for the playhead column, Unicode density bars (▁▂▃▄▅▆▇█) for per-step amplitude/velocity, and character shape (. ticks vs full glyphs) for subdivision depth. No color is needed: the pattern’s information is in the structure, and the grid only has to render the tokens and mark one cursor. The frequency-domain companion is cava (see the Toneline visualizer section).

  • License is a real product constraint, not a footnote. Strudel is AGPLv3 and its sound banks are separately licensed. Design and paradigm are not copyrightable, so the Toneline (and any KN-86 pattern helper) is clear to adopt the model. We do not copy source and we do not ship their samples. Flag this in any spec that promotes this reference to a build.
  • Timing precision lives in the coprocessor, not in Fe. Strudel gets sample-accurate timing from WebAudio while it queries per-cycle in a JS scheduler. On the KN-86 the Pi runs Fe at a ~20 fps event loop and the Pico owns 44.1 kHz synthesis, so the Fe pattern layer queries ahead (a cycle / a few rows) and streams register writes over UART. This is the tracker’s existing cadence; the model fits.
  • Bound the query allocation. Querying patterns allocates hap lists each cycle. Fe is arena + bounded mark-sweep, so query one cycle ahead and reset the arena at the cycle boundary to keep pauses deterministic, the same discipline the runtime uses at mission/cart edges.
  • We take the pattern algebra, not the DSP. Strudel is a full effects stack (filters, reverb, delay, FM). The PSG is 3 tones + noise + one envelope. The transferable part is the pattern/composition layer, which is the part that maps to a chiptune voice anyway.
  • Lineage of the idea. Strudel is TidalCycles (https://tidalcycles.org/) in the browser; TidalCycles is the origin of the pattern-as-function-of-time model. Cite Tidal as the deep source and Strudel as the accessible, still-maintained implementation.
  • Cross-link line.md — the terse phrase-grammar reference; line + Strudel together are the Toneline’s entry-grammar and combinator-algebra references.
  • Cross-link 4trk.md — the step-grid / groovebox GUI reference; Strudel is the live-coding / pattern-function reference. line, 4trk, and Strudel span the Toneline’s three plausible interaction models (typed phrase / step grid / composed pattern) over one phrase representation.
  • Cross-link features-matrix.md — concept #80 (“the sequencer language IS KEC Lisp”) is the row Strudel most directly evidences.

Primary source: Roos & McLean, Strudel: Live Coding Patterns on the Web, ICLC 2023 — https://doi.org/10.5281/zenodo.7842142.