Skip to content

[DONE] 정리 4단계 PR 17 — MIDI 받는 쪽 벡터 아홉 개가 가상 패널에서 돌고, check:midi-vectors 가 PR 마다 본다 - #87

Merged
patternflow-bot merged 6 commits into
devfrom
claude/dev-cleanup-4-pr17-xl61i0
Oct 15, 2026
Merged

patternflow-bot merged 6 commits into
devfrom
claude/dev-cleanup-4-pr17-xl61i0

Conversation

@patternflow-bot

@patternflow-bot patternflow-bot commented Oct 15, 2026 •

Copy link
Copy Markdown

dev / https://claude.ai/code/session_01UYMwmokDNM7DPd588Afarz

정리 4단계 계획서 표의 PR 17 이다. docs/midi-vectors/ 에 midi-spec.md 받는 표의 문장 하나씩을 담은 벡터 아홉 개를 두고, 시험용 RTP-MIDI 세션을 여는 _rtpmidi.ts 와 그것으로 벡터를 가상 패널에 다시 돌리는 check:midi-vectors 를 더했다. 머지되면 CI 가 PR 마다 MIDI 받는 쪽 계약을 가상 패널에 돌려 보고, 스펙 문장이 바뀌면 그 문장을 옮긴 벡터가 빨개진다.

좋아지는 것: 없음

할 일

한 일

_rtpmidi.ts 는 _vectors.ts 에 넘기는 midi Sender 다. 벡터마다 UDP 소켓 둘(제어, 데이터)을 빈 포트에 열고, open() 에서 패널의 midi 포트로 IN 을, 그다음 번호(데이터)로 같은 IN 을 보내 OK 를 받은 뒤 클럭 싱크(CK 0 → 1 → 2)를 한 번 한다. NO 가 오거나 2초 안에 답이 없으면 그 벡터는 그 까닭으로 실패한다. 걸음의 midi 는 채널 메시지 하나의 바이트([176, 24, 65])이고, RTP 버전 2, 페이로드 타입 97, 짧은 머리의 제 패킷에 실어 보낸다. 상태 바이트가 128~239 가 아니거나 데이터 수가 맞지 않으면 보내기 전에 던진다. 패널이 보낸 패킷은 RFC 6295 대로 읽어(짧은·긴 머리, Z 플래그, 델타 시간, 러닝 스테이터스) 메시지 하나를 heard 의 한 줄로 넣고, 채널 메시지가 아니면 그 바이트의 hex 를 넣어 벡터가 틀린 것이 보이게 한다. settle() 은 _osc.ts 처럼 데이터 소켓이 자기에게 보낸 표지가 돌아올 때까지 기다린다. close() 는 BY 를 보내고 소켓을 닫는다.

벡터 says (midi-spec.md) 보는 것
in-cc-absolute 0..127 maps to … (rounded; 127 = 1000). (39행) CC 20 이 0, 1, 64, 127 → params[0] 0, 8, 504, 1000 · CC 21, 22, 23 이 127, 64, 1 → [1000, 1000, 504, 8], 넷 다 paramActive
in-cc-hold The value holds until a hand turns that knob … (39행) CC 21 = 100 → 787 로 1초 잡힘 · 손 K1 은 그대로, 손 K2 에 풀림
in-cc-relative 64 = no motion, 65 = +1 detent, 63 = −1 (40행) 65, 63, 64 → delta [1, -1, 0, 0] · 63 둘과 65 → [2, -1, 0, -2]
in-cc-merged Merged with physical motion at 1× per step … (40행) 손 K3 +2 와 CC 26 = 65 셋 → delta 5
in-cc-keeps-hold Does not release an absolute hold. (40행) CC 20 = 127 을 1초 잡은 뒤 CC 24 = 63 → 1000 그대로, 잡힘 그대로
in-notes Note-on = press (edge) … note-off (or velocity 0) releases. (41행) 노트 60 온 → 누름 1, 1초 뒤에도 누름 1 에 held · 61~63 온(벨로시티 127, 1, 64) · 60 오프, 61 벨로시티 0, 62 오프(벨로시티 64) → held [F, F, F, T]
in-program-change Select pattern index n (0-based, the order on /patterns). (42행) PC 3 → /api/patterns 의 active 3 · PC 0 → 0
in-channel Messages on another channel are ignored unless the channel is 0. (44행) 채널 2, 3, 16 의 CC, 노트, PC 는 아무 일도 없고, 같은 CC 25 를 채널 1 로 보내면 delta 1
in-ignored Everything else is ignored. (44행) 노트 60 을 누른 채 CC 123, 120, 121, 19, 28, 노트 64, 59, 피치 벤드, 채널·폴리 애프터터치 → 노트는 held, 나머지 그대로

