Skip to content

Instantly share code, notes, and snippets.

@therebelrobot
Last active December 4, 2025 18:37
Show Gist options
  • Select an option

  • Save therebelrobot/fe161e21a8bffc5325891d7ad62ec49b to your computer and use it in GitHub Desktop.

Select an option

Save therebelrobot/fe161e21a8bffc5325891d7ad62ec49b to your computer and use it in GitHub Desktop.
Strudel Learning Course

🎢 Strudel Self-Study Workshop

A hands-on introduction to live-coded music in the browser


πŸ“– Overview

Strudel is a JavaScript-derived live-coding environment that lets you make music directly in a browser REPL. Each snippet you evaluate becomes part of a real-time musical texture. This course takes you from your first drum hit to expressive synthesis and full performances.

You’ll type every example into the REPL yourself. At the end of each module you’ll have short β€œfree-play” challenges to cement the ideas.


🧰 Requirements

  • The Strudel REPL or a local install
  • Headphones or speakers
  • Curiosity and willingness to experiment

πŸͺ© Module 1 β€” The Pulse: Time and Pattern

🎯 Goal

Understand how Strudel represents rhythm and repetition through pattern notation.

πŸ’‘ Concepts

Concept Meaning Example
s() choose a sound sample s("bd")
* repeat s("bd*4")
< > alternate patterns s("<bd sd>")
~ silence s("bd ~ sd ~")
, layer simultaneous sounds s("bd, hh*4")

🧠 Try This

s("bd hh bd hh")
s("bd*2 hh*2").gain(0.8)
s("<bd sd> hh <hh hh*2>")

πŸŽ›οΈ Experiment

  1. Add a second voice:

    s("bd ~ bd ~")
    s("~ hh ~ hh").gain(0.7)
  2. Slow and speed patterns:

    s("bd sd hh hh").slow(2)
    s("bd sd hh hh").fast(2)

🧩 Challenge

Create a two-bar groove that uses < > to alternate variations, includes one silence ~ per bar, and layers at least two instruments.


πŸ”Š Module 2 β€” Sound and Structure: Samples & Slicing

🎯 Goal

Control how Strudel plays and transforms audio samples.

πŸ’‘ Concepts

Function Description
bank(name) selects a folder of samples
n() selects index within a bank
.begin() / .end() play fraction of the sample
.loop(1) loop playback continuously

🧠 Try This

s("bd sd [~ bd] sd, hh*6")
  .bank("RolandTR909")
  .n("<0 1>")
  .gain(0.7)
s("rave").begin("<0 .125 .25 .375 .5 .625 .75 .875>").fast(2)

πŸŽ›οΈ Experiment

  • Vary .begin() and .end() to β€œremix” a breakbeat:

    s("loopAmen").begin("<0 .25 .5 .75>").end("<.25 .5 .75 1>")
  • Loop continuously:

    s("casio").loop(1).gain(0.6)

🧩 Challenge

Using a single loop sample, make a breakbeat that feels like a new groove by changing .begin() values each bar.


🎡 Module 3 β€” Melody and Harmony

🎯 Goal

Use pitch, scales, and simple harmony.

πŸ’‘ Concepts

Function Use
note() define melodic pattern
.s() choose instrument
.fast() / .slow() control rhythmic density

🧠 Try This

note("c e g b").s("saw").gain(0.7)
note("c4 e4 g4 b4").s("square").slow(2)
note("c3 e3 g3 b3").s("square").fast(2).gain(0.5)
note("60 64 67 71").s("piano").gain(0.8)

πŸŽ›οΈ Experiment

  • Layer melodic voices:

    note("c e g").s("saw")
    note("c e g").s("pulse").slow(2)
  • Mix .fast() and .slow() to create counterpoint.

🧩 Challenge

Compose a two-layer arpeggio where one voice plays double speed and the other half speed, harmonized in a different octave.


Excellent β€” here’s Part 2 of the Strudel self-study workshop in Markdown, continuing seamlessly from Module 3.


🎚️ Module 4 β€” Shape and Feel (ADSR Envelopes & Dynamics)

🎯 Goal

Learn to shape loudness and articulation using amplitude envelopes and dynamic controls.

πŸ’‘ Concepts

Function Purpose
.attack() time before peak volume
.decay() time to fall from peak to sustain level
.sustain() steady volume during note
.release() fade-out time
.gain() exponential loudness scaler
.velocity() per-event intensity
.postgain() final volume trim after FX

🧠 Try This

note("c3 e3 g3 b2")
  .s("saw")
  .attack("<0 .05 .1 .2>")
  .decay("<.1 .2 .3 .4>")
  .sustain("<0 .25 .5 .75 1>")
  .release("<.2 .4 .6 1>")
  .gain(0.9)

