-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPROJECT.html
More file actions
182 lines (164 loc) · 16.7 KB
/
Copy pathPROJECT.html
File metadata and controls
182 lines (164 loc) · 16.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<title>Teletype Translator — project brief</title>
<style>
:root { color-scheme: dark; }
body { background:#060907; color:#C6FFDD; font:14px/1.6 ui-monospace,Menlo,monospace;
max-width:940px; margin:0 auto; padding:42px 24px 90px; }
h1 { color:#45EE86; font-size:24px; letter-spacing:.04em; margin-bottom:2px;
text-shadow:0 0 18px rgba(69,238,134,.35); }
.sub { color:#5d8a6f; margin-bottom:26px; }
h2 { color:#45EE86; font-size:13px; letter-spacing:.18em; text-transform:uppercase;
border-bottom:1px solid #14351f; padding-bottom:6px; margin:36px 0 12px; }
code { background:#0b140d; border:1px solid #14351f; padding:1px 6px; border-radius:4px; color:#8ef5b1; }
pre { background:#0b140d; border:1px solid #14351f; border-radius:8px; padding:14px 16px;
overflow-x:auto; color:#a5e8bd; font-size:13px; }
table { border-collapse:collapse; width:100%; margin:10px 0; }
th, td { text-align:left; padding:6px 12px; border-bottom:1px solid #0f2416; vertical-align:top; }
th { color:#5d8a6f; font-size:11px; text-transform:uppercase; letter-spacing:.1em; }
.badge { display:inline-block; padding:2px 12px; border-radius:20px; font-size:12px; font-weight:700;
background:#0d2f1a; color:#45EE86; border:1px solid #1d5c34; margin-right:6px; }
.warn { background:#33230b; color:#f0b45c; border-color:#6b4a17; }
li { margin:4px 0; }
.prov { color:#3f5c4b; font-size:12px; margin-top:44px; border-top:1px solid #14351f; padding-top:12px; }
.dim { color:#5d8a6f; }
.danger { color:#f0b45c; font-weight:700; }
</style>
<h1>TELETYPE TRANSLATOR</h1>
<p class="sub">MIDI files → monome Teletype scenes, plus a live WiFi→USB performance bridge.<br>
repo <code>teletype-translator</code> · Swift (TeletypeKit + SwiftUI app) + Python (Pi bridge) ·
<span class="badge">TRANSLATOR SHIPPED</span> <span class="badge">BRIDGE LIVE — hardware-verified 2026-07-11</span></p>
<h2>Daily use — no terminal, no Claude</h2>
<table>
<tr><th>#</th><th>do this</th><th>notes</th></tr>
<tr><td>1</td><td>Power the Pi (its own USB power) and plug its <b>data</b> port into the Teletype's USB port</td><td>It boots into MIDI mode by itself, joins WiFi, and starts the bridge. Nothing to log into.</td></tr>
<tr><td>2</td><td>Open <b>TeletypeTranslator</b>, drag in a <code>.mid</code></td><td>Grid / tempo / voices / mixer as usual.</td></tr>
<tr><td>3</td><td>Click <b>MIDI ○ → ●</b> in the SCENE row, pick the network destination</td><td>The app enables the Network MIDI session and connects to the Pi <i>for you</i> — Audio MIDI Setup is no longer needed.</td></tr>
<tr><td>4</td><td>Hit <b>▶ Play</b></td><td>Voices 1–4 → CV/TR 1–4 on the rack. <b>SYN ○</b> mutes the laptop synth so only the rack sounds. <b>PANIC</b> kills stuck notes.</td></tr>
</table>
<p>Play also self-heals: if the Pi was left wearing another USB costume, it's flipped back to MIDI
automatically. The Teletype keeps its scene in flash, so it survives power cycles.</p>
<p><b>To put a new scene on the Teletype</b> (no USB stick, no SSH): <b>Remote</b> tab →
pick a slot → <b>Send scene → Teletype</b> → on the module, turn <b>PARAM</b> to
<b>READ FROM USB</b> and press the <b>front button</b> → click <b>Done</b>. Then load it on the
module (<b>ESC</b> → arrow to it → <b>ENTER</b>) — loading is what runs its init script.</p>
<h2>What & where</h2>
<p>Translates Standard MIDI Files into monome Teletype Eurorack scenes (<code>tt##.txt</code>), and streams
MIDI live from the Mac to the Teletype through a Raspberry Pi USB-gadget bridge. GitHub:
<code>github.com/negativetime/teletype-translator</code> (MIT).</p>
<table>
<tr><th>component</th><th>path</th><th>what</th></tr>
<tr><td>TeletypeKit</td><td><code>teletype-translator/TeletypeKit</code></td><td>pure-logic engine: SMF parser → MusicIR → Arranger → Scene → serializer/validator; 66 tests; byte-exact vs 8 factory scenes</td></tr>
<tr><td>ttx CLI</td><td><code>TeletypeKit/Sources/ttx</code></td><td>file translation: <code>swift run ttx file.mid --out DIR [--slot N --grid N --voices N --target cvtr|jf|w]</code></td></tr>
<tr><td>macOS app</td><td><code>teletype-translator/app</code> → <code>/Applications/TeletypeTranslator.app</code></td><td>SwiftUI workbench (phosphor-terminal theme): import, arrange, audition synth, grid editing, USB export, live MIDI out, Remote tab</td></tr>
<tr><td>bridge scenes + Teensy plan</td><td><code>teletype-translator/bridge</code></td><td><code>tt09.txt</code> (1-voice MIDI monitor), <code>tt10.txt</code> (4-voice live scene), Teensy 4.1 sketches (endgame bridge)</td></tr>
<tr><td>Pi appliance</td><td><code>teletype-translator/bridge/pi</code></td><td>everything deployed on the Pi Zero W: bridge, remote control, mode scripts, service units</td></tr>
<tr><td>scene corpus</td><td><code>teletype-translator/corpus/scenes</code></td><td>monome's 8 factory scenes (tt00–07), byte-exact round-trip ground truth</td></tr>
<tr><td>brand/site</td><td><code>teletype-translator/site</code></td><td>phosphor-terminal design system (icon.svg, brand-concepts.html) — approved website direction</td></tr>
</table>
<h2>The chain (working end-to-end)</h2>
<pre>
TeletypeTranslator.app (MIDI ● · voices → channels 1–4 · gates close at 80% of step)
─▶ Network RTP Session (Audio MIDI Setup) ─▶ WiFi (2.4 GHz — the Zero W has no 5 GHz)
─▶ Pi Zero W teletype.local (pymidi bridge :5004/5005 → g_midi gadget)
─▶ USB ─▶ Teletype USB host ─▶ MI.* ops (scene 10) ─▶ CV 1–4 pitch + TR 1–4 gates
</pre>
<h2>Current status <span class="dim">(as of 2026-07-12)</span></h2>
<table>
<tr><th>date</th><th>milestone</th></tr>
<tr><td>07-05</td><td>Translator + app shipped: full pipeline, 66 tests, app expanded (grid to 1/32+triplets, swing preview, Synth tab, JF/W i2c export targets), icon + brand</td></tr>
<tr><td>07-10</td><td>App live MIDI out (MIDI ● + destination picker + SYN mute); Teensy 4.1 bridge scaffold committed (Ethernet kit ordered, still shipping)</td></tr>
<tr><td>07-11</td><td>Pi Zero W bridge built & loop-verified byte-perfect (the Amazon "Zero 2 W" is an original Zero W — ARMv6); hardware-verified on the rack: CV/TR responding</td></tr>
<tr><td>07-12</td><td>Scene 10 (4-voice) loaded — all four TR lights confirmed; gate-time fix (80%); PANIC button; auto-restore MIDI mode on Play; Remote tab (mode switch + remote typing, confirm-tap); mixer track paging for >4-track files</td></tr>
</table>
<h2>The Pi appliance</h2>
<p>Pi Zero W Rev 1.1, Raspberry Pi OS Lite 32-bit (armhf), hostname <code>teletype</code>, stock <code>pi</code>
account with your own password and SSH key (set in <code>bridge/pi/firstrun.sh.template</code>).
IPv6 disabled. Powered separately from data (PWR port). Three switchable USB identities:</p>
<table>
<tr><th>mode</th><th>the Teletype sees</th><th>command on Pi</th></tr>
<tr><td>midi <span class="dim">(default)</span></td><td>class-compliant USB-MIDI device; <code>midibridge.service</code> feeds it from RTP-MIDI</td><td><code>tt-midi-mode</code></td></tr>
<tr><td>keyboard</td><td>HID keyboard; <code>ttype "CV 1 V 5" -e</code> / <code>ttype -k ALT+ENTER</code> types on the Teletype</td><td><code>tt-keyboard-mode</code></td></tr>
<tr><td>disk</td><td>FAT16 flash drive (<code>~/ttdisk.img</code>) — Teletype disk mode reads <code>tt##.txt</code> from it</td><td><code>tt-disk-mode</code></td></tr>
</table>
<p>HTTP control (<code>ttremote.service</code>, port 8044 — what the app's Remote tab and the auto-restore use):</p>
<pre>
GET /status → {"mode": "...", "udc": "configured|..."}
POST /mode {"mode":"midi"|"keyboard"|"disk"}
POST /type {"text":"MI.$ 1 1","enter":true} (keyboard mode only)
POST /key {"key":"ALT+ENTER"} (keyboard mode only)
</pre>
<p class="dim">udc "configured" = a host has enumerated the gadget. Host speed is a tell: Mac = high-speed, Teletype = full-speed (<code>dmesg | grep dwc2</code>).</p>
<h2>Open: Just Friends pitch <span class="dim">(unresolved as of 2026-07-12)</span></h2>
<p>Scene <code>tt10</code>/<code>tt11</code> drive JF over i2c, and JF <b>does</b> sound (CV 1 + TR also
respond, so MIDI routing and the scene are correct) — but <b>every note comes out at the same pitch</b>.
Ruled out: the <code>ii</code> cable (present), scene not loaded (script verified running), MIDI delivery
(bridge logs the notes). Fixed once already: <code>JF.NOTE x y</code> takes <b>x = pitch RELATIVE TO C3</b>
(per the ops reference), so <code>MI.NV</code> (absolute note voltage) clamped everything — now
<code>N SUB MI.N 48</code>. Still constant afterwards, so the next suspects are on JF's side:</p>
<ul>
<li><b>JF's left toggle must be on <code>sound</code></b> (not <code>shape</code>) — Just Type only makes pitched voices in sound mode</li>
<li><b>JF firmware must support Just Type</b> (JF 4.0+); older firmware ignores <code>JF.MODE</code> and treats <code>JF.NOTE</code>'s first arg as a rate, not a pitch — which would produce exactly this symptom</li>
<li>Bisect to run on the module: <code>JF.MODE 1</code> → <code>JF.NOTE N 0 V 5</code> → <code>JF.NOTE N 24 V 5</code>. Different pitches ⇒ JF is fine and the scene's MIDI→pitch math is wrong; same pitch ⇒ it's JF's mode/firmware.</li>
</ul>
<h2>The app</h2>
<ul>
<li><b>Workbench</b> — import .mid / open tt##.txt; grid 1/1–1/32 incl. triplets; tempo/tap; voices; output target CV/TR | JF | W/; per-voice mixer (mute/solo/drag-reroute/▦ grid editor); scene paging; compare tab; USB-volume export</li>
<li><b>MIDI out</b> — voices → channels 1–4; note-offs at 80% of step (real gates, per-note retrigger); <b>PANIC</b> = release all + CC-123 ×4; MIDI ●/○ + destination picker + SYN ●/○ synth mute in the SCENE row; ▶ Play auto-restores the Pi to MIDI mode if it was left in another identity</li>
<li><b>Track paging</b> — files with >4 tracks: <code>‹ tracks 1–4 of N ›</code> in the mixer; arrangement/audition/MIDI/export follow the visible page</li>
<li><b>Remote tab</b> — Pi status dot, mode chips (KEYBOARD/USB STICK arm on first tap, fire on second; MIDI instant), remote typing field + special keys</li>
<li><b>Synth tab</b> — audition-only timbre approximation (Onboard / Just Friends / W/), swing preview</li>
</ul>
<h2>Scenes</h2>
<table>
<tr><th>file</th><th>slot</th><th>what</th></tr>
<tr><td><code>bridge/tt09.txt</code></td><td>9</td><td>MIDI monitor: any note-on → <code>CV 1 MI.LNV</code> + <code>TR.PULSE 1</code>; note-off → <code>TR.PULSE 2</code></td></tr>
<tr><td><code>bridge/tt10.txt</code></td><td>10 <span class="badge">LOADED</span></td><td>4-voice live: <code>L 1 MI.NL : CV MI.NCH MI.NV</code> + <code>TR MI.NCH 1</code>; note-off drops the gate — channel N = CV/TR N</td></tr>
</table>
<p class="dim">Format: UTF-8/LF; title + description; #1–#8/#M/#I sections (≤6 lines each); #P = 4 metadata rows (length/wrap/start/end ×4 banks) + 64 tab-separated rows. Round-trips byte-identical through TeletypeKit. MI.NV confirmed semitone-table (1V/oct) in firmware source. tt03 4TRACK = canonical multi-voice template; tt05–07 = i2c, out of scope.</p>
<h2>Gotchas — don't relearn these</h2>
<table>
<tr><th>area</th><th>lesson</th></tr>
<tr><td>Pi image</td><td>Zero W is ARMv6: arm64 image = LED bootloop, config untouched. Use armhf. 2026 pi-gen images IGNORE custom.toml (ssh/userconf.txt still work) → configure WiFi via Imager-style <code>firstrun.sh</code> + <code>systemd.run=/boot/firmware/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target</code>. <b>cmdline.txt must stay ONE line.</b></td></tr>
<tr><td>network</td><td>macOS dials RTP-MIDI over IPv6 first; bridge is IPv4-only → "connected, 0 ms" but silence. IPv6 is disabled on the Pi (sysctl). Diagnose: <code>tcpdump -i wlan0 -n 'udp port 5004 or udp port 5005'</code>. "didn't respond to connection request" spam = Pi not in midi mode.</td></tr>
<tr><td>Teletype UI</td><td>Unsaved LIVE state dies on reboot. Scene menu: ENTER <b>loads</b> (wipes typed state), ALT+ENTER <b>saves</b>. Silent accept = success. F1–F8 fire scripts 1–8 from the keyboard (MIDI-free self-test). Disk-mode menu (fw 5+): PARAM knob + front button; ESC scene browser: ARROW KEYS (PARAM does nothing). Front-button-hold recall did NOT load in our test — use keyboard ENTER.</td></tr>
<tr><td>MIDI</td><td>Wall-to-wall gates never drop (fixed: 80% gate time). Only one sender at a time — app playing + test stream = MI.LNV whipsaw. A 2-voice arrangement only ever lights 2 TRs: mixer rows = voice count.</td></tr>
<tr><td>HID gadget</td><td><b>The fake keyboard is unreliable.</b> The Teletype only polls a <i>freshly enumerated</i> HID device, and in practice only reliably right after the module itself was power-cycled; it then stops polling within seconds. <code>ttsend</code> re-enumerates + retries ×6, which usually lands ONE key — but it can also fail every try. Treat the Pi keyboard as best-effort; a real keyboard is the reliable path for typing on the Teletype. One enumeration ≈ one keypress.</td></tr>
<tr><td>session survival</td><td>Mode switches used to stop <code>midibridge</code>, which tore down the Mac's RTP session every time (endless "didn't respond to the connection request" + manual Connect). Fixed 2026-07-12: <code>bridge.py</code> opens the gadget node lazily and reopens it when it returns, and the mode scripts no longer stop the service — the session now survives keyboard/disk excursions. The app also brings up the Network MIDI session itself on MIDI-on/Play.</td></tr>
<tr><td class="danger">composite ✗</td><td><b>Do NOT build a composite (multi-function) USB gadget.</b> Tried MIDI+HID in one device (2026-07-12) to avoid the mode dance: the Teletype's USB host cannot parse it — it enumerates (<code>configured</code>) but polls nothing, MIDI stops flowing, and the host stack <b>wedges</b> (pops the disk menu, ignores HID) until the module is <b>power-cycled</b>. One function at a time is a hard constraint. The mode dance is therefore inherent — but it is only needed to <i>load</i> a scene; play lives in MIDI mode.</td></tr>
<tr><td>hardware</td><td>A dead/flaky Teletype USB stick is a common failure; the Pi's disk mode replaces it entirely. One USB port on the Teletype = keyboard/stick/Pi juggling; the Pi's three identities eliminate it.</td></tr>
</table>
<h2>Build / run / test</h2>
<pre>
# engine tests (66)
cd teletype-translator/TeletypeKit && swift test
# translate a MIDI file
swift run ttx song.mid --out /Volumes/STICK --slot 9 --grid 4 --voices 4
# build + install the app
cd teletype-translator/app && xcodegen generate && \
DEVELOPER_DIR=/Applications/Xcode.app xcodebuild -project TeletypeTranslator.xcodeproj \
-scheme TeletypeTranslator -configuration Release -derivedDataPath build \
CODE_SIGNING_ALLOWED=NO build && \
codesign --force -s - build/Build/Products/Release/TeletypeTranslator.app && \
rm -rf /Applications/TeletypeTranslator.app && \
cp -R build/Build/Products/Release/TeletypeTranslator.app /Applications/
# Pi access + bridge log
ssh -i ~/.ssh/YOUR_KEY pi@teletype.local
journalctl -f -u midibridge # peer connects + note traffic
# push a scene to the Teletype without a stick
scp -i ~/.ssh/YOUR_KEY ttNN.txt pi@teletype.local:~/
ssh ... 'sudo mount -o loop ~/ttdisk.img /mnt/ttdisk && sudo cp ~/ttNN.txt /mnt/ttdisk/ && sudo umount /mnt/ttdisk'
curl -X POST http://teletype.local:8044/mode -d '{"mode":"disk"}' # then Teletype disk menu → READ
</pre>
<h2>Teensy endgame <span class="dim">(kit still shipping)</span></h2>
<p>Teensy 4.1 + Ethernet magjack = the wired, no-Linux successor: AppleMIDI lib + NativeEthernet →
usbMIDI device (sketches ready in <code>bridge/rtpmidi_bridge</code> + <code>bridge/usbmidi_smoke</code>;
modeled on the library's official Teensy41 example). USB Type must be pure MIDI for the Teletype;
DHCP needs a router (link-local fallback 169.254.57.42); Teensy pins are 3.3 V only. No-regret board:
i2c2midi MK2 (the robust long-term option, PCB from Pusherman) is itself Teensy 4.1-based.</p>
<h2>Next steps</h2>
<ul>
<li>Teensy bridge when the Ethernet kit arrives (flash smoke sketch first — tests the Teletype leg alone)</li>
<li>Ear/hardware-verify a <i>generated</i> raga scene on the rack (incl. the <code>P.I</code> metro fix — source-analysis only so far; JF octave <code>JF.SHIFT V -4</code> is a reasoned guess)</li>
<li>Possible app follow-ups: gate-length knob, per-scene disk-image push from the UI, website build from <code>site/brand-concepts.html</code></li>
</ul>
<p class="prov">as of 2026-07-12 · canonical project brief — supersedes bridge/README.md, bridge/pi/README.md, docs/plans/ (removed; content absorbed; history in git) ·
sources: this week's build/bridge sessions, TeletypeKit tests, monome teletype firmware source (usb_disk_mode.c, midi.c), Teletype Commands epub</p>