기다림은 20 ms 다. docs/contract-vectors.md 의 MIDI 예와 같고, PR 15 가 적은 대로 패널은 프레임 끝에 받은 것을 놓고 패턴은 다음 프레임에 받는다. in-cc-hold 와 in-cc-keeps-hold 는 손이 250 ms 안에 쓴 채널을 되찾지 않는 펌웨어 규칙(ABSOLUTE_RELEASE_GRACE_MS)을 REST 벡터처럼 1초로 넘긴다.

README 는 메시지를 적는 법, in-/out-, /api/midi 의 벡터가 rest-api-vectors/midi- 에 있다는 것, 세션을 스위트가 연다는 것, 20 ms 의 까닭, 스펙과 펌웨어가 다른 한 곳, 다시 돌리지 않는 것(MIDI over USB, 참가자 둘 제한, Bonjour, 재부팅 뒤 기억, 쇼, 채널 0, 63~65 밖의 상대값)을 적는다(D11).

증거

로컬, web/:

$ git diff --stat origin/dev...HEAD
 docs/midi-vectors/README.md              |  15 ++++
 docs/midi-vectors/in-cc-absolute.json    |  19 +++++
 docs/midi-vectors/in-cc-hold.json        |  13 ++++
 docs/midi-vectors/in-cc-keeps-hold.json  |  11 +++
 docs/midi-vectors/in-cc-merged.json      |  12 +++
 docs/midi-vectors/in-cc-relative.json    |  15 ++++
 docs/midi-vectors/in-channel.json        |  14 ++++
 docs/midi-vectors/in-ignored.json        |  20 +++++
 docs/midi-vectors/in-notes.json          |  18 +++++
 docs/midi-vectors/in-program-change.json |  13 ++++
 web/scripts/drift/_rtpmidi.ts            | 130 +++++++++++++++++++++++++++++++
 web/scripts/drift/midi-vectors.ts        |  10 +++
 12 files changed, 290 insertions(+)

$ npm run check:midi-vectors
docs/midi-vectors/in-cc-absolute.json: same
docs/midi-vectors/in-cc-hold.json: same
docs/midi-vectors/in-cc-keeps-hold.json: same
docs/midi-vectors/in-cc-merged.json: same
docs/midi-vectors/in-cc-relative.json: same
docs/midi-vectors/in-channel.json: same
docs/midi-vectors/in-ignored.json: same
docs/midi-vectors/in-notes.json: same
docs/midi-vectors/in-program-change.json: same
passed   midi-vectors

$ for i in $(seq 1 20); do npx tsx scripts/drift.ts midi-vectors; done
passed 20 failed 0 in 26666 ms          # tsx 를 띄우는 것까지 한 번에 1.3초

$ npm run typecheck                     # exit 0
$ npm run lint -- --max-warnings=0      # exit 0

처음 typecheck 는 0n 같은 BigInt 리터럴에서 TS2737 로 실패했다(web 의 target 이 ES2017). 숫자로 바꿔 ddbd9f6 에서 고쳤다.

깨뜨린 한 번: tools/virtual-panel/transports/midi.ts 를 고쳐 모든 채널을 받고, 절대값을 floor(v × 1000 / 128) 로 놓고 500 ms 뒤 놓고, 상대 CC 가 잡힘을 풀고 +쪽을 두 배로 하고, 벨로시티 0 노트 온을 누름으로 보고, PC 에 1 을 더하고, CC 120·121·123 에 노트를 놓게(펌웨어처럼) 했다. git checkout 으로 되돌렸다.

in-cc-absolute: drift     params[0] 8, 504, 1000 ← 7, 500, 992 · step 12 [1000, 1000, 504, 8] ← [992, 992, 500, 7]
in-cc-hold: drift         step 02 paramActive[1] true ← false, params[1] 787 ← 781
in-cc-keeps-hold: drift   step 04 paramActive[0] true ← false, params[0] 1000 ← 992
in-cc-merged: drift       deltas[2] 5 ← 8
in-cc-relative: drift     deltas[0] 1, 2 ← 2, 4
in-channel: drift         deltas[1] 0 ← 2, presses[0] 0 ← 1, pattern.index 0 ← 4, paramActive[0] false ← true
in-ignored: drift         held[0] true ← false
in-notes: drift           step 11 held[1] false ← true
in-program-change: drift  active 3, 0 ← 4, 1
failed   midi-vectors                   # exit 1