πŸŽ›οΈ Experiment

  • Create per-note accents:

    s("hh*8").gain(".4!2 1 .4!2 1 .4 1").velocity(".4 1").fast(2)
  • Compare .gain() vs .velocity() β€” gain affects amplitude after synthesis, velocity affects instrument expression.

🧩 Challenge

Write a four-chord progression whose ADSR parameters evolve slowly across eight bars. Try giving each chord distinct attack/decay/release values to create breathing motion.


🌊 Module 5 β€” Filtering and Motion

🎯 Goal

Sculpt timbre with filters and modulation.

πŸ’‘ Concepts

Function Description
.bpf(freq) band-pass filter center frequency
.bpq(q) resonance (bandwidth sharpness)
.fm(index) FM modulation depth
.fmh(ratio) harmonicity ratio
.fmattack(), .fmdecay(), .fmsustain(), .fmenv() envelope controls for FM
.wt(), .warp(), .warpmode() wavetable morphing
.wtrate(), .wtdepth(), .wtskew() LFO modulation of wavetable position

🧠 Try This

note("c e g b").s("sine")
  .bpf("<500 1000 2000 4000>")
  .bpq("<0 .5 1 2>")
note("c e g b g e").s("sine")
  .fm("<0 1 2 8 16>")
  .fmh("<1 2 1.5 1.61>")

Add envelopes for expressiveness:

note("c e g b g e").s("sine")
  .fm(4)
  .fmattack("<0 .05 .1 .2>")
  .fmdecay("<.01 .05 .1 .2>")
  .fmsustain("<1 .75 .5 0>")
  .fmenv("<exp lin>")

πŸŽ›οΈ Experiment

  1. Sweep filters by patterning frequency values.

  2. Combine FM and filter modulation for metallic textures.

  3. Try a wavetable sweep:

    s("basique").bank("wt_digital")
      .note("F1")
      .wt("0 0.25 0.5 0.75 1")
      .warp("<0 .25 .5 .75 1>")
      .warpmode("<asym bendp spin logistic sync wormhole>*2")

🧩 Challenge

Design a bass patch that evolves between two timbres. Hint: alternate .warpmode("fold") and "spin" while sweeping .warp().


🌫️ Module 6 β€” Space and Texture (Effects & Atmosphere)

🎯 Goal

Add depth, polish, and motion with effects.

πŸ’‘ Concepts

Effect Control Notes
.reverb(amount) 0 – 1 spaciousness
.chorus(depth) 0 – 1 detuned shimmer
.compressor(settings) "threshold:ratio:knee:attack:release" dynamics
.distort(amount) > 0 harmonic saturation
.postgain() compensates output level

🧠 Try This

note("d f a c").s("sawtooth")
  .chorus(0.5)
  .reverb("<0 .2 .4 .6>")
  .gain(0.7)
s("bd sd [~ bd] sd, hh*8")
  .compressor("-20:20:10:.002:.02")
  .postgain(1.3)

πŸŽ›οΈ Experiment

  • Animate reverb send:

    note("a f c d").s("pad").reverb("<0 .3 .8 .3>").gain(0.8)
  • Stack multiple effects and adjust .postgain() to avoid clipping.

🧩 Challenge

Design an evolving ambient pad: use modulation and reverb so rhythm melts into texture. Layer at least two oscillators with different chorus depths for stereo width.


Excellent β€” here’s Part 3, completing the self-study workshop Markdown with Modules 7–10 and the closing sections.


πŸŒ€ Module 7 β€” Pattern Alchemy (Transformation & Variation)

🎯 Goal

Learn how to transform, remix, and randomize patterns so your music constantly evolves.

πŸ’‘ Concepts

Function Purpose
.rev() Reverse a pattern
.every(n, fn) Apply fn every n cycles
.off(offset, fn) Delay then apply fn
.stack(n) Duplicate layer with phase offsets
.add() / .combine() Merge patterns together

🧠 Try This

// reverse every third cycle
s("bd sd hh hh").every(3, p => p.rev())
// accent hats slightly off the grid
s("hh*8").off(0.125, p => p.gain(0.5))
// triple-layer hi-hat texture
s("hh*8").stack(3)

πŸŽ›οΈ Experiment

  1. Add .rev() to one instrument while another keeps time.
  2. Use .every() to swap drum patterns each bar.
  3. Combine these techniques with .off() to make micro-variations.

🧩 Challenge

Build a groove that never quite repeats: alternate fills with .every(4, p => p.rev()), offset the snare by 0.25, and layer a polyrhythmic hi-hat using .fast(3) against .slow(2).


🧰 Module 8 β€” Interactive Control (Sliders & Live Manipulation)

🎯 Goal

Make your music reactive β€” tweak parameters live from the REPL.

