본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

Web MIDI: 또 다른 sink지, 또 다른 scheduler가 아냐

~12 min · web-midi, one-scheduler, sinks

Level 0식은 재
0 XP0/33 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"출력을 더할 때는 leaf에서 갈라. scheduler를 절대 fork하지 마."

기능: 진짜 신스를 라이브로

v1 엔진은 solo와 코드를 Web MIDI로 내보낼 수 있어. macOS IAC 버스로 연결한 소프트웨어·하드웨어 신스를 구동해서 인앱 오디오와 나란히 박자에 맞춰 울려. 그 자체로도 만족스러운 기능이지만, 더 중요한 건 이 출력을 어떻게 추가했느냐야. 틀린 방식이 너무 유혹적이거든.

유혹적인 실수: scheduler를 fork해

MIDI 출력을 더하는 순진한 방식은 MIDI 전용 scheduler를 하나 더 만드는 거야. 자기 루프와 자기 타이밍을 가진 scheduler가 오디오 scheduler 옆에서 돌아. 이제 결정자가 둘이고 두 출력은 지난 레슨의 독립 voice처럼 조금씩 갈라져. jitter 문제를 한 층 위에서 다시 만든 셈이야. 클럭 하나로 합쳤던 수정을 결정자 둘로 나누면서 조용히 풀어 버린 거지.

맞는 방식: scheduler 하나, sink 여럿

단일 scheduler가 계속 언제를 정하게 둬. 출력은 어떻게 방출할지만 달라져. Web Audio sink는 샘플을 재생하고 Web MIDI sink는 note-on 바이트를 보내. MIDI를 추가한 건 scheduler가 아니라 sink를 하나 더한 거야. Track 4의 API-first와 단일 진실원 원칙이 playback 레이어로 내려온 모양이지. 결정자는 하나고 소비자는 여럿이며, 실제 방출이 달라지는 leaf에서만 갈라져. 인앱 오디오를 구동하는 같은 scheduler가 하드웨어 신스도 박자에 맞게 구동한다는 사실이 이 구조를 증명해.

Code

scheduler가 아니라 sink에서 갈라·javascript
// ONE scheduler가 WHEN을 정해 (지난 레슨의 anchored 클럭에서).
// 출력은 HOW만 달라. scheduler가 아니라 sink야.
function emit(note, audioTime) {
  if (audioOut.enabled) {
    playSample(note, audioTime);                  // Web Audio sink
  }
  if (midiOut.enabled) {
    midiOut.send(noteOnBytes(note), toMidiTime(audioTime));  // Web MIDI sink
  }
}

// MIDI 추가는 scheduler를 fork한 게 아니라 sink를 더한 거야.
// scheduler 하나, sink 여럿: playback 레이어의 API-first 규칙.

External links

Exercise

출력을 만드는 시스템(렌더러, 알림기, exporter)을 가져와 두 번째 출력 채널을 더한다고 상상해. 무엇/언제를 정하는 로직을 복제할래, 아니면 마지막 방출에서만 갈래? 새 출력을 위해 파이프라인 전체를 복사하려는 본능이 들면 그게 scheduler를 fork하는 거야. 출력이 실제로 달라지는 단 하나의 leaf를 찾아서 거기서만 갈라.
Hint
결정 로직(뭘, 언제, 어느 음)은 한 번 살아야 해. 방출(스피커로, MIDI로, 파일로, webhook으로)만 출력별이야. 방출만 복사하고, 그 위 전부는 공유해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 피파warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.