(벡터 ← 패널로 줄였다.) 스펙 문장을 바꾼 한 번: midi-spec.md 44행의 「Everything else is ignored.」를 「… is dropped.」로 바꾸면 in-ignored.json: drift, says docs/midi-spec.md ← says not in docs/midi-spec.md. 되돌렸다.

보내는 쪽(버릴 스크립트, 커밋하지 않음). 같은 프로세스에서 패널을 띄우고 이 Sender 로 세션을 연 뒤, 패널의 midi(sim).send() 와 다른 소켓에서 지어 보낸 패킷을 heard 가 어떻게 읽는지 봤다:

peers ["check:midi-vectors"]
panel sends                          [[176,24,70],[144,60,127],[192,2]]
Z, running status, two-octet delta   [[176,24,65],[176,25,63]]
long header, three                   [[176,20,1],[144,61,127],[192,5]]
sysex                                ["not a channel message: 03f07df7"]
not RTP                              ["not RTP-MIDI: 68656c6c6f"]
peers after BY []

PR 16 을 merge 한 뒤, 같은 Sender 로 out 을 보는 버릴 벡터 하나를 replay() 에 돌렸다(폴더째 지웠다). 손 K1 +1 → [176, 24, 65], /api/patterns/select?index=2 → [192, 2], 호스트의 CC 25 = 65 → 100 ms 동안 <absent>, status 의 midi.rtpPeers 1 과 rtpPeer "check:midi-vectors" 가 맞았다. 버튼 둘은 내가 채널 3 상태 바이트(146, 130)를 잘못 적어 drift 였고, 패널이 보낸 것은 [144, 62, 127] at 50 ms, [128, 62, 0] at 50 ms 로 채널 1 그대로였다. heard 가 패널이 보낸 것을 그 시각과 함께 보여 준다는 증거로 둔다.

$ npm run check:drift
passed bom, bom-schematic, changelog, editions, features, links-absolute, markers, midi-vectors, mqtt-vectors, osc-vectors, presets, rest-vectors, shared, virtual-panel · skipped roadmap(원래 레포를 못 읽는다, CI 는 strict)
$ python3 ../.github/scripts/check_links.py
all 730 relative links resolve
$ npm run check:human-lock
check:human-lock: no locked words changed
$ npm run check:voice
check:voice: nothing found              # 처음엔 _rtpmidi.ts 머리 주석의 넷 나열을 threes 로 잡아 `7004cd5` 에서 나눴다
$ npx tsx scripts/drift.ts markers
passed   markers
$ git fetch --tags && npm run doc-map           # CSV 는 git checkout 으로 되돌렸다
1116 links from 223 files → docs/investigations/2026-10-doc-map.csv
0 do not resolve
1366 Korean/English pairs → docs/investigations/2026-10-doc-parity.csv
7 en-short, 18 ko-short, 1 missing-ko, 1340 ok

dev 의 PR 20 과 PR 16 을 merge 한 bb6883e 에서 다시:

$ npm run typecheck && npm run lint -- --max-warnings=0     # 둘 다 exit 0
$ npm run check:drift
passed audio-vectors, bom, bom-schematic, changelog, editions, features, links-absolute, markers, midi-vectors, mqtt-vectors, osc-vectors, presets, rest-vectors, shared, virtual-panel · skipped roadmap
$ python3 ../.github/scripts/check_links.py
all 734 relative links resolve
$ npm run check:human-lock && npm run check:voice
check:human-lock: no locked words changed
check:voice: nothing found
$ for i in $(seq 1 10); do npx tsx scripts/drift.ts midi-vectors; done
passed 10 failed 0

GitHub, 이 PR: bb6883e 에서 「Drift checks / check」(116538099274), 「Docs point at files that exist / check」(116538083143), 「Web CI / build」(116538104103) 셋 다 success. PR 16 merge 전 5e8dee5 에서도 셋 다 success 였다(116533549112, 116533553251, 116533928042).

doc-map 의 새 줄은 다섯이고 다 닿는다. docs/midi-vectors/README.md 에서 docs/midi-spec.md, docs/contract-vectors.md, docs/rest-api-vectors/README.md, tools/virtual-panel/README.md, tools/rtpmidi-probe/rtpmidi_probe.py 로.

실행법

cd web && npm ci
npm run check:midi-vectors

벡터 하나를 고쳐 보려면 docs/midi-vectors/ 의 JSON 을 고치고 같은 명령을 돌린다. git 에 넣기 전에도 디스크에서 읽는다.