πŸ’‘ Concepts

Function Purpose
slider(value, min, max, step) Creates a UI control
.markcss(css) Visually mark events
.source(fn) Define a custom WebAudio node

🧠 Try This

// global mix control
const MIX = slider(0.6, 0, 1, 0.05)
note("c e g").s("sine").gain(MIX)
// live filter cutoff
const CUT = slider(1200, 200, 8000, 50)
note("c e g b").s("sine").bpf(CUT).bpq(1.2).gain(0.7)
// visually mark notes
note("c4 a3 f4 e4").s("sine").markcss('text-decoration:underline')
// custom WebAudio source
source(ctx => {
  const osc = ctx.createOscillator()
  osc.type = "sawtooth"
  const gain = ctx.createGain()
  gain.gain.value = 0.2
  osc.connect(gain)
  osc.start()
  return gain
})
.attack(0.01).decay(0.2).sustain(0.2).release(0.3)

πŸŽ›οΈ Experiment

  • Assign sliders to multiple parameters (e.g. filter + reverb).
  • Use different sliders to cross-fade between two instruments.
  • Mark active parts visually with .markcss() for performance clarity.

🧩 Challenge

Build a performance patch with two interactive sliders β€” one for cutoff, one for mix. Perform a slow evolution by hand over several cycles.


🎼 Module 9 β€” Design and Compose (Layering & Structure)

🎯 Goal

Combine everything into a cohesive piece.

πŸ’‘ Concepts

  • Each note() or s() line is a voice.
  • Timing is cyclic β€” use .slow() and .fast() for phrasing.
  • Think in layers rather than tracks.

🧠 Try This

// Bass
note("c2(3,8) g1(3,8) c2(5,8)").s("sine").gain(0.8)

// Chords
note("<c3e3g3 a2c3e3 f2a2c3 g2b2d3>").s("saw").slow(2)
  .attack(0.02).decay(0.3).sustain(0.5).release(0.4).gain(0.6)

// Drums
s("bd sd [~ bd] sd, hh*8").gain(0.9)

πŸŽ›οΈ Experiment

  1. Add .bpf() to chords for movement.
  2. Apply .compressor() to the drums.
  3. Use .off() to stagger the bass slightly behind the beat.

🧩 Challenge

Compose a two-minute section that introduces, builds, and releases energy. Add or remove layers live to create arrangement dynamics.


πŸ’₯ Module 10 β€” The Stage (Performance & Improvisation)

🎯 Goal

Perform confidently in real time.

πŸ’‘ Concepts

Tip Meaning
Re-evaluate lines Start/stop parts live
Use clearAll() Silence everything instantly
Keep gain ≀ 1 Avoid clipping
Structure in cycles Plan phrases beforehand

🧠 Try This

// Base groove
s("bd ~ bd ~"), s("~ hh ~ hh").gain(0.6)

// Add snare later
s("~ sd ~ sd").gain(0.8)

// Bring in bass
note("c2(3,8) g1(3,8) c2(5,8)").s("sine").fm(2).fmh(1.5).gain(0.8)

// Pad swell
note("<c3e3g3 a2c3e3 f2a2c3 g2b2d3>").s("saw")
  .chorus(0.4).reverb(0.5)
  .attack(0.01).decay(0.3).sustain(0.5).release(0.6).slow(2)

πŸŽ›οΈ Experiment

  • Practice muting/unmuting parts with line evaluation.
  • Change pattern parameters live without stopping the clock.
  • Gradually introduce polyrhythms for a finale.

🧩 Challenge

Perform a 3-minute improvised jam. Introduce ideas slowly, develop texture, and resolve into silence. Use everything you’ve learned: filters, FM, effects, ADSR, and pattern transformation.


πŸ“˜ Appendix β€” Quick Reference Sheet

Core Functions

Category Functions
Sound s(), bank(), n(), begin(), end(), loop()
Pitch note(), scale(), chord()
Timing fast(), slow(), off(), stack(), rev()
Dynamics gain(), velocity(), attack(), decay(), sustain(), release(), postgain()
Modulation fm(), fmh(), wt(), warp(), warpmode()
Filters bpf(), bpq()
FX chorus(), reverb(), compressor(), distort()
Interaction slider(), markcss(), source()

Pattern Tricks

Pattern Code Effect
"bd*4" repeat kick 4 times
"<bd sd>" alternate each cycle
"[bd sd]" play both in sequence within one cycle
"bd, hh" layer simultaneous patterns
"~" silence rest

🌟 Closing Thoughts

You’ve now touched every major part of Strudel: pattern syntax, sample banks, synthesis, filters, FX, and interaction. Keep your REPL open and treat it as an instrument β€” not a file. The best way to improve is to play with time and texture every day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment