Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

notepat-remote: BIOS-level keyboard capture in jweb iframe \u2192 window.max.outlet (bypass Max [key] focus issue)

+91 -94
+70 -94
ac-m4l/AC-NotepatRemote.amxd.json
··· 10 10 "enablehscroll": 0, 11 11 "enablevscroll": 0, 12 12 "devicewidth": 360, 13 - "description": "ac-native notepat relay + local hotkey input. Native [key]/[keyup] for sub-ms local note entry. jweb~ handles WS relay + UI only.", 13 + "description": "ac-native notepat relay + local hotkey input. BIOS in the jweb~ iframe captures keys and forwards via window.max.outlet so Max's focus limits don't apply.", 14 14 "boxes": [ 15 15 { 16 16 "box": { ··· 25 25 "presentation": 1, 26 26 "presentation_rect": [0, 0, 360, 220], 27 27 "rendermode": 1, 28 - "url": "https://aesthetic.computer/notepat-remote?daw=1&density=1&nogap&v=7" 28 + "url": "https://aesthetic.computer/notepat-remote?daw=1&density=1&nogap&v=8" 29 29 } 30 30 }, 31 31 { 32 32 "box": { 33 - "comment": "Strip 'note' and 'channel' keywords from jweb messages", 33 + "comment": "Split jweb messages: note / channel / keydown / keyup / other", 34 34 "id": "obj-route", 35 35 "maxclass": "newobj", 36 36 "numinlets": 1, 37 - "numoutlets": 3, 38 - "outlettype": ["", "", ""], 39 - "patching_rect": [10, 250, 180, 22], 40 - "text": "route note channel" 37 + "numoutlets": 5, 38 + "outlettype": ["", "", "", "", ""], 39 + "patching_rect": [10, 250, 360, 22], 40 + "text": "route note channel keydown keyup" 41 41 } 42 42 }, 43 43 { 44 44 "box": { 45 - "comment": "Emit MIDI into the track's next device", 45 + "comment": "MIDI output for the track's device chain", 46 46 "id": "obj-noteout", 47 47 "maxclass": "newobj", 48 48 "numinlets": 2, ··· 53 53 }, 54 54 { 55 55 "box": { 56 + "comment": "Debug: note pitch/vel after route (relay path)", 57 + "id": "obj-print-note", 58 + "maxclass": "newobj", 59 + "numinlets": 1, 60 + "numoutlets": 0, 61 + "patching_rect": [400, 290, 200, 22], 62 + "text": "print NOTEPAT-NOTE" 63 + } 64 + }, 65 + { 66 + "box": { 67 + "comment": "Debug: channel after route", 68 + "id": "obj-print-chan", 69 + "maxclass": "newobj", 70 + "numinlets": 1, 71 + "numoutlets": 0, 72 + "patching_rect": [400, 320, 200, 22], 73 + "text": "print NOTEPAT-CHAN" 74 + } 75 + }, 76 + { 77 + "box": { 56 78 "comment": "Debug: unmatched jweb messages", 57 - "id": "obj-print", 79 + "id": "obj-print-other", 58 80 "maxclass": "newobj", 59 81 "numinlets": 1, 60 82 "numoutlets": 0, 61 - "patching_rect": [700, 300, 180, 22], 62 - "text": "print NOTEPAT-REMOTE" 83 + "patching_rect": [400, 350, 200, 22], 84 + "text": "print NOTEPAT-OTHER" 85 + } 86 + }, 87 + { 88 + "box": { 89 + "comment": "Debug: ASCII code on keydown forwarded from BIOS", 90 + "id": "obj-print-keydown", 91 + "maxclass": "newobj", 92 + "numinlets": 1, 93 + "numoutlets": 0, 94 + "patching_rect": [400, 410, 200, 22], 95 + "text": "print KEY-DOWN" 63 96 } 64 97 }, 65 98 { 66 99 "box": { 67 - "comment": "Debug: note pitch/vel after route", 68 - "id": "obj-print-note", 100 + "comment": "Debug: ASCII code on keyup", 101 + "id": "obj-print-keyup", 69 102 "maxclass": "newobj", 70 103 "numinlets": 1, 71 104 "numoutlets": 0, 72 - "patching_rect": [200, 340, 200, 22], 73 - "text": "print NOTEPAT-NOTE" 105 + "patching_rect": [400, 440, 200, 22], 106 + "text": "print KEY-UP" 74 107 } 75 108 }, 76 109 { 77 110 "box": { 78 - "comment": "Debug: channel after route", 79 - "id": "obj-print-chan", 111 + "comment": "Debug: mapped pitch after expr (0 means unmapped)", 112 + "id": "obj-print-keypitch", 80 113 "maxclass": "newobj", 81 114 "numinlets": 1, 82 115 "numoutlets": 0, 83 - "patching_rect": [200, 380, 200, 22], 84 - "text": "print NOTEPAT-CHAN" 116 + "patching_rect": [400, 510, 200, 22], 117 + "text": "print KEY-PITCH" 85 118 } 86 119 }, 87 120 { ··· 91 124 "numinlets": 1, 92 125 "numoutlets": 3, 93 126 "outlettype": ["bang", "int", "int"], 94 - "patching_rect": [700, 250, 90, 22], 127 + "patching_rect": [620, 250, 90, 22], 95 128 "text": "live.thisdevice" 96 129 } 97 130 }, ··· 102 135 "numinlets": 1, 103 136 "numoutlets": 2, 104 137 "outlettype": ["", ""], 105 - "patching_rect": [700, 280, 60, 22], 138 + "patching_rect": [620, 280, 60, 22], 106 139 "text": "route ready" 107 140 } 108 141 }, ··· 114 147 "numinlets": 2, 115 148 "numoutlets": 1, 116 149 "outlettype": [""], 117 - "patching_rect": [700, 330, 120, 22], 150 + "patching_rect": [620, 310, 120, 22], 118 151 "text": "script daw-activate" 119 152 } 120 153 }, 121 - 122 - { 123 - "box": { 124 - "comment": "Native keydown capture — sub-ms path to noteout, bypasses iframe", 125 - "id": "obj-key", 126 - "maxclass": "newobj", 127 - "numinlets": 1, 128 - "numoutlets": 3, 129 - "outlettype": ["int", "int", "int"], 130 - "patching_rect": [10, 420, 40, 22], 131 - "text": "key" 132 - } 133 - }, 134 - { 135 - "box": { 136 - "comment": "Native keyup capture for note-off", 137 - "id": "obj-keyup", 138 - "maxclass": "newobj", 139 - "numinlets": 1, 140 - "numoutlets": 3, 141 - "outlettype": ["int", "int", "int"], 142 - "patching_rect": [260, 420, 50, 22], 143 - "text": "keyup" 144 - } 145 - }, 146 154 { 147 155 "box": { 148 156 "comment": "ASCII → MIDI pitch (notepat layout). 0 = unmapped.", ··· 151 159 "numinlets": 1, 152 160 "numoutlets": 1, 153 161 "outlettype": ["int"], 154 - "patching_rect": [10, 450, 780, 22], 162 + "patching_rect": [10, 420, 780, 22], 155 163 "text": "expr (($i1==97)*69)+(($i1==98)*71)+(($i1==99)*60)+(($i1==100)*62)+(($i1==101)*64)+(($i1==102)*65)+(($i1==103)*67)+(($i1==104)*72)+(($i1==105)*74)+(($i1==106)*76)+(($i1==107)*77)+(($i1==108)*79)+(($i1==109)*81)+(($i1==110)*83)+(($i1==111)*80)+(($i1==112)*82)+(($i1==113)*70)+(($i1==114)*68)+(($i1==115)*63)+(($i1==116)*73)+(($i1==117)*78)+(($i1==118)*61)+(($i1==119)*66)+(($i1==120)*59)+(($i1==121)*75)+(($i1==122)*58)" 156 164 } 157 165 }, ··· 163 171 "numinlets": 1, 164 172 "numoutlets": 1, 165 173 "outlettype": ["int"], 166 - "patching_rect": [10, 480, 780, 22], 174 + "patching_rect": [10, 450, 780, 22], 167 175 "text": "expr (($i1==97)*69)+(($i1==98)*71)+(($i1==99)*60)+(($i1==100)*62)+(($i1==101)*64)+(($i1==102)*65)+(($i1==103)*67)+(($i1==104)*72)+(($i1==105)*74)+(($i1==106)*76)+(($i1==107)*77)+(($i1==108)*79)+(($i1==109)*81)+(($i1==110)*83)+(($i1==111)*80)+(($i1==112)*82)+(($i1==113)*70)+(($i1==114)*68)+(($i1==115)*63)+(($i1==116)*73)+(($i1==117)*78)+(($i1==118)*61)+(($i1==119)*66)+(($i1==120)*59)+(($i1==121)*75)+(($i1==122)*58)" 168 176 } 169 177 }, ··· 175 183 "numinlets": 1, 176 184 "numoutlets": 2, 177 185 "outlettype": ["bang", "int"], 178 - "patching_rect": [10, 510, 60, 22], 186 + "patching_rect": [10, 480, 60, 22], 179 187 "text": "select 0" 180 188 } 181 189 }, ··· 186 194 "numinlets": 1, 187 195 "numoutlets": 2, 188 196 "outlettype": ["bang", "int"], 189 - "patching_rect": [260, 510, 60, 22], 197 + "patching_rect": [200, 480, 60, 22], 190 198 "text": "select 0" 191 199 } 192 200 }, ··· 210 218 "numinlets": 2, 211 219 "numoutlets": 1, 212 220 "outlettype": ["list"], 213 - "patching_rect": [260, 540, 80, 22], 221 + "patching_rect": [200, 540, 80, 22], 214 222 "text": "pack 0 0" 215 223 } 216 - }, 217 - { 218 - "box": { 219 - "comment": "Debug: did Max [key] fire at all?", 220 - "id": "obj-print-keydown", 221 - "maxclass": "newobj", 222 - "numinlets": 1, 223 - "numoutlets": 0, 224 - "patching_rect": [60, 420, 200, 22], 225 - "text": "print KEY-DOWN" 226 - } 227 - }, 228 - { 229 - "box": { 230 - "comment": "Debug: did Max [keyup] fire?", 231 - "id": "obj-print-keyup", 232 - "maxclass": "newobj", 233 - "numinlets": 1, 234 - "numoutlets": 0, 235 - "patching_rect": [320, 420, 200, 22], 236 - "text": "print KEY-UP" 237 - } 238 - }, 239 - { 240 - "box": { 241 - "comment": "Debug: did mapping produce a pitch?", 242 - "id": "obj-print-keypitch", 243 - "maxclass": "newobj", 244 - "numinlets": 1, 245 - "numoutlets": 0, 246 - "patching_rect": [80, 510, 170, 22], 247 - "text": "print KEY-PITCH" 248 - } 249 224 } 250 225 ], 251 226 "lines": [ ··· 254 229 { "patchline": { "source": ["obj-route", 0], "destination": ["obj-print-note", 0] } }, 255 230 { "patchline": { "source": ["obj-route", 1], "destination": ["obj-noteout", 1] } }, 256 231 { "patchline": { "source": ["obj-route", 1], "destination": ["obj-print-chan", 0] } }, 257 - { "patchline": { "source": ["obj-route", 2], "destination": ["obj-print", 0] } }, 258 - { "patchline": { "source": ["obj-thisdevice", 0], "destination": ["obj-routeready", 0] } }, 259 - { "patchline": { "source": ["obj-routeready", 0], "destination": ["obj-activate", 0] } }, 260 - { "patchline": { "source": ["obj-activate", 0], "destination": ["obj-jweb", 0] } }, 232 + { "patchline": { "source": ["obj-route", 2], "destination": ["obj-expr-on", 0] } }, 233 + { "patchline": { "source": ["obj-route", 2], "destination": ["obj-print-keydown", 0] } }, 234 + { "patchline": { "source": ["obj-route", 3], "destination": ["obj-expr-off", 0] } }, 235 + { "patchline": { "source": ["obj-route", 3], "destination": ["obj-print-keyup", 0] } }, 236 + { "patchline": { "source": ["obj-route", 4], "destination": ["obj-print-other", 0] } }, 261 237 262 - { "patchline": { "source": ["obj-key", 0], "destination": ["obj-expr-on", 0] } }, 263 - { "patchline": { "source": ["obj-key", 0], "destination": ["obj-print-keydown", 0] } }, 264 238 { "patchline": { "source": ["obj-expr-on", 0], "destination": ["obj-sel-on", 0] } }, 265 239 { "patchline": { "source": ["obj-sel-on", 1], "destination": ["obj-pack-on", 0] } }, 266 240 { "patchline": { "source": ["obj-sel-on", 1], "destination": ["obj-print-keypitch", 0] } }, 267 241 { "patchline": { "source": ["obj-pack-on", 0], "destination": ["obj-noteout", 0] } }, 268 242 269 - { "patchline": { "source": ["obj-keyup", 0], "destination": ["obj-expr-off", 0] } }, 270 - { "patchline": { "source": ["obj-keyup", 0], "destination": ["obj-print-keyup", 0] } }, 271 243 { "patchline": { "source": ["obj-expr-off", 0], "destination": ["obj-sel-off", 0] } }, 272 244 { "patchline": { "source": ["obj-sel-off", 1], "destination": ["obj-pack-off", 0] } }, 273 - { "patchline": { "source": ["obj-pack-off", 0], "destination": ["obj-noteout", 0] } } 245 + { "patchline": { "source": ["obj-pack-off", 0], "destination": ["obj-noteout", 0] } }, 246 + 247 + { "patchline": { "source": ["obj-thisdevice", 0], "destination": ["obj-routeready", 0] } }, 248 + { "patchline": { "source": ["obj-routeready", 0], "destination": ["obj-activate", 0] } }, 249 + { "patchline": { "source": ["obj-activate", 0], "destination": ["obj-jweb", 0] } } 274 250 ], 275 251 "dependency_cache": [], 276 252 "latency": 0,
+21
system/public/aesthetic.computer/bios.mjs
··· 4509 4509 const hasDawParam = new URLSearchParams(window.location.search).has("daw"); 4510 4510 if (hasDawParam) { 4511 4511 _dawConnectSend(send, updateMetronome); 4512 + 4513 + // 🎹 Low-latency keyboard bridge for M4L devices. 4514 + // jweb~ captures keyboard focus, which means Max's own [key]/[keyup] 4515 + // objects never see keystrokes. Capture them here on the main thread and 4516 + // forward directly via window.max.outlet — sub-ms, no worker round-trip, 4517 + // no iframe focus fight. 4518 + const _dawKeyEmit = (kind, e) => { 4519 + if (e.repeat) return; // skip auto-repeat 4520 + const k = typeof e.key === "string" ? e.key : ""; 4521 + if (k.length !== 1) return; // skip modifier / arrow / function keys 4522 + const ascii = k.toLowerCase().charCodeAt(0); 4523 + if ( 4524 + typeof window !== "undefined" && 4525 + window.max && 4526 + typeof window.max.outlet === "function" 4527 + ) { 4528 + try { window.max.outlet(kind, ascii); } catch (_err) {} 4529 + } 4530 + }; 4531 + window.addEventListener("keydown", (e) => _dawKeyEmit("keydown", e), true); 4532 + window.addEventListener("keyup", (e) => _dawKeyEmit("keyup", e), true); 4512 4533 } 4513 4534 4514 4535 function requestBeat(time) {
system/public/m4l/notepat-remote.amxd

This is a binary file and will not be displayed.