
Strudel
- 98 installs
- 52 repo stars
- Updated March 4, 2026
- bfollington/terma
strudel is a Claude Code skill for creating and encoding Strudel.cc live-coding music patterns in JavaScript to run in the browser.
About
strudel is a skill for working with Strudel.cc, a browser-based live-coding music environment that ports TidalCycles pattern language to JavaScript. A developer uses it to create drum patterns, melodies, basslines, effects, and generative compositions, then run them in the browser via copy-paste or an encoded URL. It bundles pattern templates and a syntax reference.
- Writes Strudel.cc live-coding music patterns in JavaScript
- Covers drums, melodies, basslines, effects, and generative compositions
- Encodes patterns into shareable base64 strudel.cc URLs
Strudel by the numbers
- 98 all-time installs (skills.sh)
- Ranked #790 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
strudel capabilities & compatibility
Free; Strudel runs in the browser with no API keys.
- Capabilities
- music generation · generative art · creative coding
- Use cases
- image generation
- Pricing
- Free
What strudel says it does
Strudel is a browser-based live-coding environment that ports TidalCycles' pattern language to JavaScript.
Creating drum patterns, melodies, basslines, or full compositions
npx skills add https://github.com/bfollington/terma --skill strudelAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 98 |
|---|---|
| repo stars | ★ 52 |
| Last updated | March 4, 2026 |
| Repository | bfollington/terma ↗ |
What it does
Create and share Strudel.cc live-coding music patterns and generative compositions in the browser.
Who is it for?
Generating algorithmic drum, melody, and bassline patterns to play at strudel.cc.
Skip if: Producing rendered audio files or DAW projects; it outputs code, not audio.
When should I use this skill?
Creating a drum pattern, melody, bassline, or generative composition in Strudel.
What you get
Working Strudel code plus a clickable base64-encoded strudel.cc URL for immediate playback.
- Strudel pattern code
- Encoded strudel.cc playback URL
By the numbers
- 6 bundled pattern templates (techno-drums, acid-bass, ambient-pad, generative-melody, breakbeat, polyrhythm)
Files
Strudel
Overview
Strudel is a browser-based live-coding environment that ports TidalCycles' pattern language to JavaScript. It uses pure functional programming to create musical patterns that can include drums, melodies, synthesis, and effects. Patterns are immutable query functions that transform time spans into event streams, supporting mini-notation DSL, Euclidean rhythms, and extensive audio processing.
When to Use This Skill
Use this skill when:
- Creating drum patterns, melodies, basslines, or full compositions
- Generating algorithmic or generative music
- Applying effects like filters, reverb, delay, or distortion
- Debugging Strudel code or explaining pattern behavior
- Encoding Strudel code into shareable URLs
Core Workflow
1. Understanding the User's Musical Intent
When a user requests a pattern, clarify:
- Genre/style (techno, ambient, jazz, etc.)
- Artist references (Lorn, Clams Casino, Aphex Twin, etc.)
- Tempo (BPM or cycles per minute)
- Elements needed (drums only, drums + bass, full arrangement)
- Complexity level (simple loop vs. generative/evolving)
For genre/artist-specific techniques, consult references/genre-styles.md which provides:
- Sonic characteristics for common genres and artists
- Strudel implementation patterns
- Concrete code examples for each style
- Typical tempo ranges, effects, and structural elements
2. Writing the Pattern
Build patterns using Strudel's functional, compositional approach:
Basic structure:
// Simple drum pattern
s("bd hh sd hh")
// With tempo
s("bd hh sd hh").cpm(120)
// Layered with stack
stack(
s("bd(3,8)"), // Kick drum
s("hh*8"), // Hi-hats
s("~ sd ~ sd") // Snare on backbeat
).cpm(120)Key patterns:
- Use mini-notation (
"bd hh sd") for concise rhythm specification - Use
stack()to layer multiple patterns - Use
.cpm()for tempo in cycles per minute - Use Euclidean rhythms
(pulses, steps)for interesting distributions - Use lambda functions for transformations:
.off(1/8, x => x.add(7))
3. Providing Output to the User
The user always wants to run the code in their browser. Provide both:
1. The raw code - for copy-pasting into https://strudel.cc/ 2. An encoded URL - using scripts/strudel_url.py for immediate playback
When to encode URLs:
- Initial creation: Always provide encoded URL when creating a new pattern from scratch
- Iterations/refinements: Skip encoding unless user requests it
- Final version: Offer to encode when work is complete
Encoding a URL:
python3 scripts/strudel_url.py encode 's("bd hh sd hh").cpm(120)'This produces a clickable link like:
https://strudel.cc/#cygiYmQgaGggc2QgaGgiKS5jcG0oMTIwKQ%3D%3DPresent the URL as a clickable link the user can immediately open.
4. Iteration and Refinement
After the user tests the pattern, they may request modifications:
- Adjust tempo:
.cpm(130)or.slow(2)or.fast(2) - Add effects:
.room(.5),.lpf(1000),.delay(.25) - Add variation:
.sometimes(x => x.speed(2)),.every(4, rev) - Change sounds: Replace sample names or add synthesis parameters
Provide the updated code for copy-pasting. Skip URL encoding during iterations unless requested.
Common Pattern Templates
Use templates from assets/patterns/ as starting points:
- techno-drums.js - 4/4 techno with Euclidean patterns
- acid-bass.js - TB-303 style bassline with filter sweeps
- ambient-pad.js - Lush pad with slow chord progression
- generative-melody.js - Algorithmic melody with scale and Euclidean rhythms
- breakbeat.js - Amen break slicing and manipulation
- polyrhythm.js - Multiple time signatures layered
Reference these when users request similar styles.
Key Syntax Quick Reference
Mini-notation:
"a b c"- Sequence (evenly distributed)"<a b c>"- Slow cat (one per cycle)"[a b]"- Subdivision (faster)"a,b,c"- Parallel/chord"a*4"- Speed up (4x)"a/2"- Slow down (2x)"a(3,8)"- Euclidean rhythm (3 pulses in 8 steps)"~"- Rest/silence"a?"- 50% probability
Common functions:
s()or.sound()- Sample selectionnote()or.note()- Pitch (note name or MIDI number).scale()- Apply scale (e.g.,"C:minor")stack()- Layer patterns.fast()/.slow()- Speed control.rev()- Reverse.sometimes()- Apply transformation 50% of the time.every(n, fn)- Apply transformation every n cycles
Effects:
.lpf()- Low-pass filter (cutoff frequency).hpf()- High-pass filter.room()- Reverb (0-1).delay()- Delay effect (0-1).gain()- Volume.pan()- Stereo position (0=left, 1=right)
For comprehensive syntax, consult references/strudel-reference.md which contains:
- Complete mini-notation syntax
- All pattern transformations (Euclidean, temporal, stochastic)
- Audio effects and synthesis parameters
- Signal functions (sine, perlin, saw, etc.)
- Pattern alignment strategies
- Practical composition examples
Search this file with grep when needed:
grep -i "euclidean" references/strudel-reference.md
grep -i "filter" references/strudel-reference.md
grep -i "delay" references/strudel-reference.mdDebugging Common Issues
Pattern not playing:
- Ensure the pattern ends with appropriate audio output (
.s(),.sound(), or.note()) - Check that samples are loaded (default samples work without loading)
- Verify syntax: balanced brackets, quotes, parentheses
Timing issues:
- Use
.cpm()orsetcpm()to set tempo explicitly - Check for conflicting
.fast()/.slow()calls - Verify Euclidean rhythms have valid (pulses, steps) values
Filter not working:
- Ensure cutoff frequency is in valid range (20-20000 Hz)
- Check filter envelope parameters (
.lpa(),.lpd(),.lps(),.lpr()) - Use
.lpenv()for filter envelope depth
Code too complex:
- Break into smaller patterns and test individually
- Use variables:
let drums = s("bd hh") - Stack tested patterns:
stack(drums, bass, melody)
Resources
scripts/strudel_url.py
Python script for encoding/decoding Strudel URLs.
Encode:
python3 scripts/strudel_url.py encode '<strudel-code>'Decode:
python3 scripts/strudel_url.py decode '<strudel-url>'Always encode URLs after creating or modifying patterns to provide clickable links.
references/strudel-reference.md
Comprehensive technical reference including:
- Complete mini-notation syntax
- All pattern operations and transformations
- Audio effects and synthesis
- Signals and continuous patterns
- Composition examples
- Best practices
Load this file when users need detailed syntax explanations or advanced techniques.
references/genre-styles.md
Genre and artist style guide including:
- Dark Ambient Hip-Hop (Lorn, Clams Casino)
- Techno (General, Dub Techno)
- Drum & Bass / Jungle (Jungle, Liquid DnB)
- Trap / Hip-Hop (Trap, Boom Bap)
- Ambient / Experimental (Dark Ambient, IDM/Glitch)
- House (Deep House, Acid House)
Each entry provides sonic characteristics and Strudel implementation techniques. Load when users reference specific genres or artists.
Search this file when needed:
grep -i "lorn" references/genre-styles.md
grep -i "techno" references/genre-styles.md
grep -i "ambient" references/genre-styles.mdassets/patterns/
Example pattern templates for common musical styles. Copy and adapt these as starting points:
techno-drums.js- Electronic drum patternsacid-bass.js- Resonant basslinesambient-pad.js- Atmospheric texturesgenerative-melody.js- Algorithmic melodiesbreakbeat.js- Sample manipulationpolyrhythm.js- Complex rhythmic structures
// Classic acid bassline with TB-303 style filter modulation
note("[<g1 f1>/8](<3 5>,8)")
.clip(perlin.range(.15, 1.5))
.release(.1)
.s("sawtooth")
.lpf(sine.range(400, 800).slow(16)) // Sweeping filter
.lpq(cosine.range(6, 14).slow(3)) // Moving resonance
.lpenv(sine.mul(4).slow(4)) // Filter envelope
.lpd(.2).lpa(.02)
.ftype('24db')
.rarely(add(note(12))) // Occasional octave jump
.room(.2).shape(.3)
// Lush ambient pad with slow chord progression
note("<[c,e,g]!3 [d,f,a] [e,g,b]!2 [f,a,c]>")
.slow(8) // Very slow changes
.superimpose(x => x.add(.04)) // Slight detuning
.add(perlin.range(0, .2)) // Subtle pitch drift
.s("triangle")
.attack(2) // Slow fade in
.release(3) // Slow fade out
.lpf(sine.range(800, 2000).slow(32)) // Gentle filter movement
.room(.8) // Heavy reverb
.gain(.3)
// Amen break-style pattern with slicing
samples('github:tidalcycles/dirt-samples')
s("breaks165")
.slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev))
.slow(0.75)
.sometimes(x => x.speed("<1 0.5 2>")) // Occasional speed changes
.room(.2)
.hpf(100) // Clean up low end
.gain(.7)
// Generative melody using Euclidean rhythms and scale
note("<0 2 4 6>*8")
.scale("C:minor")
.euclid(5, 8) // Euclidean distribution
.fast("<1 2 4>") // Variable speed
.every(4, rev) // Reverse every 4 cycles
.sometimes(add(12)) // Random octave jumps
.off(1/8, x => x.add(7).degradeBy(.3)) // Echo with fifth
.s("sawtooth")
.lpf(sine.range(300, 2000).slow(8))
.room(.3)
.gain(.5)
// Polyrhythmic pattern with multiple time signatures
stack(
s("bd(3,8)"), // 3 against 8
s("sd(5,8,2)"), // 5 against 8, offset by 2
s("hh*8").speed(perlin.range(.9, 1.1)),
s("metal(7,16)").gain(.5) // 7 against 16
).cpm(130)
// Basic techno drum pattern
// Classic 4/4 kick with offbeat hi-hats and syncopated snare
stack(
s("bd(3,8)"), // Euclidean kick pattern
s("sd:[~ <sd!3 sd(3,4,2)>]"), // Syncopated snare
s("hh*8") // Hi-hat on every 8th
.speed(perlin.range(.9, 1.1)) // Subtle speed variation
.gain(perlin.range(.3, .5)), // Dynamic variation
s("oh").euclid(3, 16) // Open hi-hat accents
.gain(.4)
).cpm(128)
Genre and Artist Style Reference
This reference provides genre-specific characteristics and artist styles to help translate user references into concrete Strudel techniques.
Dark Ambient Hip-Hop
Lorn
Sonic Characteristics:
- Heavily pitched-down vocal samples (
.speed(0.5-0.7)) - Deep, melodic sub bass (sine wave with light saturation,
.lpf(180-250)) - Glitchy, industrial textures (
.degradeBy(),.scrub()on metal samples) - Heavy reverb and delay (
.room(.6-.9),.delay(.25-.5)) - Slow tempos (65-80 BPM)
- Sparse, weighted drums with long decay
Strudel Techniques:
// Lorn-style bass
note("d1 f1 g1 d1")
.s("sine")
.lpf(200)
.shape(.3-.5)
.decay(.6-.8)
.room(.2)
// Glitchy textures
s("metal").n("<2 4 7 3>").fit()
.scrub(irand(8).div(8).seg(16))
.dist("2:.6")
.hpf(8000)
.delay(.5)
.room(.6)Clams Casino
Sonic Characteristics:
- Ethereal, pitched vocal chops (
.speed(0.5-0.8), heavy reverb) - Lush pad layers with stereo width (
.pan()modulation) - Dreamy atmosphere (long reverb tails
.room(.8-.9),.size(.9-.95)) - Simple, sparse drum patterns
- Extended chord voicings (7ths, 9ths, 11ths)
- Delay as melodic element (
.delay(.375-.5))
Strudel Techniques:
// Clams Casino-style vocals
s("vox").n("<0 2 4 1>").fit()
.speed(choose([0.5, 0.6, 0.7]))
.room(.8)
.size(.9)
.delay(.375)
// Atmospheric pads
note("<d2 f2 g2 a2>")
.add(note("[0,7,10,14]")) // Extended chords
.s("sine")
.attack(1.5)
.release(3)
.room(.9)
.pan(sine.range(0.3, 0.7).slow(16))Techno
General Techno
Sonic Characteristics:
- 4/4 kick pattern (120-140 BPM)
- Euclidean hi-hat patterns
- Repetitive, hypnotic elements
- Filter sweeps on synth lines
- Heavy use of delay and reverb
Strudel Techniques:
// Techno kick
s("bd(4,4)").n(27)
.gain(.9)
.lpf(120)
// Euclidean hats
s("hh").struct("x(5,8)")
.gain(.4)
.hpf(8000)
// Acid bassline
note("<c2 eb2 f2 g2>")
.s("sawtooth")
.lpf(sine.range(300, 2000).fast(4))
.resonance(10)Dub Techno
Sonic Characteristics:
- Deep, rolling bass
- Chord stabs with long delay (
.delay(.5-.75)) - Minimalist drums
- Heavy reverb creating space
- Slower tempo (120-125 BPM)
Strudel Techniques:
// Dub chord stabs
note("<d3 f3 a3>").slow(4)
.s("sawtooth")
.decay(.3)
.delay(.75)
.room(.8)
.lpf(1500)Drum & Bass / Jungle
Jungle
Sonic Characteristics:
- Fast tempo (160-180 BPM)
- Amen break manipulation
- Deep sub bass (sine wave, 40-80 Hz)
- Reggae/dub influences
- Sample chops and edits
Strudel Techniques:
// Amen break
s("breaks").n(0).fit()
.scrub(irand(16).div(16).seg(8))
.fast(2)
.sometimes(rev)
// Jungle sub bass
note("d1 ~ ~ d1 ~ d1 ~ ~")
.s("sine")
.lpf(80)
.decay(.6)
.sustain(.8)Liquid DnB
Sonic Characteristics:
- Melodic elements
- Smooth pads and chords
- Jazz-influenced harmony
- Warm bass tones
- Soulful vocals
Strudel Techniques:
// Liquid pad
note("<d3 f3 a3 c4>")
.add(note("[0,4,7]"))
.s("sine")
.attack(1)
.release(2)
.room(.7)
.lpf(3000)Trap / Hip-Hop
Trap
Sonic Characteristics:
- Hi-hat rolls (
.fast(4)or*8) - 808 sub bass slides (
.slide()or portamento) - Snare rolls and triplet patterns
- Slow tempo (130-170 BPM, half-time feel)
- Layered percussion
Strudel Techniques:
// Trap hi-hat rolls
s("hh*8").n(3)
.sometimes(fast(2))
.gain(perlin.range(.3, .6))
// 808 bass
note("c1 ~ eb1 f1")
.s("sine")
.lpf(200)
.dist("2:.8")
.decay(.8)Boom Bap
Sonic Characteristics:
- Vinyl crackle texture
- Hard-hitting snare on 2 and 4
- Kicked-back swing feel
- Soul/jazz samples
- Moderate tempo (85-95 BPM)
Strudel Techniques:
// Boom bap drums
stack(
s("bd").beat("0,6,10,14", 16),
s("sd").beat("4,12", 16).gain(.7),
s("hh!16").gain(perlin.range(.2, .4))
).swingBy(0.4, 8)
// Vinyl texture
s("vinyl").n(2).fit()
.gain(.2)
.hpf(800)
.room(.2)Ambient / Experimental
Dark Ambient
Sonic Characteristics:
- Long, evolving textures
- Low-frequency drones
- Sparse or absent rhythm
- Heavy reverb and space
- Dissonant or atonal harmony
Strudel Techniques:
// Dark drone
note("d1 f1 ab1")
.s("sine")
.attack(4)
.release(8)
.room(.95)
.dist("0.5:.3")
.lpf(sine.range(200, 800).slow(32))
// Textural noise
s("white")
.lpf(perlin.range(400, 1200))
.hpf(300)
.gain(.15)
.room(.9)IDM / Glitch
Sonic Characteristics:
- Complex rhythmic patterns (Euclidean, polyrhythms)
- Digital artifacts and glitches (
.degradeBy(),.crush()) - Unconventional time signatures
- Precise, robotic sounds
- Frequent pattern variation
Strudel Techniques:
// Glitchy pattern
s("bd hh sd").struct("x(5,13)")
.degradeBy(perlin.range(0, .5))
.crush(4)
.sometimes(ply(2))
.every(4, rev)
// Polyrhythmic layers
stack(
s("bd(3,8)"),
s("sd(5,13)"),
s("hh(7,16)")
)House
Deep House
Sonic Characteristics:
- Warm, groovy basslines
- Soulful vocal samples
- Jazzy chords (7ths, 9ths)
- Subtle percussion layers
- 4/4 kick with open hi-hat on offbeats
- Tempo (120-125 BPM)
Strudel Techniques:
// Deep house bass
note("c2 ~ eb2 ~ f2 ~ eb2 ~")
.s("sawtooth")
.lpf(800)
.resonance(5)
.decay(.4)
// Jazzy chords
note("<c3 eb3 f3 bb3>")
.add(note("[0,4,7,10]"))
.s("triangle")
.attack(.3)
.decay(.8)
.room(.4)Acid House
Sonic Characteristics:
- TB-303 bassline (
.resonance(10-20)) - Filter sweeps (sine/perlin modulation)
- Repetitive, hypnotic
- Squelchy, resonant sounds
- Moderate tempo (120-130 BPM)
Strudel Techniques:
// 303 bassline
note("<c2 eb2 f2 g2>")
.s("sawtooth")
.lpf(sine.range(200, 2500).fast(4))
.resonance(15)
.decay(.1)
.gain(.6)Usage Guidelines
When using this reference:
1. Identify user's reference - Look for artist/genre mentions in request 2. Load relevant section - Read applicable characteristics 3. Apply techniques - Use provided Strudel code patterns 4. Adapt, don't copy - Treat examples as starting points, not templates 5. Combine elements - Mix characteristics from multiple genres/artists as needed
Searching this file:
grep -i "lorn" references/genre-styles.md
grep -i "trap" references/genre-styles.md
grep -i "ambient" references/genre-styles.mdContributing New Styles
When discovering new genre/artist patterns through usage:
1. Document sonic characteristics (what you hear) 2. Provide Strudel implementation techniques (how to create it) 3. Include BPM ranges, typical effects, and structural elements 4. Add concrete code examples that demonstrate the style
Strudel Technical Reference: Operations and Pattern Syntax
Strudel ports TidalCycles' functional pattern language to JavaScript, implementing pure functional reactive programming for live-coded music. Patterns are immutable query functions that transform time spans into event streams.
Core Concepts
Pattern Query Model: Patterns are opaque functions TimeSpan → [Event] that generate events on-demand. Transformations create new patterns wrapping old ones, manipulating queries before and results after.
Cycles: The fundamental temporal unit. Default cycle = 0.5 CPS (2 seconds). All patterns align to cycle boundaries using rational numbers (fractions) for precise timing.
Immutability: Every transformation returns a new pattern. No mutation.
Method Chaining: Fluent interface for composing transformations: .fast(2).rev().add(7)
Mini-Notation DSL
The mini-notation is a domain-specific language for expressing rhythmic patterns concisely. Double-quoted strings are automatically parsed as mini-notation.
Sequence and Structure
// Space-separated sequence - events evenly distributed
note("c e g b") // 4 events per cycle, each 1/4 cycle
// Square brackets - subdivide parent event's time
note("e5 [b4 c5] d5 [c5 b4]") // Nested subdivisions
note("e5 [b4 c5] d5 [c5 b4 [d5 e5]]") // Arbitrary depth
// Angle brackets - slow cat (one per cycle)
note("<e5 b4 d5 c5>") // One element each cycle
note("<e5 b4 d5 c5>*8") // Can be multiplied
// Comma - parallel/simultaneous events
note("[g3,b3,e4]") // Chord (polyphony)
note("g3,b3,e4") // Outer brackets optionalTiming Operators
// * - Multiplication (speed up)
note("[e5 b4 d5 c5]*2") // Play twice per cycle
sound("hh*8") // 8 hi-hats per cycle
// / - Division (slow down)
note("[e5 b4 d5 c5]/2") // Spread over 2 cycles
// @ - Weight/elongation (relative duration)
note("<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>*2")
// @2 element is twice as long as @1 elements
// ! - Replication (repeat without speeding up)
note("<[g3,b3,e4]!2 [a3,c3,e4]>*2")
sound("[bd!4, cp!3]") // Different repetition counts
// ~ or - - Rest/silence
note("[b4 [~ c5] d5 e5]")
sound("bd hh - rim")Euclidean Rhythms
// (pulses, steps, offset?)
s("bd(3,8)") // 3 beats over 8 steps (Bjorklund algorithm)
s("bd(3,8,0)") // Optional offset (default 0)
s("bd(3,8,3)") // Start from position 3
note("e5(2,8) b4(3,8) d5(2,8) c5(3,8)") // Multiple patterns
// Negative pulses invert pattern
s("bd(-3,8)")Polymeter
// {} - Curly braces for polymeter
sound("{per per:6 [~ per:14] per:27, text:17 ~ ~ ~ tone:29}")
// Patterns repeat until LCM fits cycle
// First pattern sets pulse/meter
// With step specification %
note("{c eb g, c2 g2}%6") // Align to 6 stepsRandomness
// ? - Degradation (random removal)
sound("bd hh? sd? oh") // ? = 50% removal
sound("bd hh?0.1 sd?0.9 oh") // Explicit probabilities
// | - Random choice per cycle
note("[c3|e3|a3], [c4|e4|a4]")Sample Selection
sound("casio:1") // Select sample 1 from bank
sound("hh:0 hh:1 hh:2 hh:3") // Explicit indices
// Alternative functional form (more composable)
n("0 1 [4 2] 3*2").sound("jazz")Mini-Notation to Function Equivalents
| Mini-Notation | Function | Description |
|---|---|---|
"x y" | seq(x, y) | Sequence (fastcat) |
"<x y>" | cat(x, y) | Slow cat |
"x,y" | stack(x, y) | Parallel |
"x@3 y@2" | stepcat([3,x], [2,y]) | Weighted steps |
"{a b c, x y}" | polymeter([a,b,c], [x,y]) | Polymeter |
"{x y z}%2" | polymeterSteps(2, x,y,z) | Polymeter with steps |
"~" | silence | Rest |
"x*n" | fast(n) | Speed up |
"x/n" | slow(n) | Slow down |
Complete Mini-Notation Example
n("60(3,8) [64 67]!2 <69 [72 74]*2> ~@2 [60,64,67]?0.5")
.sound("piano")
// Uses: Euclidean, replication, angle brackets,
// rests, weight, polyphony, probabilityPattern Construction
Basic Constructors
// cat / slowcat - one per cycle
cat("e5", "b4", ["d5", "c5"]).note()
// Equivalent: "<e5 b4 [d5 c5]>"
// seq / sequence / fastcat - cram into one cycle
seq("e5", "b4", ["d5", "c5"]).note()
// Equivalent: "e5 b4 [d5 c5]"
// stack / polyrhythm - simultaneous
stack("g3", "b3", ["e4", "d4"]).note()
// Equivalent: "g3,b3,[e4,d4]"
// Chained form
s("hh*4").stack(note("c4(5,8)"))Weighted Concatenation
// stepcat - proportional steps
stepcat([3,"e3"], [1, "g3"]).note()
// Equivalent: "e3@3 g3"
stepcat("bd sd cp", "hh hh").sound()
// Infers steps from pattern length
// arrange - multi-cycle patterns
arrange(
[4, "<c a f e>(3,8)"],
[2, "<g a>(5,8)"]
).note()Polymeter
polymeter("c eb g", "c2 g2").note()
// First pattern repeats 2x, second 3x to fit LCM=6
// Equivalent: "{c eb g, c2 g2}%6"
polymeterSteps(2, ["c", "d", "e", "f"]).note()
// 2 steps per cycleTime and Rhythm Operations
Speed Control
// fast - speed up by factor
s("bd hh sd hh").fast(2)
// Mini-notation: "[bd hh sd hh]*2"
// slow - slow down by factor
s("bd hh sd hh").slow(2)
// Mini-notation: "[bd hh sd hh]/2"
// Accepts patterns
note("c d e f").fast("<1 2 4>")
// hurry - fast + speed (pitch shift)
note("c e g").hurry(2)Temporal Shifting
// early - nudge pattern earlier
"bd ~".stack("hh ~".early(.1)).s()
// late - nudge pattern later
"bd ~".stack("hh ~".late(.1)).s()
// Can be patterned for micro-timing
s("hh*8").late("[0 .01]*4") // HumanizationTemporal Windowing
// zoom - play portion over full cycle
s("bd*2 hh*3 [sd bd]*2 perc").zoom(0.25, 0.75)
// Equivalent to: s("hh*3 [sd bd]*2")
// compress - compress into timespan, leave gap
cat(
s("bd sd").compress(.25, .75),
s("~ bd sd ~")
)
// linger - select and repeat fraction
s("lt ht mt cp, [hh oh]*2").linger("<1 .5 .25 .125>")
// fastGap - speed up but leave gap
s("bd sd").fastGap(2) // Compressed into first halfReversal and Rotation
// rev - reverse pattern
note("c d e g").rev()
// palindrome - alternate forward/backward each cycle
note("c d e g").palindrome()
// iter - rotate starting position each cycle
note("0 1 2 3".scale('A minor')).iter(4)
// Cycle 1: 0 1 2 3
// Cycle 2: 1 2 3 0
// Cycle 3: 2 3 0 1
// Cycle 4: 3 0 1 2
// iterBack - reverse iteration
note("0 1 2 3".scale('A minor')).iterBack(4)Duration Control
// clip / legato - multiply duration, cut samples
note("c a f e").s("piano").clip("<.5 1 2>")
// ply - repeat each event n times
s("bd ~ sd cp").ply("<1 2 3>")
// segment - sample continuous pattern at discrete points
note(saw.range(40,52).segment(24))Euclidean Operations
// euclid - distribute pulses across steps
note("c3").euclid(3,8) // Cuban tresillo
// Mini-notation: "c3(3,8)"
// euclidRot - with rotation offset
note("c3").euclidRot(3,16,14) // Samba rhythm
// Mini-notation: "c3(3,16,14)"
// euclidLegato - hold each pulse until next
note("c3").euclidLegato(3,8)Swing and Groove
// swingBy - delay events in second half of slices
s("hh*8").swingBy(1/3, 4)
// offset: 0=none, 0.5=half delay, 1=wrap
// subdivision: slices per cycle
// swing - shorthand (1/3 offset)
s("hh*8").swing(4)Tempo Control
// Global tempo
setcps(1) // 1 cycle per second
setcpm(110) // 110 cycles per minute
// Pattern-specific tempo
s("<bd sd>,hh*2").cpm(90) // 90 BPM
// BPM to CPS conversion:
// setcpm(bpm/bpc) where bpc = beats per cycle
setcpm(110/4) // 4-beat cycles at 110 BPMPattern Transformations and Combinators
Higher-Order Functions
// every - apply transformation every n cycles
note("c d e f").every(4, x => x.rev())
// when - conditional application
"c3 eb3 g3".when("<0 1>/2", x => x.sub(5)).note()
// lastOf / firstOf - apply at specific cycle position
note("c3 d3 e3 g3").lastOf(4, x => x.rev())
note("c3 d3 e3 g3").firstOf(4, x => x.rev())Stochastic Application
// Probability-based transformations
s("hh*8").sometimes(x => x.speed("0.5")) // 50%
s("hh*8").sometimesBy(.4, x => x.speed("0.5")) // 40%
// Named probability functions
.often(fn) // 75%
.rarely(fn) // 25%
.almostNever(fn) // 10%
.almostAlways(fn) // 90%
.always(fn) // 100%
.never(fn) // 0%
// Cycle-level (not event-level)
s("bd,hh*8").someCyclesBy(.3, x => x.speed("0.5"))
s("bd,hh*8").someCycles(x => x.speed("0.5")) // 50%Layering and Accumulation
// superimpose - layer transformation on original
"<0 2 4 6>*8"
.superimpose(x => x.add(2))
.scale('C minor').note()
// layer - transformations without original
"<0 2 4 6>*8"
.layer(x => x.add("0,2"))
.scale('C minor').note()
// off - offset and layer transformation
"c3 eb3 g3".off(1/8, x => x.add(7)).note()
n("0 [4 <3 2>] <2 3> [~ 1]")
.off(1/16, x => x.add(4))
.off(1/8, x => x.add(7))
// echo - repeats with velocity decay
s("bd sd").echo(3, 1/6, .8)
// echoWith - custom function each iteration
"<0 [2 4]>"
.echoWith(4, 1/8, (p,n) => p.add(n*2))
.scale("C:minor").note()Structural Transformations
// chunk - divide into n parts, apply per cycle
"0 1 2 3".chunk(4, x => x.add(7)).scale("A:minor").note()
// chunkBack - reverse order
"0 1 2 3".chunkBack(4, x => x.add(7)).scale("A:minor").note()
// fastChunk - source pattern doesn't repeat
"<0 8> 1 2 3 4 5 6 7"
.fastChunk(4, x => x.color('red')).slow(2)
.scale("C2:major").note()
// inside - apply transformation inside slower cycle
"0 1 2 3 4 3 2 1".inside(4, rev).scale('C major').note()
// Equivalent: .slow(4).rev().fast(4)
// outside - apply transformation outside faster cycle
"<[0 1] 2 [3 4] 5>".outside(4, rev).scale('C major').note()
// Equivalent: .fast(4).rev().slow(4)Masking and Filtering
// struct - apply structure to pattern
note("c,eb,g")
.struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
.slow(2)
// mask - silence when mask is 0 or "~"
note("c [eb,g] d [eb,g]").mask("<1 [0 1]>")
// reset / restart - restart pattern on onsets
s("[<bd lt> sd]*2, hh*8").reset("<x@3 x(5,8)>")
s("[<bd lt> sd]*2, hh*8").restart("<x@3 x(5,8)>")Pattern Selection
// pick - select patterns by index/name
note("<0 1 2!2 3>".pick(["g a", "e f", "f g f g", "g c d"]))
s("<a!2 [a,b] b>".pick({a: "bd(3,8)", b: "sd sd"}))
// pickRestart - restart chosen pattern when triggered
"<a@2 b@2 c@2 d@2>".pickRestart({
a: n("0 1 2 0"),
b: n("2 3 4 ~"),
c: n("[4 5] [4 3] 2 0"),
d: n("0 -3 0 ~")
}).scale("C:major").s("piano")
// squeeze - compress selected pattern into event
note(squeeze("<0@2 [1!2] 2>", ["g a", "f g f g", "g a c d"]))
// inhabit / pickSqueeze - cycles squeezed into target
"<a b [a,b]>".inhabit({a: s("bd(3,8)"), b: s("cp sd")})Arpeggiation
// arp - select indices in stacked notes
note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
.arp("0 [0,2] 1 [0,2]")
// arpWith - custom selection function
note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
.arpWith(haps => haps[2])Randomness
// degradeBy - random event removal
s("hh*8").degradeBy(0.2) // Remove 20%
// Mini-notation: "[hh?0.2]*8"
s("hh*8").degrade() // 50% removal
// undegradeBy - inverse degradation
s("hh*10").layer(
x => x.degradeBy(0.2).pan(0),
x => x.undegradeBy(0.8).pan(1)
)
// choose - random choice each event
note("c2 g2!2 d2 f1").s(choose("sine", "triangle", "bd:6"))
// wchoose - weighted random choice
note("c2 g2!2 d2 f1")
.s(wchoose(["sine",10], ["triangle",1], ["bd:6",1]))
// chooseCycles / randcat - random per cycle
chooseCycles("bd", "hh", "sd").s().fast(8)
// Mini-notation: s("bd | hh | sd").fast(8)
// wchooseCycles - weighted cycle choice
wchooseCycles(["bd(3,8)",5], ["hh hh hh",3]).fast(4).s()Value Modifiers
// Arithmetic operations
n("0 2 4".add("<0 3 4 0>")).scale("C:major") // Transposition
note("c3 e3 g3".add("<0 5 7 0>")) // Transpose notes
n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor") // Descending
"<1 1.5 2>*4".mul(150).freq() // Multiplication
pattern.div(2) // Division
// Rounding
pattern.round() // Nearest integer
pattern.floor() // Floor
pattern.ceil() // Ceiling
// Range mapping
sine.range(100, 2000) // Map 0-1 to range
sine.rangex(100, 2000) // Exponential curve
sine2.range2(100, 2000) // Map -1 to 1 to rangeStereo Operations
// jux - apply function only to right channel
s("lt ht mt ht hh").jux(rev)
// juxBy - adjustable stereo width (0=mono, 1=full)
s("bd lt [~ ht] mt cp ~ bd hh").juxBy("<0 .5 1>/2", rev)
// pan - stereo position (0=left, 1=right)
s("[bd hh]*2").pan("<.5 1 .5 0>")
s("bd rim sd rim bd ~ cp rim").pan(sine.slow(2))Looping
// ribbon - loop pattern slice at offset for cycles
note("<c d e f>").ribbon(1, 2)
n(irand(8).segment(4)).scale("c:pentatonic").ribbon(1337, 2)Audio Effects and Sound Design
Filters
// Low-pass filter (cutoff: 0-20000 Hz)
s("bd sd,hh*8").lpf("<4000 2000 1000 500>")
// Aliases: cutoff, ctf, lp
// With Q-factor (resonance: 0-50)
s("bd*16").lpf("1000:0 1000:10 1000:20 1000:30")
s("bd sd,hh*8").lpf(2000).lpq("<0 10 20 30>")
// Aliases: lpq, resonance
// High-pass filter
s("bd sd,hh*8").hpf("<4000 2000 1000 500>")
s("bd sd,hh*8").hpf(2000).hpq("<0 10 20 30>")
// Aliases: hp, hcutoff, hresonance
// Band-pass filter
s("bd sd,hh*6").bpf("<1000 2000 4000 8000>")
s("bd sd").bpf(500).bpq("<0 1 2 3>")
// Aliases: bandf, bp, bandq
// Filter type selection (12db, ladder, 24db)
note("{f g g c d a a#}%8").s("sawtooth")
.lpenv(4).lpf(500).ftype("<0 1 2>").lpq(1)
note("c f g g a c d4").fast(2)
.sound('sawtooth')
.lpf(200).ftype("<ladder 12db 24db>")
// Vowel formant filter
note("[c2 <eb2 <g2 g1>>]*2").s('sawtooth')
.vowel("<a e i <o u>>")
// Vowels: a e i o u ae aa oe ue y uh un en an onFilter Envelopes
// Each filter type has ADSR envelope control
note("[c eb g <f bb>](3,8,<0 1>)".sub(12))
.s("sawtooth")
.lpf(sine.range(300,2000).slow(16)) // Cutoff
.lpq(sine.range(2,10).slow(32)) // Resonance
.lpa(0.005) // Attack
.lpd(.02) // Decay
.lps(.5) // Sustain (0-1)
.lpr(.1) // Release
.lpenv(4) // Envelope depth (can be negative)
.ftype('24db')
// High-pass envelope: hpa, hpd, hps, hpr, hpenv
// Band-pass envelope: bpa, bpd, bps, bpr, bpenvAmplitude Envelope (ADSR)
// Individual parameters (time in seconds, sustain 0-1)
note("c3 e3 f3 g3")
.attack("<0 .1 .5>") // Time to peak
.decay("<.1 .2 .3 .4>") // Time to sustain
.sustain("<0 .1 .4 .6 1>") // Sustain level
.release("<0 .1 .4 .6 1>/2") // Release time
// Combined ADSR
note("[c3 bb2 f3 eb3]*2").sound("sawtooth")
.lpf(600)
.adsr(".1:.1:.5:.2")Pitch Envelope
// Pitch modulation envelope (chiptune/percussive sounds)
n("<-4,0 5 2 1>*<2!3 4>")
.scale("<C F>/8:pentatonic")
.s("gm_electric_guitar_jazz")
.penv("<.5 0 7 -2>*2") // Modulation in semitones
.patt(.02) // Attack time
.pdec(.1) // Decay time
.prel(.1) // Release time
.pcurve("<0 1>") // 0=linear, 1=exponential
.panchor("<0 .5 1>") // Anchor pointWaveshaping and Distortion
// Distortion (0-10+, gets loud!)
s("bd sd,hh*8").distort("<0 2 3 10:.5>")
// With postgain (second parameter)
note("d1!8").s("sine")
.penv(36).pdecay(.12)
.distort("8:.4")
// Bit crusher (1=severe, 16=minimal)
s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
// Coarse (sample rate reduction, Chrome-only)
s("bd sd,hh*8").coarse("<1 4 8 16 32>")Delay (Global Effect)
// Basic delay (level: 0-1)
s("bd bd").delay("<0 .25 .5 1>")
// With time and feedback in mininotation
s("bd bd").delay("0.65:0.25:0.9 0.65:0.125:0.7")
// Separate parameters
s("bd bd")
.delay(.25)
.delaytime("<.125 .25 .5 1>")
.delayfeedback("<.25 .5 .75>") // WARNING: >=1 infinite
// Aliases: delayt, dt / delayfb, dfbReverb (Global Effect)
// Basic reverb (level: 0-1)
s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>")
// With room size in mininotation
s("bd sd [~ bd] sd").room("<0.9:1 0.9:4>")
// Room parameters (recalculated when changed)
s("bd sd [~ bd] sd")
.room(.8)
.rsize(4) // Size: 0-10
.rfade(4) // Fade time in seconds
.rlp(5000) // Lowpass: 0-20000 Hz
.rdim(400) // Lowpass at -60dB: 0-20000 Hz
// Custom impulse response
s("bd sd [~ bd] sd")
.room(.8)
.ir("<shaker_large:0 shaker_large:2>")Phaser
n(run(8)).scale("D:pentatonic").s("sawtooth")
.release(0.5)
.phaser("<1 2 4 8>") // Speed of modulation
.phaserdepth("<0 .5 .75 1>") // Depth: 0-1, default 0.75
.phasercenter("<800 2000 4000>") // Center Hz, default 1000
.phasersweep("<800 2000 4000>") // Sweep: 0-4000, default 2000Tremolo (Amplitude Modulation)
note("d d d# d".fast(4)).s("supersaw")
.tremolosync("4") // Speed in cycles
.tremolodepth("<1 2 .7>") // Depth
.tremoloskew("<.5 0 1>") // Shape: 0-1
.tremolophase("<0 .25 .66>") // Phase offset in cycles
.tremoloshape("<sine tri square>") // Shape typeDynamics
// Gain (exponential multiplier)
s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2)
// Velocity (0-1, multiplied with gain)
s("hh*8")
.gain(".4!2 1 .4!2 1 .4 1")
.velocity(".4 1")
// Compressor (format: "threshold:ratio:knee:attack:release")
s("bd sd [~ bd] sd,hh*8")
.compressor("-20:20:10:.002:.02")
// Postgain (applied after all effects)
.postgain(1.5)Sample Manipulation
// Begin/End (0-1, proportion of sample)
samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/dirt-samples')
s("rave").begin("<0 .25 .5 .75>").fast(2)
s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2)
// Speed (playback rate, negative = reverse)
s("bd*6").speed("1 2 4 1 -2 -4")
speed("1 1.5*2 [2 1.1]").s("piano").clip(1)
// Looping
s("casio").loop(1)
s("space").loop(1)
.loopBegin("<0 .125 .25>")
.loopEnd("<1 .75 .5 .25>")
// Wavetables (samples starting with "wt_" auto-loop)
samples('github:bubobubobubobubo/dough-waveforms')
note("c eb g bb").s("wt_dbass").clip(2)
// Slicing and chopping
samples('github:tidalcycles/dirt-samples')
s("breaks165")
.slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev))
.slow(0.75)
// Splice (adjusts speed to match duration)
s("breaks165")
.splice(8, "0 1 [2 3 0]@2 3 0@2 7")
// Striate (progressive portions)
s("numbers:0 numbers:1 numbers:2").striate(6).slow(3)
// Duration control
note("c a f e").s("piano").clip("<.5 1 2>") // Clip/legato
s("rhodes").loopAt(2) // Fit to N cycles
s("rhodes/2").fit() // Fit to event duration
// Cut groups (stop previous in group)
s("[oh hh]*4").cut(1)Synthesis
// Basic oscillators (sine, sawtooth, square, triangle)
note("c2 <eb2 <g2 g1>>".fast(2))
.sound("<sawtooth square triangle sine>")
// Default is triangle if only note() used
note("c2 e2 g2")
// Limiting harmonics (additive synthesis)
note("c2 <eb2 <g2 g1>>".fast(2))
.sound("sawtooth")
.n("<32 16 8 4>") // Number of harmonic partials
// Noise oscillators (white, pink, brown, crackle)
sound("<white pink brown>")
note("c3").noise("<0.1 0.25 0.5>")
s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(2))
// Vibrato
note("a e").vib("<.5 1 2 4 8 16>") // Frequency in Hz
note("a e").vib("<.5 1 2 4 8 16>:12") // With depth
note("a e").vib(4).vibmod("<.25 .5 1 2 12>") // Depth separately
// FM synthesis
note("c e g b g e")
.fm("<0 1 2 8 32>") // Modulation index
.fmh("<1 2 1.5 1.61>") // Harmonicity ratio
.fmattack("<0 .05 .1 .2>") // FM envelope attack
.fmdecay("<.01 .05 .1 .2>") // FM envelope decay
.fmsustain("<1 .75 .5 0>") // FM envelope sustain
.fmenv("<exp lin>") // Envelope curveSignals and Continuous Patterns
Signals are continuous patterns with infinite temporal resolution.
Basic Signals
// Unipolar (0 to 1)
sine, cosine, saw, tri, square, rand, perlin
// Bipolar (-1 to 1)
sine2, cosine2, saw2, tri2, square2, rand2
// Integer random
irand(n) // Random integers 0 to n-1Signal Transformations
// Range mapping
s("[bd sd]*2,hh*8").cutoff(sine.range(500,4000))
s("[bd sd]*2,hh*8").cutoff(sine.rangex(500,4000)) // Exponential
// Bipolar range
s("[bd sd]*2,hh*8").cutoff(sine2.range2(500,4000))
// Temporal modulation
note("<[c2 c3]*4 [bb1 bb2]*4>")
.sound("sawtooth")
.lpf(sine.range(100, 2000).slow(4))
// Perlin noise for smooth random values
s("bd sd,hh*4").cutoff(perlin.range(500,2000))
// Segment signal at discrete points
note("c e g b").lpf(tri.range(100, 5000).segment(16))Signal Usage Examples
// Complex filter modulation
note("[c eb g <f bb>](3,8,<0 1>)".sub(12))
.s("sawtooth/64")
.lpf(sine.range(300,2000).slow(16))
.lpa(0.005)
.lpd(perlin.range(.02,.2))
.lps(perlin.range(0,.5).slow(3))
.lpenv(perlin.range(1,8).slow(2))
// Generative melody
note(saw.range(0,15).segment(8).scale("C:minor"))
// Random cutoff sweep
s("bd*4,hh*8").cutoff(perlin.range(500, 8000))Pattern Alignment Strategies
When combining patterns with different structures, alignment strategies control how values interact:
// Default (.in) - right values applied INTO left structure
'0 [1 2] 3'.add('10 20') // '10 [11 12] 23'
// .out - left values applied OUT TO right structure
'0 1 2'.add.out('10 20')
// .mix - structures combined, events at intersections
'0 1 2'.add.mix('10 20')
// .squeeze - right cycles squeezed into left events
"0 1 2".add.squeeze("10 20")
// Equivalent: "[10 20] [11 21] [12 22]"
// .squeezeout - left cycles squeezed into right events
"0 1 2".add.squeezeout("10 20")
// Equivalent: "[10 11 12] [20 21 22]"
// .reset - right cycles truncated to fit left events
"0 1 2 3 4 5 6 7".add.reset("10 [20 30]")
// Equivalent: "10 11 12 13 20 21 30 31"
// .restart - like reset but from cycle 0
"0 1 2 3".add.restart("10 20")Practical Composition Examples
Layered Techno Pattern
samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
hh: ['hh27/000_hh27closedhh.wav']
}, 'github:tidalcycles/dirt-samples');
stack(
// Drums with random variation
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8")
.speed(perlin.range(.7,.9)),
// Bassline with octave jumps and detuning
"<a1 b1*2 a1(3,8) e2>"
.off(1/8, x => x.add(12).degradeBy(.5))
.add(perlin.range(0,.5))
.superimpose(add(.05))
.note()
.decay(.15).sustain(0)
.s('sawtooth')
.gain(.4)
.cutoff(sine.slow(7).range(300,5000)),
// Chord progression with voicings
"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand')
.superimpose(x => x.add(.04))
.add(perlin.range(0,.5))
.note()
.s('sawtooth')
.cutoff(1000).lpenv(3)
)Algorithmic Acid Pattern
note("[<g1 f1>/8](<3 5>,8)")
.clip(perlin.range(.15,1.5))
.release(.1)
.s("sawtooth")
.lpf(sine.range(400,800).slow(16))
.lpq(cosine.range(6,14).slow(3))
.lpenv(sine.mul(4).slow(4))
.lpd(.2).lpa(.02)
.ftype('24db')
.rarely(add(note(12)))
.room(.2).shape(.3)
.superimpose(x => x.add(note(12)).delay(.5).bpf(1000))Polyrhythmic Drums
stack(
s("bd(3,8)"),
s("sd(5,8,2)"),
s("hh*8").speed(perlin.range(.9,1.1)),
s("metal(7,16)").gain(.5)
).cpm(130)Generative Melody with Conditional Transformations
note("<0 2 4 6>*8")
.scale("C:minor")
.euclid(5,8)
.fast("<1 2 4>")
.every(4, rev)
.sometimes(add(12))
.off(1/8, x => x.add(7).degradeBy(.3))
.s("sawtooth")
.lpf(sine.range(300,2000).slow(8))
.room(.3)Phasing Pattern
note("<C D G A Bb D C A G D Bb A>*[6,6.1]")
.scale("C:major")
.s("sine")
.gain(.5)Complex Rhythmic Transformation
"0 1 2 3 4 3 2 1"
.inside(4, rev)
.euclid(3,8)
.iter(4)
.scale('C major')
.note()
.s("triangle")
.jux(rev)
.room(.5)Key Functional Programming Patterns
1. Immutability: Patterns never mutate; transformations return new patterns 2. Composition: Build complex patterns from simple functions via chaining 3. Currying: Functions return new functions for flexible application 4. Higher-order functions: Functions that accept/return functions (jux, off, every, etc.) 5. Pipelining: Chain operations fluently with dot notation 6. Pure functions: No side effects, same input → same output 7. Lazy evaluation: Patterns computed only when queried 8. Functor structure: Patterns form a functor with .fmap() semantics 9. Monadic operations: .bind() / .join() for pattern-of-patterns
Lambda Functions and Combinators
Arrow functions essential for transformations:
// Basic lambda
.off(1/8, x => x.add(7))
// Multiple transformations
.layer(
x => x.s("sawtooth").vib(4),
x => x.s("square").add(note(12))
)
// With index parameter
.echoWith(4, 1/8, (p, n) => p.add(n*2))
// Composition
const transform = x => x.fast(2).rev().add(7)
pattern.every(4, transform)Best Practices
1. Use lambda functions for inline transformations 2. Chain methods for readable pipelines 3. Compose reusable transformation functions 4. Leverage signals for continuous modulation 5. Combine patterns with stack/layer for polyphony 6. Apply probability for variation and evolution 7. Use alignment modes to control pattern interaction 8. Segment signals when discrete events needed 9. Pattern all parameters for dynamic variation 10. Test in REPL at https://strudel.cc/
Resources
- Documentation: https://strudel.cc/
- Workshop: https://strudel.cc/workshop/getting-started/
- REPL: https://strudel.cc/
- Function Reference: https://strudel.cc/workshop/recap/
- GitHub (archived): https://github.com/tidalcycles/strudel
- Codeberg (active): https://codeberg.org/uzu/strudel
- TypeScript Defs: https://github.com/mnvr/strudel-ts
- Discord: https://discord.com/invite/HGEdXmRkzT
#!/usr/bin/env python3
"""
Strudel URL encoder/decoder
Encodes Strudel code to base64 URL format or decodes URLs back to code.
"""
import base64
import sys
import urllib.parse
def encode_strudel(code: str) -> str:
"""
Encode Strudel code to base64 URL format.
Args:
code: The Strudel JavaScript code to encode
Returns:
Full Strudel URL with encoded code
"""
# Encode to UTF-8 bytes then base64
encoded_bytes = base64.b64encode(code.encode('utf-8'))
encoded_str = encoded_bytes.decode('utf-8')
# URL encode the base64 string
url_encoded = urllib.parse.quote(encoded_str, safe='')
return f"https://strudel.cc/#{url_encoded}"
def decode_strudel(url: str) -> str:
"""
Decode a Strudel URL back to code.
Args:
url: Full Strudel URL or just the base64 hash fragment
Returns:
Decoded Strudel code
"""
# Extract hash fragment if full URL provided
if '#' in url:
encoded_str = url.split('#', 1)[1]
else:
encoded_str = url
# URL decode first
url_decoded = urllib.parse.unquote(encoded_str)
# Base64 decode
decoded_bytes = base64.b64decode(url_decoded)
code = decoded_bytes.decode('utf-8')
return code
def main():
if len(sys.argv) < 3:
print("Usage:")
print(" Encode: python strudel_url.py encode <code>")
print(" Decode: python strudel_url.py decode <url>")
sys.exit(1)
command = sys.argv[1].lower()
if command == 'encode':
code = sys.argv[2]
url = encode_strudel(code)
print(url)
elif command == 'decode':
url = sys.argv[2]
code = decode_strudel(url)
print(code)
else:
print(f"Unknown command: {command}")
print("Use 'encode' or 'decode'")
sys.exit(1)
if __name__ == '__main__':
main()
Related skills
FAQ
How does the user run the pattern?
The user always wants to run the code in the browser, either by copy-pasting or via a base64-encoded strudel.cc URL.
What language is Strudel?
Strudel ports TidalCycles' pattern language to JavaScript using pure functional programming.