가정

  • 기준 브랜치는 dev(머리말). 표가 PR 17 에 적은 docs/midi-vectors/README.md, docs/midi-vectors/in-*.json, _rtpmidi.ts, midi-vectors.ts 만 더했다. CHANGELOG.md, docs/rfc-cleanup-progress.md, doc-map CSV, web/package.json(PR 2 가 check:midi-vectors 를 이미 넣었다), midi.ts 는 안 고쳤다. 라벨은 앞 PR 들처럼 달지 않았다.
  • 계획서가 적은 다섯(CC 20 의 0·1·64·127, CC 24 의 65·63, 노트 60~63, Program Change, 다른 채널)에 넷을 더했다. 39행의 잡힘, 40행의 「Does not release an absolute hold.」와 「Merged with physical motion」, 44행의 「Everything else is ignored.」다. 받는 표의 행마다 패널이 이미 하는 것이고, 마지막 것에서 「for an issue」 하나가 나온다.
  • README 는 out- 과 rest-api-vectors/midi- 까지 적었다. 표가 README 를 k17 에만 주어 k18 은 고칠 수 없기 때문이다.
  • 걸음의 메시지는 채널 메시지 하나다. 받는 표가 채널 메시지만 말하고, 「Everything else」는 채널 메시지로 충분히 보인다. 시스템 익스클루시브나 실시간 메시지를 보내는 벡터가 필요해지면 isMessage 를 넓힌다.
  • CC 24~27 은 63, 64, 65 만 보낸다. 40행의 「two's-complement offset」과 57행의 「binary offset」이 그 밖에서 갈린다(D8, k23).
  • 클럭 싱크와 BY 는 가상 패널엔 없어도 되지만 rtpmidi_probe.py 가 하고, 기기는 참가자를 둘까지만 받아 남겨 두지 않는다. 실제 기기를 걷는 일은 이 단계가 아니다(질문 29).
  • in-program-change, in-channel, in-ignored 는 새 패널이 Origin(번호 0)에서 시작한다고 본다. 패널의 시작 상태다.
  • 스크린샷은 없다. 화면이 바뀌는 PR 이 아니다.
  • firmware/ 와 web/src/ 를 건드리지 않아 build.sh all, check:ci, check:panels 는 §2.1(1) 대상이 아니다. 계획서대로 typecheck 와 이 PR 의 check, 깨뜨린 한 번을 적었다.

Changelog

Tools

  • MIDI has contract vectors. docs/midi-vectors/ holds nine, each one sentence of the host → device table in docs/midi-spec.md and the messages that show a panel doing it, from CC 20 at 127 landing on 1000 to a message on channel 2 the panel ignores. npm run check:midi-vectors in web/ replays them against the virtual panel built with the midi feature, over an RTP-MIDI session web/scripts/drift/_rtpmidi.ts opens for each, and CI runs it with the other drift checks.

for an issue

D9. 벡터는 스펙을 따르고, 펌웨어는 한 곳에서 다르다(PR 15 의 「k17, for an issue」와 같다).

  • in-ignored.json · midi-spec.md 44, "Everything else is ignored." · core_midi.h:296 이 CC 120, 121, 123 에 누르던 노트를 모두 놓는다(clearNotes()). 노트 60 을 누른 채 CC 123 을 보내면 기기에선 held 가 풀린다.

for another k

  • k18: import { midi } from "./_rtpmidi". heard 는 패널이 보낸 채널 메시지를 [status, ...data] 로, 러닝 스테이터스를 채워 한 줄씩 담는다. 채널 메시지가 아니면 hex 글이다. 세션은 첫 걸음 전에 열리므로 midi-out.ts 가 보내는 것이 처음부터 닿는다(위 버릴 out 벡터, 시각까지). README 는 out- 과 rest-api-vectors/midi- 를 이미 적었다. check:rest-vectors 는 midi Sender 를 가져오지 않아 rest-api-vectors/midi-*.json 은 http, hand, wait 만 쓸 수 있다. 세션이 있어야 보이는 것(rtpPeers, rtpPeer, rx)은 docs/midi-vectors/out-*.json 에 두어야 check:midi-vectors 가 세션을 열고 돌린다.
  • k22: docs/README.md 의 midi-spec.md 줄에 midi-vectors/ 를, pfst 줄처럼.
  • k23: midi-spec.md 40행이 CC 24~27 을 「two's-complement offset」이라 하고, 제 예와 57행은 binary offset 이다. 패널과 펌웨어(core_midi.h:287)는 값 − 64 로 읽는다. 벡터는 둘이 맞는 63, 64, 65 만 보낸다.

