Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

notepat-remote: worker-safe send API + BIOS daw:midi handler + hud.label hide

+41 -38
+1 -1
ac-m4l/AC-NotepatRemote.amxd.json
··· 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=3" 28 + "url": "https://aesthetic.computer/notepat-remote?daw=1&density=1&nogap&v=4" 29 29 } 30 30 }, 31 31 {
+17
system/public/aesthetic.computer/bios.mjs
··· 5159 5159 return; 5160 5160 } 5161 5161 5162 + // 🎹 MIDI note emit from a worker piece → Max (jweb~ main-thread bridge). 5163 + // Pieces running inside an M4L device's jweb~ send 5164 + // send({ type: "daw:midi", content: { pitch, velocity, channel? } }) 5165 + // and BIOS forwards to `window.max.outlet` which the patcher routes via 5166 + // [route note channel] → [noteout]. 5167 + if (type === "daw:midi" && content) { 5168 + if (typeof window !== "undefined" && window.max && typeof window.max.outlet === "function") { 5169 + const pitch = Number(content.pitch); 5170 + const velocity = Number(content.velocity); 5171 + const channel = Number.isFinite(Number(content.channel)) ? Number(content.channel) : 0; 5172 + if (!Number.isFinite(pitch) || !Number.isFinite(velocity)) return; 5173 + try { window.max.outlet("channel", channel); } catch (_e) {} 5174 + try { window.max.outlet("note", pitch, velocity); } catch (_e) {} 5175 + } 5176 + return; 5177 + } 5178 + 5162 5179 // Helper function to generate appropriate filenames for tape recordings 5163 5180 function generateTapeFilename(extension, suffix = "") { 5164 5181 const options = window.currentRecordingOptions || {
+23 -37
system/public/aesthetic.computer/disks/notepat-remote.mjs
··· 49 49 let frame = 0; 50 50 let lastEmittedChannel = -1; 51 51 52 - // Max for Live jweb~ bridge — looked up lazily because jweb~ injects 53 - // window.max AFTER the page load, not before module evaluation. 54 - function maxBridge() { 55 - if (typeof window === "undefined") return null; 56 - const m = window.max; 57 - if (!m || typeof m.outlet !== "function") return null; 58 - return m; 59 - } 52 + // Piece runs in a Worker, so `window.max.outlet` isn't reachable here. 53 + // BIOS (main thread) owns that bridge: we send a `daw:midi` message via 54 + // the worker→BIOS `send` pipe and BIOS forwards it to Max. 55 + // 56 + // Handler lives in bios.mjs — it calls window.max.outlet("channel", ch) + 57 + // window.max.outlet("note", pitch, vel), which the patcher routes: 58 + // [jweb~ msg outlet] → [route note channel] → [noteout]. 59 + let _send = null; // captured from boot(); used from ws callbacks too 60 60 61 61 function emitMaxNote(pitch, velocity, channel) { 62 - const m = maxBridge(); 63 - if (!m) return; 64 - try { 65 - if (channel !== lastEmittedChannel) { 66 - m.outlet("channel", channel); 67 - lastEmittedChannel = channel; 68 - } 69 - m.outlet("note", pitch, velocity); 70 - // Mirror to Max console so it's obvious when a note fired. 71 - console.log(`🎹 out note=${pitch} vel=${velocity} ch=${channel}`); 72 - } catch (err) { 73 - console.log("🎹 outlet err:", err?.message || err); 74 - } 62 + if (!_send) return; 63 + _send({ 64 + type: "daw:midi", 65 + content: { pitch, velocity, channel }, 66 + }); 67 + console.log(`🎹 out note=${pitch} vel=${velocity} ch=${channel}`); 75 68 } 76 69 77 70 function connectWs() { ··· 191 184 return n + o; 192 185 } 193 186 194 - function boot({ wipe, cursor }) { 187 + function boot({ wipe, cursor, hud, send }) { 195 188 wipe(8, 10, 18); 196 189 cursor?.("native"); 190 + // Hide the default HUD piece-name label — device UI has its own header. 191 + hud?.label?.(""); 192 + // Capture `send` for use in ws callbacks and key/touch handlers below. 193 + _send = send; 194 + console.log("🎹 boot ready, send captured:", typeof _send); 197 195 connectWs(); 198 - // Quick bridge probe — reports whether jweb~ has injected window.max yet 199 - // by the time boot runs. 200 - const initialBridge = !!maxBridge(); 201 - console.log(`🎹 boot bridge=${initialBridge}`); 202 - // And again one tick later in case jweb~ injects window.max just after boot. 203 - setTimeout(() => { 204 - console.log(`🎹 boot+50ms bridge=${!!maxBridge()}`); 205 - }, 50); 206 - setTimeout(() => { 207 - console.log(`🎹 boot+500ms bridge=${!!maxBridge()}`); 208 - }, 500); 209 196 } 210 197 211 198 function sim() { ··· 222 209 if (eventsSeen < 15) { 223 210 eventsSeen += 1; 224 211 const name = e.name || "?"; 225 - const bridge = !!maxBridge(); 226 - console.log(`🎹 evt#${eventsSeen} ${name} key=${e.key || ""} bridge=${bridge}`); 212 + console.log(`🎹 evt#${eventsSeen} ${name} key=${e.key || ""}`); 227 213 } 228 214 // Click-to-test-note: tap anywhere on the device UI fires C4 so we can 229 215 // verify the Max bridge path without depending on keyboard focus. ··· 261 247 const H = screen.height; 262 248 let y = 4; 263 249 264 - // Header — check bridge each frame in case jweb~ injected window.max late 265 - const bridgeActive = !!maxBridge(); 250 + // Header — bridge status is whether `send` was captured at boot. 251 + const bridgeActive = !!_send; 266 252 ink(...accent).write("NOTEPAT-REMOTE", { x: 4, y, size: 1 }); 267 253 ink(...(bridgeActive ? good : warn)).write( 268 254 bridgeActive ? "[M4L]" : "[solo]",
system/public/m4l/notepat-remote.amxd

This is a binary file and will not be displayed.