About

Strudel 은 비트 스케치엔 최고, 곡 완성엔 약하다 — 그리고 lazy import 하면 이펙트가 조용히 빠진다

앱에 붙여 써 본 감상과, 「다음 mousedown」 을 기다리는 오디오 초기화 함정

Strudel(브라우저에서 코드로 음악을 짜는 라이브 코딩 도구)은 비트를 즉석에서 스케치하는 데는 지금까지 써 본 것 중 가장 빠르다. 대신 곡을 끝까지 완성하거나 서비스에 넣는 데는 약하다. 그리고 앱에 넣을 때 lazy import 를 하면 이펙트가 에러 없이 빠지는 함정이 있다. 이 글을 읽으면 Strudel 을 어디에 쓰고 어디엔 안 쓸지, 앱에 붙일 때 무엇을 피할지 알 수 있다.

왜 써 봤나

Threads 에서 누가 Strudel 로 140 BPM 비트를 라이브 코딩하는 영상을 봤다. 직접 해 보려고 데모 앱에 Strudel 페이지를 붙이고, 내친김에 학습용 앱까지 만들었다.

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

이 한 줄이 킥을 8칸에 3번 고르게 흩고, 하이햇 8번, 2 · 4 박에 스네어를 깔고, 소리를 살짝 찌그러뜨린다. 소리 만들기 + 박자 계산 + 예약이 한 줄에 다 들어 있다.

써 보니 강한 곳, 약한 곳

강한 곳은 분명했다. 설치가 없고, 코드가 URL 에 담겨서 링크 하나로 공유된다. bd(3,8), <a b>, [x x] 같은 몇 글자로 복잡한 리듬이 나온다. 연주를 끊지 않고 코드를 바꿔 끼울 수 있다. 라이브 공연용으로 설계된 TidalCycles 계열이라 그렇다.

약한 곳은 사이클(반복) 중심이라는 데서 온다. 인트로 → 벌스 → 코러스 같은 선형 곡 구조를 짜기가 불편했고, 긴 멜로디나 화성은 음을 늘어놓는 수밖에 없어서 장황해졌다. 다만 곡 구조용 기능이나 녹음 · 내보내기, 모바일 동작은 아직 제대로 시험하지 못했다. 여기까지는 써 본 감각이다.

앱에 넣을 때 확인한 사실은 이렇다.

  • @strudel/web 1.3.0, 라이선스는 AGPL-3.0-or-later (npm 표기). 공개 서비스에 번들할 때 무엇이 요구되는지는 여기서 단정하지 않는다. 쓸 거면 라이선스 해설을 따로 확인한다

  • 프로덕션 빌드에서 Strudel 청크가 659KB (dynamic import 로 떼어 냈을 때)

  • 드럼 샘플은 재생할 때 raw.githubusercontent.com 에서 받아 온다. 오프라인에선 안 되고 첫 재생이 늦다

하고 싶은 것 고를 것
비트 스케치, 리듬 실험, 라이브 공연 Strudel
곡 완성, 믹싱 · 마스터링 DAW (Ableton, Logic 등)
앱에 효과음 · 인터랙티브 음악 넣기 Tone.js 나 Web Audio 직접

함정 — lazy import 하면 이펙트가 조용히 빠진다

번들이 커서 「재생 버튼을 처음 누를 때 import」 로 만들었다. 재생은 됐고 화면엔 에러도 없었다. 그런데 콘솔에는 1초 남짓 간격으로 이게 계속 찍혔다.

[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() 은 안에서 initAudioOnFirstClick() 을 부른다. 소스(superdough)를 따라가 보니 이름 그대로였다. document 에 mousedown 리스너를 달고, 다음 mousedown 에서 AudioWorklet 을 로드한다.

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

순서로 보면 이렇다. 사용자가 재생 버튼을 mousedown 할 때는 Strudel 이 아직 없다. click 에서 import 하고 initStrudel() 을 부르면 그제서야 리스너가 붙는다. 그 mousedown 은 이미 지나갔다. 샘플 재생은 되지만 shape 처럼 AudioWorklet 을 쓰는 노드는 매번 생성에 실패해서 그 소리만 빠진다. 사용자가 아무 데나 한 번 더 클릭하면 그때 로드된다. 그래서 「가끔 되고 가끔 안 되는」 버그처럼 보였다.

고친 방법은 놓친 mousedown 을 직접 쏘는 것이다.

const strudel = await import('@strudel/web')
const ready = strudel.initStrudel()
document.dispatchEvent(new MouseEvent('mousedown'))   // 이미 클릭한 뒤라 지금 발화
await Promise.all([ready, strudel.initAudioOnFirstClick()])
await strudel.evaluate('s("bd*4").shape(0.4)')

로컬 Chrome 에서(2026-09-19) 고치기 전엔 getTrigger 에러가 계속 났고 [superdough] AudioWorklets loaded 로그가 없었다. 고친 뒤엔 첫 재생에서 AudioWorklets loaded 가 찍히고 getTrigger 에러는 0 이 됐다.

한 가지 더 헷갈린 게 있다. Vite 개발 서버에선 처음 dynamic import 할 때 의존성 최적화 때문에 페이지가 한 번 새로고침된다. 첫 클릭이 「안 먹은」 것처럼 보이는 원인이 하나 더 있었던 셈이다.

dispatchEvent 로 만든 이벤트는 isTrusted = false 인데도 오디오가 살아나는 이유는 아직 정리하지 못했다. 클릭 핸들러 안이라 사용자 활성화가 남아 있어서일 거라고 보고 있다. AudioWorklet 을 쓰는 함수가 shape 말고 무엇이 더 있는지도 목록으로 확인하지 않았다.

음악 · 오디오 라이브러리를 웹 서비스에 붙이다 막혔다면 negabaro 에 물어봐도 된다. 위 함정은 우리 앱에 넣으면서 직접 밟은 것이다.

핵심 포인트

1

Strudel 은 반복 패턴 스케치 · 라이브용. 곡 완성은 DAW, 앱 효과음은 Tone.js · Web Audio

2

앱 임베드 전에 번들 크기(약 659KB) · AGPL · 샘플의 네트워크 의존을 확인한다

3

initStrudel 은 「다음 mousedown」 에서 AudioWorklet 을 로드한다

4

클릭 뒤에 import 했다면 document.dispatchEvent(new MouseEvent("mousedown")) 로 직접 발화

사용 사례

사내 데모 · 학습 페이지에 라이브 코딩 음악 붙이기 큰 오디오 라이브러리를 클릭 시점에 불러오는 구조 점검
← 오디오 테스트는 화면 말고 AnalyserNode 로 — 백그라운드 탭에서 17개가 전부 「무음」 으로 나온 이유 「100% 로컬」 은 미디어 파일 얘기다 — Whisper 와 ffmpeg 를 WASM 으로 밀어 넣은 대가 →