Strudel 기반 TUI 음악 작곡 도구 + MCP 서버. 터미널에서 라이브 코딩하거나, Claude Code 같은 AI 에이전트를 통해 음악을 만들 수 있다.
npm run build # tsc → dist/
npm run dev # tsc --watch
npm start # TUI 모드 (기본)
npm run tui # TUI 모드
npm run mcp # MCP 서버 (stdio)
npm run test:sound # 오디오 테스트모든 실행 커맨드에 --experimental-specifier-resolution=node 플래그 필요.
src/
├── cli.ts # 진입점: tui(기본) / serve / mcp 서브커맨드 라우팅
├── engine/ # 오디오 엔진 (순수 로직, UI 무관)
│ ├── audio.ts # node-web-audio-api → globalThis 폴리필 (반드시 먼저 import)
│ ├── player.ts # webaudioRepl 래퍼 — evaluate/start/stop/getVisualState
│ ├── tracks.ts # 멀티트랙 상태관리 — stack() 결합, 뮤트/솔로/게인
│ ├── mixer.ts # 마스터 게인 + 이펙트 프리셋 (clean/lofi/ambient/dark/bright/tape)
│ ├── samples.ts # 샘플 로드 — GitHub/로컬/URL, fetch 폴리필
│ ├── patterns.ts # 패턴 저장/불러오기 — ~/.strudel-console/patterns/*.strudel
│ ├── render.ts # WAV 렌더링 — OfflineAudioContext, 오실레이터 직접 스케줄링
│ ├── theory.ts # 음악 이론 분석 + 장르 프리셋 6종
│ └── index.ts # 배럴 export (모든 엔진 API)
├── mcp/
│ └── server.ts # MCP 서버 — 20개 도구 + 2개 리소스
├── tui/
│ ├── App.tsx # 루트 컴포넌트 — 키보드 라우팅, 모드 관리
│ ├── highlight.ts # Strudel 구문 강조 토크나이저
│ ├── components/
│ │ ├── Editor.tsx # 구문 강조 에디터 + 인라인 에러
│ │ ├── Visualizer.tsx # 피아노 롤 + Braille 웨이브폼 + 사이클 표시
│ │ ├── TrackList.tsx # 트랙 목록 (M/S/게인 표시)
│ │ ├── StatusBar.tsx # BPM, CPS, 사이클 위치
│ │ ├── HelpBar.tsx # 단축키 안내
│ │ └── Prompt.tsx # 인라인 입력 프롬프트 (저장/이름변경)
│ └── hooks/
│ ├── usePlayer.ts # 엔진 상태 폴링 + 트랙 조작
│ ├── useEditor.ts # 에디터 상태 (줄, 커서)
│ └── useHistory.ts # 명령어 히스토리
└── types/
└── strudel.d.ts # @strudel/* 타입 스텁
- audio.ts를 반드시 먼저 import — superdough가
globalThis.AudioContext를 사용하므로 player.ts보다 먼저 폴리필해야 함 - 모듈 레벨 상태 —
repl,tracks[],masterGainValue등 엔진 상태는 모듈 스코프에 저장 - 콜백 기반 재컴파일 —
setRecompileCallback()으로 tracks → player 연결. 트랙 변경 시 자동으로evaluate()호출 - 100ms 폴링 — TUI의 usePlayer가 100ms 간격으로 엔진 상태를 폴링해서 Visualizer 업데이트
[TUI 입력 / MCP 호출]
↓
tracks.ts — 트랙 코드 업데이트
↓
compileTracksCode() — solo/mute 반영, stack() 결합
↓
applyMasterGain() — 마스터 게인 적용
↓
player.evaluate(compiledCode) — REPL에서 평가
↓
superdough → node-web-audio-api → 소리 출력
- MCP (
mcp/server.ts): stdio 트랜스포트, 도구마다ensureInitialized()호출, 상태 없는 요청/응답 - TUI (
tui/App.tsx): Ink/React 컴포넌트, Tab 프리픽스 단축키, 폴링 기반 렌더링
둘 다 같은 engine/ 모듈을 공유하지만 동시에 실행되지는 않음.
- ESM
.js확장자 필수:import "./audio.js",from "../engine/index.js" - 타입 전용 임포트:
import type { Track } from "./tracks.js" - 동적 임포트로 지연 로딩:
const { evaluateToPattern } = await import("../engine/index.js")
- 파일: camelCase (
usePlayer.ts) / PascalCase 컴포넌트 (Editor.tsx) - 함수: camelCase (
initPlayer,compileTracksCode) - 타입/인터페이스: PascalCase (
Track,TracksSnapshot,PlayerState) - 컴포넌트: PascalCase (
Editor,Visualizer,TrackList)
- MCP 도구: try-catch →
{ content: [...], isError: true }반환 - TUI: 훅에서 catch → 에디터 하단에 인라인 에러 표시
- 엔진: 초기화 안 됐으면
!repl체크 후 early throw
- 엔진: 모듈 스코프 변수 (싱글톤 패턴)
- TUI: React hooks (
useState,useCallback,useRef) - 트랙 ID:
t1,t2, ... 순차 생성
- @strudel v1.1.0 고정 — 1.2.x는 @kabelsalat ESM 호환 문제 있음
- superdough
initAudio()—typeof window === 'undefined'이면 early return하므로 Node.js에서는 수동ac.resume()필요 - WAV 렌더링 — superdough 우회, OfflineAudioContext에 오실레이터 직접 스케줄링. 퍼커시브 사운드는 시뮬레이션 (bd=사인 피치다운, sd=트라이앵글, hh=고주파 사각파)
- 샘플 fetch —
file://URL 지원을 위해 samples.ts에서 fetch 패치. 실제 WAV/MP3는 재생 시 lazy fetch (superdough 캐시) - 테스트 프레임워크 없음 —
test-sound.ts,test-raw-audio.ts로 수동 검증
| 경로 | 용도 |
|---|---|
~/.strudel-console/patterns/ |
저장된 패턴 (.strudel JSON) |
~/.strudel-console/renders/ |
렌더링된 WAV 파일 |
- Strudel 문서: https://strudel.cc
- MCP 스펙: README.md의 "MCP 서버 스펙" 섹션
- 음악 이론 가이드:
engine/theory.ts하단MUSIC_THEORY_GUIDE