Upstream

  • What: Add docs/midi-vectors/, nine vectors of docs/midi-spec.md's host → device table and a README, and check:midi-vectors, which replays them against the virtual panel through an RTP-MIDI initiator in web/scripts/drift/_rtpmidi.ts.
  • Checks: npm run typecheck, npm run lint and npm run check:drift (midi-vectors included) pass; firmware/ and web/src/ are untouched.
  • Docs map: five new rows from docs/midi-vectors/README.md, all resolving.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UYMwmokDNM7DPd588Afarz

k17 of the step 4 plan: nine vectors of midi-spec.md's host → device table, replayed against the virtual panel through an RTP-MIDI initiator of their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UYMwmokDNM7DPd588Afarz
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UYMwmokDNM7DPd588Afarz
@patternflow-bot patternflow-bot changed the title [WIP] 정리 4단계 PR 17 — MIDI 받는 쪽 벡터가 가상 패널에서 돌고, check:midi-vectors 가 PR 마다 본다 [WIP] 정리 4단계 PR 17 — MIDI 받는 쪽 벡터 아홉 개가 가상 패널에서 돌고, check:midi-vectors 가 PR 마다 본다 Oct 15, 2026
@patternflow-bot patternflow-bot changed the title [WIP] 정리 4단계 PR 17 — MIDI 받는 쪽 벡터 아홉 개가 가상 패널에서 돌고, check:midi-vectors 가 PR 마다 본다 [DONE] 정리 4단계 PR 17 — MIDI 받는 쪽 벡터 아홉 개가 가상 패널에서 돌고, check:midi-vectors 가 PR 마다 본다 Oct 15, 2026
@patternflow-bot
patternflow-bot marked this pull request as ready for review October 15, 2026 16:31
@patternflow-bot
patternflow-bot merged commit e912efa into dev Oct 15, 2026
3 checks passed
@patternflow-bot
patternflow-bot deleted the claude/dev-cleanup-4-pr17-xl61i0 branch October 15, 2026 16:33
engmung pushed a commit to engmung/Patternflow that referenced this pull request Oct 15, 2026
engmung pushed a commit to engmung/Patternflow that referenced this pull request Oct 15, 2026
Patternflow-lab#11
Patternflow-lab#12
Patternflow-lab#13
Patternflow-lab#15
Patternflow-lab#17
Patternflow-lab#14
Patternflow-lab#16
Patternflow-lab#19
Patternflow-lab#20
Patternflow-lab#18
Patternflow-lab#21
Patternflow-lab#23
Patternflow-lab#24
Patternflow-lab#25
Patternflow-lab#26
Patternflow-lab#28
Patternflow-lab#29
Patternflow-lab#30
Patternflow-lab#27
Patternflow-lab#31
Patternflow-lab#32
Patternflow-lab#34
Patternflow-lab#35
Patternflow-lab#33
Patternflow-lab#36
Patternflow-lab#37
Patternflow-lab#38
Patternflow-lab#39
Patternflow-lab#40
Patternflow-lab#41
Patternflow-lab#42
Patternflow-lab#43
Patternflow-lab#46
Patternflow-lab#44
Patternflow-lab#47
Patternflow-lab#48
Patternflow-lab#49
Patternflow-lab#45
Patternflow-lab#50
Patternflow-lab#51
Patternflow-lab#52
Patternflow-lab#54
Patternflow-lab#57
Patternflow-lab#55
Patternflow-lab#53
Patternflow-lab#56
Patternflow-lab#59
Patternflow-lab#58
Patternflow-lab#60
Patternflow-lab#61
Patternflow-lab#62
Patternflow-lab#63
Patternflow-lab#64
Patternflow-lab#65
Patternflow-lab#66
Patternflow-lab#67
Patternflow-lab#68
Patternflow-lab#69
Patternflow-lab#70
Patternflow-lab#72
Patternflow-lab#73
Patternflow-lab#71
Patternflow-lab#75
Patternflow-lab#74
Patternflow-lab#77
Patternflow-lab#78
Patternflow-lab#76
Patternflow-lab#79
Patternflow-lab#80
Patternflow-lab#81
Patternflow-lab#82
Patternflow-lab#84
Patternflow-lab#83
Patternflow-lab#85
Patternflow-lab#86
Patternflow-lab#87
Patternflow-lab#88
Patternflow-lab#90
Patternflow-lab#91
Patternflow-lab#89
Patternflow-lab#92
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants