PySwitch 위에서 도는 데이터 기반 멀티 모드 매핑 확장입니다. 스위치 동작을 파이썬 코드 대신 JSON 프로필로 정의하고, 웹 에디터에서 편집해서 MIDI Captain에 바로 저장합니다.
- 지원 기기: MIDI Captain Nano 4, Mini 6, 10 (PySwitch가 지원하는 기기 전부)
- 기본 프로필: Quad Cortex mini (
qc), Anagram (anagram) - 에디터:
multimode/web(GitHub Pages로 배포)
content/ 업스트림 PySwitch (boot.py, config.py, communication.py, fonts, lib/pyswitch)
multimode/overlay/ 장치 루트에 덮어쓰는 파일
code.py, display.py, inputs.py, communication.py
lib/multimode/ 엔진 (engine.py, devices.py, setup.py, store.py, power.py, bootselect.py)
profiles/ modes.json + 프로필 JSON
multimode/web/ 웹 에디터 (정적 사이트)
multimode/tools/ mmtool.py (빌드·검증·배포), sim.py (PC 시뮬레이터)
multimode/tests/ 프로필 동작 테스트
장치 이미지 = content/의 업스트림 파일 + multimode/overlay. 업스트림 파일은 건드리지 않으므로 업스트림 머지가 쉽습니다.
| 전원 켤 때 누르고 있는 키 | 결과 |
|---|---|
| 1 | USB 드라이브 연결 (설정·펌웨어 변경) |
bootSelect에 지정한 키 (기본: 2 = QC, A = Anagram) |
해당 모드로 시작하고 기억 |
| 없음 | 마지막 모드로 시작 |
프로필 안에서 {"mode": "next"} 동작으로도 모드를 바꿀 수 있습니다 (기본 Anagram 프로필: C 더블클릭).
에디터의 설치 탭이 순서대로 안내합니다.
- 1번 스위치를 누른 채 전원을 켜서 MIDICAPTAIN 드라이브를 열고, 장치 드라이브 연결.
- CircuitPython 버전 확인: PySwitch의
.mpy라이브러리는 바이트코드 v5(CircuitPython 7.x)라서 7.x만 지원합니다. 8.x 이상이면 7.3.x.uf2로 먼저 바꿉니다 (에디터의 시리얼 메뉴로 부트로더 진입·굽기 가능). - 백업: 장치 전체를 PC 폴더(
midicaptain-backup-…)로 복사합니다. 순정 펌웨어는 재배포할 수 없으므로 각자 보관해야 하며, 특히license폴더가 중요합니다. - 설치: 기존 파일을 지우고(
license폴더는 남김) 펌웨어와 프로필을 씁니다. - 드라이브를 꺼내고 스위치를 누르지 않고 다시 켭니다.
되돌리기: 설치 탭의 백업에서 복원.
- 1번 스위치를 누른 채 장치를 켜서 드라이브(MIDICAPTAIN 또는 CIRCUITPY)를 연결합니다.
- 에디터에서 장치 드라이브 연결 → 드라이브 최상위 폴더를 선택합니다. (데스크톱 Chrome·Edge)
- 처음이면 펌웨어 설치·업데이트, 그다음 매핑을 편집하고 장치에 저장.
- 스위치를 누르지 않고 장치를 다시 켭니다.
로컬에서 띄우기: python3 multimode/tools/mmtool.py serve → http://localhost:8765
언어: English, 简体中文, 日本語, 한국어. 브라우저 언어를 따르고, 상단에서 바꾸거나 ?lang=ja처럼 링크로 지정할 수 있습니다 (문구는 web/i18n.js, 프리셋 이름은 web/presets.js).
python3 multimode/tools/mmtool.py validate # 모든 기기 × 모든 프로필 검증 (실제 엔진으로 모든 동작 실행)
python3 multimode/tools/mmtool.py deploy # 검증 → 장치 백업 → 바뀐 파일만 복사 (--dry-run 가능, 장치의 프로필은 --profiles 없이는 유지)
python3 multimode/tools/mmtool.py backup | restore DIR
python3 -m unittest discover -s multimode/tests
┌──────┬──────┬──────┐ 힌트 (길게 누르기, 작은 글씨)
│ A2 │ B2 │ PRE │ 라벨 (짧게 누르기)
├──────┴──────┼──────┤ 상태줄 (status) │ 전원 (power)
├──────┬──────┬──────┤ 타일 캡션 (tiles[].caption)
│ PRE │ II │ A │ 타일 값 (tiles[].value, 40pt)
├──────┴──────┴──────┤
│ SCENE B │ 메시지 (방금 실행한 동작 = flash, 없으면 message)
├──────┬──────┬──────┤
│ C2 │ D2 │ TAP │ 라벨
│SCN - │SCN + │ GIG │ 힌트
└──────┴──────┴──────┘
배치는 overlay/lib/multimode/devices.py의 screen_layout() 한 곳에서 정의되고, 장치·PC 렌더러·웹 미리보기가 같이 씁니다.
화면 그림: python3 multimode/tools/render.py (Pillow 필요, 장치 폰트로 그림) → multimode/screens/sheet.png.
글자 폭 표 tools/font_metrics.json(render.py --metrics)으로 validate와 웹 미리보기가 칸을 넘는 문구를 알려줍니다.
메모리 (Mini 6, CircuitPython 7.3.1, 실행 중 여유 RAM): 이전 화면 50KB, 새 화면 18.5KB (라벨 21개). 표현식은 부팅 때 함수로 컴파일되고 프로필 안의 문자열을 대체해서 메모리를 아낍니다.
- 값은 숫자 그대로이거나 파이썬 식 문자열입니다. 라벨은
{식}템플릿입니다. - 동작:
cc/val,pc,note/vel,raw,set/to,toggle,inc/by/mod/lo/min/max,if/then/else,mode(next,prev, 이름),flash/ms/color(메시지 줄에 잠깐 표시, 기본 1500ms). 모든 MIDI 동작에ch(1-16)로 채널 지정 가능. - 레이어의
screen은 프로필screen을 항목별로 덮어씁니다 (tiles는 칸별). 예: 튜너 레이어{"statusColor": "RED", "tiles": [{"color": "RED"}, …]}. label,hint는 기기별 문구{ "*": 기본, "mc10": … }도 됩니다.- 이전 형식의
bar/barColor/main은status/statusColor/message로 읽힙니다. - 색: 이름(
BLUE등 PySwitch 색),#rrggbb,[r,g,b], 또는 그런 값을 내는 식.led에 3개 목록을 주면 LED 3칸을 각각 칠합니다. desc(설명)는 문자열 또는{ "en": …, "zh": …, "ja": …, "ko": … }. 에디터 전용이라 장치에는profiles/<이름>.desc.json으로 따로 저장되고, 펌웨어는 읽지 않습니다.double을 정의한 키는 짧게 누르기가doubleMs만큼 늦게 실행됩니다. 대기 중에는_pend가 그 키 이름입니다.- 식은 부팅할 때 한꺼번에 함수로 컴파일됩니다 (CircuitPython 7에는
compile()이 없고,eval()은 매번 ~15ms 걸림). 실측: 프로필 로드 185ms, 전체 화면 갱신 24ms, RAM 15KB.
PySwitch(© Thomas Weber)의 GPLv3를 따릅니다. 이 폴더의 파일은 basster6/PySwitch 포크에서 추가했습니다 (2026, basster6). Adafruit 라이브러리와 폰트는 각자의 라이선스(content/ 참고)를 따릅니다. Quad Cortex(Neural DSP), Anagram, MIDI Captain(Paint Audio)은 각 소유자의 상표이며, 이 프로젝트는 제조사와 관계가 없습니다.
{ "name": "Quad Cortex mini", "tag": "QC", "channel": 1, "holdMs": 600, "doubleMs": 700, "power": true, "state": { "gig": 0, "qp": 2 }, // 상태 변수 (초깃값) "consts": { "FS": [[35,36,37,38],[39,40,41,42]] }, "vars": [["opp", "3 - qp"]], // 파생 변수, 순서대로 계산 "screen": { "status": "QC {'GIG VIEW' if gig else 'CHAIN'}", "statusColor": "DARK_YELLOW", "power": "{('%d%%' % pwp) if pwp >= 0 else 'USB'}", "tiles": [{ "caption": "MODE", "value": "{MODES[ms]}", "color": "ORANGE" }, …], "message": "", "messageColor": "BLACK" }, "layers": [ // 위에서부터 조건(when)이 맞는 첫 레이어가 키를 정의 { "name": "Gig View", "when": "gig", "screen": { "main": "GV QP{qp}" }, "keys": { "1": { "label": "A{qp}", "hint": { "*": "PAGE SWAP", "mc10": "PAGE" }, "color": "BLUE", "led": ["BLUE", "BLUE", "GRAY"], "short": [{ "cc": "FS[qp - 1][0]" }], "long": [{ "cc": "FS[opp - 1][0]" }], "double": [{ "toggle": "gig" }, { "cc": 46, "val": "127 if gig else 0" }], "desc": { "short": "풋스위치 A (현재 페이지)" } } } } ] }