About

Strudel Is Great for Beat Sketches, Weak for Finished Tracks — and Lazy Importing It Silently Drops Effects

Notes from embedding it in an app, plus the audio init trap that waits for "the next mousedown"

Strudel (a live-coding tool for making music with code in the browser) is the fastest thing I've used for sketching a beat on the spot. It's weak at finishing a song or shipping inside a product, though. And if you lazy-import it into an app, effects can drop out with no error. After this you'll know where Strudel fits, where it doesn't, and what to avoid when embedding it.

Why I tried it

I saw a Threads video of someone live-coding a 140 BPM beat in Strudel. To try it myself I added a Strudel page to a demo app, then went further and built a learning app around it.

s("bd(3,8), hh*8, ~ sd ~ sd").shape(0.4)

That one line spreads 3 kicks evenly over 8 steps, plays 8 hi-hats, puts a snare on beats 2 and 4, and adds a bit of distortion. Sound, timing math and scheduling all fit in one line.

Strong spots, weak spots

The strong side was obvious. No install, and the code lives in the URL, so one link shares it. A few characters like bd(3,8), <a b>, [x x] produce complex rhythms. You can swap code without stopping playback. It comes from the TidalCycles family, which was designed for live performance.

The weak side comes from being cycle (loop) based. Laying out a linear song, intro → verse → chorus, felt awkward, and long melodies or chord progressions turn into long lists of notes. I haven't properly tested the song-structure features, recording/export, or mobile behavior yet, so this part is a hands-on impression.

Facts I did check while embedding it:

  • @strudel/web 1.3.0, license AGPL-3.0-or-later (as listed on npm). I'm not going to state what that requires when bundling it into a public service. If you plan to, check a proper license explainer

  • The Strudel chunk was 659KB in the production build (split out with dynamic import)

  • Drum samples are fetched from raw.githubusercontent.com at play time. No offline use, and the first play is slow

You want Pick
Beat sketches, rhythm experiments, live sets Strudel
Finished songs, mixing and mastering A DAW (Ableton, Logic, etc.)
Sound effects or interactive music in an app Tone.js or raw Web Audio

The trap: lazy importing silently drops effects

Because of the bundle size, I imported it on the first press of the play button. Playback worked, and there was no error on screen. The console, though, kept printing this roughly once a second.

[getTrigger] error: Failed to construct 'AudioWorkletNode': AudioWorkletNode cannot be created:
AudioWorklet does not have a valid AudioWorkletGlobalScope. Load a script via audioWorklet.addModule() first.

initStrudel() calls initAudioOnFirstClick() internally. Following the source (superdough), it does exactly what the name says: it adds a mousedown listener on document and loads the AudioWorklet on the next mousedown.

Before: import on first click mousedownno listener yet import() + initlistener added now evaluate()samples play shape: worklet error, ~1/s next clickworklet loads After: fire the missed mousedown yourself mousedown import() + init dispatchEvent(mousedown)AudioWorklets loaded evaluate(): all effects

In order: when the user presses the play button (mousedown), Strudel isn't loaded yet. The click imports it and calls initStrudel(), and only then is the listener attached. That mousedown is already gone. Samples play, but nodes that need an AudioWorklet, like shape, fail to construct every time, so just that sound goes missing. The next click anywhere loads it. That's why it looked like a "works sometimes" bug.

The fix is to fire the missed mousedown yourself.

const strudel = await import('@strudel/web')
const ready = strudel.initStrudel()
document.dispatchEvent(new MouseEvent('mousedown'))   // the click already happened, fire it now
await Promise.all([ready, strudel.initAudioOnFirstClick()])
await strudel.evaluate('s("bd*4").shape(0.4)')

In local Chrome (2026-09-19), before the fix the getTrigger error kept coming and there was no [superdough] AudioWorklets loaded log. After the fix, AudioWorklets loaded shows on the first play and getTrigger errors drop to zero.

One more thing confused me. The Vite dev server reloads the page once on the first dynamic import because of dependency optimization. So there was a second reason the first click seemed to "not take."

I haven't worked out why audio comes alive even though an event from dispatchEvent has isTrusted = false. My guess is that we're still inside a click handler, so user activation is still there. I also haven't listed which functions besides shape rely on AudioWorklet.

If you're stuck wiring a music or audio library into a web service, you can ask negabaro. We stepped on this trap ourselves while building our app.

Key Points

1

Strudel is for loop sketches and live sets. Finish songs in a DAW; app sound effects go to Tone.js or Web Audio

2

Before embedding, check bundle size (about 659KB), the AGPL license and the network-loaded samples

3

initStrudel loads the AudioWorklet on "the next mousedown"

4

If you imported after the click, fire document.dispatchEvent(new MouseEvent("mousedown")) yourself

Use Cases

Adding live-coded music to demos or learning pages Reviewing setups that load a large audio library on click
← Test Audio With AnalyserNode, Not the Screen — Why All 17 Demos Looked Silent in a Background Tab "100% Local" Is About Your Media Files — The Price of Running Whisper and ffmpeg as WASM →