Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

notepat-remote: move max bridge to bios (pieces run in workers)

Pieces run inside a Web Worker, so window.acSetLiveFocus /
acSetLiveTrackColor defined in the piece were no-ops — they lived in
the worker's global scope, not jweb's main-thread window where Max's
executejavascript actually runs. And the piece was listening for
e.is("blur") but disk.mjs dispatches focus-change as
focus/defocus, so the event path never fired either.

Bridge moved into bios.mjs (main thread, M4L-only):
• window.acSetLiveFocus(N) → send({type:"focus-change", content:!!N})
→ existing disk.mjs dispatch as e.is("focus")/e.is("defocus")
• window.acSetLiveTrackColor(N) → send({type:"live:track-color",
content:[r,g,b]}) → new disk.mjs dispatch as e.is("live:track-color")
with e.trackColor
• daw:request-focus / daw:request-track-color types forward to
window.max.outlet so the piece can ask Max to re-emit post-boot

Piece now uses e.is("defocus") (not "blur"), listens for
e.is("live:track-color"), sends the re-request messages via send(),
and drops all the worker-broken window/document code + poll.

+87 -68
+40
system/public/aesthetic.computer/bios.mjs
··· 4616 4616 for (const k of Object.keys(_dawHeldPitch)) delete _dawHeldPitch[k]; 4617 4617 _dawEmitMax("focus", 0); 4618 4618 }, true); 4619 + 4620 + // 🎹 Max → piece bridge. Patcher calls these via 4621 + // `executejavascript window.acSetLiveFocus(N)` / 4622 + // `executejavascript window.acSetLiveTrackColor(N)`. 4623 + // The piece runs in a Web Worker so it can't define these itself; 4624 + // we forward the state in via the same `send()` used for other 4625 + // main→worker messages. Existing "focus-change" path surfaces as 4626 + // e.is("focus") / e.is("defocus") in the piece's act(). 4627 + window.acSetLiveFocus = (f) => { 4628 + const focused = !!f; 4629 + console.log(`🎹 acSetLiveFocus ${focused ? "ACTIVE" : "inactive"}`); 4630 + try { send({ type: "focus-change", content: focused }); } catch (_e) {} 4631 + }; 4632 + window.acSetLiveTrackColor = (colorInt) => { 4633 + const n = Number(colorInt) >>> 0; 4634 + if (!Number.isFinite(n)) { 4635 + console.log(`🎹 acSetLiveTrackColor ignored (bad ${colorInt})`); 4636 + return; 4637 + } 4638 + const rgb = [(n >> 16) & 255, (n >> 8) & 255, n & 255]; 4639 + console.log(`🎹 acSetLiveTrackColor ${n} → rgb(${rgb.join(",")})`); 4640 + try { send({ type: "live:track-color", content: rgb }); } catch (_e) {} 4641 + }; 4619 4642 } 4620 4643 4621 4644 function requestBeat(time) { ··· 5279 5302 if (!Number.isFinite(pitch) || !Number.isFinite(velocity)) return; 5280 5303 try { window.max.outlet("channel", channel); } catch (_e) {} 5281 5304 try { window.max.outlet("note", pitch, velocity); } catch (_e) {} 5305 + } 5306 + return; 5307 + } 5308 + 5309 + // 🎹 Piece → Max: ask max to re-emit state values whose initial 5310 + // fire happened before the piece mounted its handlers (focus, 5311 + // track color). Matches the `route ready goonline requestTrackColor 5312 + // requestFocus …` outlets on the patcher's route-top. 5313 + if (type === "daw:request-focus") { 5314 + if (typeof window !== "undefined" && window.max?.outlet) { 5315 + try { window.max.outlet("requestFocus", 1); } catch (_e) {} 5316 + } 5317 + return; 5318 + } 5319 + if (type === "daw:request-track-color") { 5320 + if (typeof window !== "undefined" && window.max?.outlet) { 5321 + try { window.max.outlet("requestTrackColor", 1); } catch (_e) {} 5282 5322 } 5283 5323 return; 5284 5324 }
+28 -68
system/public/aesthetic.computer/disks/notepat-remote.mjs
··· 105 105 // device's parent track. Null until that lands; paint falls back to the 106 106 // rainbow/ambient scheme when it's null so this stays optional. 107 107 let liveTrackColor = null; 108 - let lastFocusPollFrame = -9999; 109 108 110 109 // Button grid layout (recomputed on each paint in case screen size changes). 111 110 let buttons = []; ··· 220 219 _send = send; 221 220 connectWs(); 222 221 223 - // ── Focus detection ──────────────────────────────────────────────── 224 - // jweb inside Max for Live doesn't always surface DOM blur/focus as AC 225 - // events. We listen on four paths: (1) AC act() events, (2) native 226 - // window blur/focus, (3) document visibilitychange, (4) Max [active] 227 - // object pushing via window.acSetLiveFocus. Each transition logs its 228 - // source so the Max Console shows which hook actually catches it. 229 - if (typeof window !== "undefined") { 230 - const setFocus = (f, source) => { 231 - if (focused !== f) { 232 - focused = f; 233 - focusedChangedFrame = frame; 234 - if (!f) heldKeys.clear(); 235 - try { console.log(`[focus] ${source} → ${f ? "ACTIVE" : "inactive"}`); } catch {} 236 - } 237 - }; 238 - try { 239 - window.addEventListener("blur", () => setFocus(false, "window.blur")); 240 - window.addEventListener("focus", () => setFocus(true, "window.focus")); 241 - document.addEventListener?.("visibilitychange", () => 242 - setFocus(!document.hidden, "document.visibilitychange")); 243 - } catch {} 244 - // Max side pushes focus + theme state via these globals. Defining 245 - // them unconditionally lets the patcher call them whenever it likes. 246 - window.acSetLiveFocus = (f) => { 247 - try { console.log(`[focus] max.active received ${f}`); } catch {} 248 - setFocus(!!f, "max.active"); 249 - }; 250 - window.acSetLiveTrackColor = (colorInt) => { 251 - const n = Number(colorInt) >>> 0; 252 - if (!Number.isFinite(n)) { 253 - try { console.log(`[track-color] max push ignored (bad int ${colorInt})`); } catch {} 254 - return; 255 - } 256 - liveTrackColor = [(n >> 16) & 255, (n >> 8) & 255, n & 255]; 257 - try { 258 - console.log( 259 - `[track-color] max push ${n} → rgb(${liveTrackColor[0]},${liveTrackColor[1]},${liveTrackColor[2]})`, 260 - ); 261 - } catch {} 262 - }; 263 - // Pull the current track color now — the Max patcher's 264 - // `live.observer` fires once on device load (before this boot 265 - // runs), so without an explicit re-request we'd never see it. 266 - try { window.max?.outlet?.("requestTrackColor", 1); } catch {} 267 - // Same story for the [active] focus signal — the initial fire 268 - // can happen before jweb navigates to the live URL. 269 - try { window.max?.outlet?.("requestFocus", 1); } catch {} 270 - } 222 + // ── Re-request Max-side state from the piece (worker) side ─────── 223 + // Pieces run in a Web Worker — no window/document here. The bios.mjs 224 + // main-thread bridge defines window.acSetLiveFocus / Live track 225 + // color handlers that forward into the worker via send() as 226 + // "focus-change" + "live:track-color" messages, which disk.mjs 227 + // dispatches as act() events. On boot we ask Max to re-emit both 228 + // because their initial fires can land before we've mounted. 229 + send({ type: "daw:request-focus" }); 230 + send({ type: "daw:request-track-color" }); 271 231 // Also open AC's session-scoped socket + udp purely so the transport 272 232 // status indicator can show UDP connectivity. Callbacks are no-ops — 273 233 // the notepat:midi subscription flows through the raw WS above. ··· 298 258 frame += 1; 299 259 if (wsState === "closed" && frame >= reconnectAt) connectWs(); 300 260 301 - // Poll document.hasFocus() every ~250ms as a safety net — some jweb 302 - // focus transitions (user clicks the Live mixer header, browses a 303 - // menu) don't dispatch blur/focus events. Polling catches those. 304 - if (frame - lastFocusPollFrame > 15) { 305 - lastFocusPollFrame = frame; 306 - if (typeof document !== "undefined" && typeof document.hasFocus === "function") { 307 - const hf = document.hasFocus(); 308 - if (hf !== focused) { 309 - focused = hf; 310 - focusedChangedFrame = frame; 311 - if (!hf) heldKeys.clear(); 312 - try { console.log(`[focus] document.hasFocus() → ${hf ? "ACTIVE" : "inactive"}`); } catch {} 313 - } 314 - } 315 - } 261 + // (Worker has no document — focus state lands exclusively via the 262 + // "focus-change" event dispatched by disk.mjs from bios's main-thread 263 + // bridge, driven by Max [active] or DOM focus/blur.) 316 264 317 265 // Subscribe over UDP once the geckos channel comes up. Lost connection 318 266 // resets the flag so we re-subscribe on reconnect. ··· 344 292 function act({ event: e }) { 345 293 if (!e?.is) return; 346 294 347 - // Focus/blur signals from AC (if AC forwards them; harmless if it doesn't). 295 + // Focus/defocus from disk.mjs's "focus-change" dispatch. bios pushes 296 + // this both from DOM window focus/blur AND from Max's [active] via 297 + // window.acSetLiveFocus, so a single event handler covers all paths. 348 298 if (e.is("focus")) { 349 299 if (!focused) { 350 300 focused = true; 351 301 focusedChangedFrame = frame; 352 - try { console.log("[focus] ac-event:focus → ACTIVE"); } catch {} 302 + console.log("[focus] focus-change → ACTIVE"); 353 303 } 354 304 return; 355 305 } 356 - if (e.is("blur")) { 306 + if (e.is("defocus")) { 357 307 if (focused) { 358 308 focused = false; 359 309 focusedChangedFrame = frame; 360 310 heldKeys.clear(); 361 - try { console.log("[focus] ac-event:blur → inactive"); } catch {} 311 + console.log("[focus] focus-change → inactive"); 312 + } 313 + return; 314 + } 315 + 316 + // Live track color pushed by Max via bios → disk dispatch. 317 + if (e.is("live:track-color")) { 318 + const rgb = e.trackColor; 319 + if (Array.isArray(rgb) && rgb.length === 3) { 320 + liveTrackColor = rgb.slice(); 321 + console.log(`[track-color] received rgb(${rgb.join(",")})`); 362 322 } 363 323 return; 364 324 }
+19
system/public/aesthetic.computer/lib/disk.mjs
··· 10696 10696 } 10697 10697 } 10698 10698 10699 + // Max for Live → piece: track color push (bios forwards as this 10700 + // message type after Max's live.observer fires into jweb via 10701 + // window.acSetLiveTrackColor). Pieces read e.trackColor in act(). 10702 + if (type === "live:track-color") { 10703 + if (!cachedAPI) return; 10704 + const $api = cachedAPI; 10705 + const data = { 10706 + device: "live", 10707 + trackColor: content, 10708 + is: (e) => e === "live:track-color", 10709 + }; 10710 + $api.event = data; 10711 + try { 10712 + act($api); 10713 + } catch (e) { 10714 + console.warn("️ ✒ Act failure (live:track-color)", e); 10715 + } 10716 + } 10717 + 10699 10718 if (type === "visibility-change") { 10700 10719 // 🧨 Just in case of a regression... 23.06.02.21.12 10701 10720 // Because the `bios` focus event changed from visibility behavior.
system/public/m4l/notepat.com.amxd

This is a binary file and will not be displayed.