diff --git a/ac-m4l/AC-KnobMap.amxd.json b/ac-m4l/AC-KnobMap.amxd.json index ca464fa02..2a78fb68e 100644 --- a/ac-m4l/AC-KnobMap.amxd.json +++ b/ac-m4l/AC-KnobMap.amxd.json @@ -166,7 +166,7 @@ 200.0, 22.0 ], - "text": "script window.postMessage({type:'spreadnob:ready'},'*')" + "text": "script window.acSnReady&&window.acSnReady()" } }, { @@ -983,7 +983,7 @@ 230.0, 22.0 ], - "text": "script window.postMessage({type:'spreadnob:range',low:$1,high:$2},'*')" + "text": "script window.acSnNote&&window.acSnNote($1)" } }, { @@ -1001,7 +1001,7 @@ 205.0, 22.0 ], - "text": "script window.postMessage({type:'spreadnob:note',note:$1},'*')" + "text": "script window.acSnNote&&window.acSnNote($1)" } }, { @@ -1019,7 +1019,7 @@ 215.0, 22.0 ], - "text": "script window.postMessage({type:'spreadnob:value',value:$1},'*')" + "text": "script window.acSnValue&&window.acSnValue($1)" } }, { @@ -1037,7 +1037,7 @@ 220.0, 22.0 ], - "text": "script window.postMessage({type:'spreadnob:active',active:$1},'*')" + "text": "script window.acSnActive&&window.acSnActive($1)" } }, { @@ -1073,7 +1073,7 @@ 255.0, 22.0 ], - "text": "script window.postMessage({type:'spreadnob:target',name:'$1'},'*')" + "text": "script window.acSnTarget&&window.acSnTarget('$1')" } }, { @@ -1205,7 +1205,7 @@ 215.0, 22.0 ], - "text": "script window.postMessage({type:'spreadnob:min',min:$1},'*')" + "text": "script window.acSnMin&&window.acSnMin($1)" } }, { @@ -1223,7 +1223,7 @@ 215.0, 22.0 ], - "text": "script window.postMessage({type:'spreadnob:max',max:$1},'*')" + "text": "script window.acSnMax&&window.acSnMax($1)" } }, { diff --git a/system/netlify/functions/index.mjs b/system/netlify/functions/index.mjs index be17c6334..4ed74ec3a 100644 --- a/system/netlify/functions/index.mjs +++ b/system/netlify/functions/index.mjs @@ -936,13 +936,14 @@ async function fun(event, context) { console.log("🎹 Max for Live detected - DAW sync connected"); }; - // 🎛️ Spreadnob bridge — catches postMessage from M4L script commands - window.addEventListener("message", function(event) { - var d = event.data; - if (d && d.type && typeof d.type === "string" && d.type.indexOf("spreadnob:") === 0) { - send({ type: d.type, content: d }); - } - }); + // 🎛️ Spreadnob bridge — called directly by M4L script commands + window.acSnNote = function(n) { send({ type: "spreadnob:note", content: { note: n } }); }; + window.acSnTarget = function(name) { send({ type: "spreadnob:target", content: { name: name } }); }; + window.acSnValue = function(v) { send({ type: "spreadnob:value", content: { value: v } }); }; + window.acSnActive = function(v) { send({ type: "spreadnob:active", content: { active: v } }); }; + window.acSnMin = function(v) { send({ type: "spreadnob:min", content: { min: v } }); }; + window.acSnMax = function(v) { send({ type: "spreadnob:max", content: { max: v } }); }; + window.acSnReady = function() { send({ type: "spreadnob:ready", content: {} }); }; // Signal to M4L that we're ready if (window.max) { diff --git a/system/public/aesthetic.computer/bios.mjs b/system/public/aesthetic.computer/bios.mjs index 873cf4e12..7639d9d8a 100644 --- a/system/public/aesthetic.computer/bios.mjs +++ b/system/public/aesthetic.computer/bios.mjs @@ -103,18 +103,20 @@ let _dawSampleRate = null; // 🎹 Store DAW sample rate for AudioContext creati let frozenUrlPath = null; // Called from boot() to connect the send function +// NOTE: sendFunc is captured by VALUE at call time, but `send` may be reassigned +// later (e.g. noWorker fallback). Use window.acSEND as the live reference. function _dawConnectSend(sendFunc, updateMetronome) { if (window.acDawConnect) { - // Wrap sendFunc to also update metronome and capture sample rate const wrappedSend = (msg) => { if (msg.type === "daw:tempo" && updateMetronome) { updateMetronome(msg.content.bpm); } - // 🎹 Capture DAW sample rate for AudioContext creation if (msg.type === "daw:samplerate" && msg.content?.samplerate) { _dawSampleRate = msg.content.samplerate; } - sendFunc(msg); + // Use window.acSEND (always current) with sendFunc as fallback + const currentSend = window.acSEND || sendFunc; + currentSend(msg); }; window.acDawConnect(wrappedSend); } diff --git a/system/public/aesthetic.computer/disks/spreadnob.mjs b/system/public/aesthetic.computer/disks/spreadnob.mjs index 7ab1e0ac6..661c65afc 100644 --- a/system/public/aesthetic.computer/disks/spreadnob.mjs +++ b/system/public/aesthetic.computer/disks/spreadnob.mjs @@ -68,42 +68,49 @@ function arcXY(cx, cy, r, deg) { return [cx + r * Math.cos(rad), cy - r * Math.sin(rad)]; } -function boot() {} +let simCount = 0; +let dawExists = false; +let dawKeys = ""; + +function boot({ needsPaint }) { needsPaint(); } function sim({ sound, needsPaint }) { + simCount++; let dirty = false; - const sn = sound?.spreadnob; - if (sn) { - if (sn.active !== null && sn.active !== undefined) { - active = !!Number(sn.active); - sn.active = null; + // Read spreadnob data from sound.daw (stored as sn* props on persistentDawState) + const daw = sound?.daw; + dawExists = !!daw; + if (daw) { + if (daw.snActive !== undefined && daw.snActive !== null) { + active = !!Number(daw.snActive); + daw.snActive = null; dirty = true; } - if (sn.target !== null && sn.target !== undefined) { - target = String(sn.target).trim(); - sn.target = null; + if (daw.snTarget !== undefined && daw.snTarget !== null) { + target = String(daw.snTarget).trim(); + daw.snTarget = null; dirty = true; } - if (sn.min !== null && sn.min !== undefined) { - const n = Number(sn.min); + if (daw.snMin !== undefined && daw.snMin !== null) { + const n = Number(daw.snMin); if (Number.isFinite(n)) paramMin = n; - sn.min = null; + daw.snMin = null; dirty = true; } - if (sn.max !== null && sn.max !== undefined) { - const n = Number(sn.max); + if (daw.snMax !== undefined && daw.snMax !== null) { + const n = Number(daw.snMax); if (Number.isFinite(n)) paramMax = n; - sn.max = null; + daw.snMax = null; dirty = true; } - if (sn.value !== null && sn.value !== undefined) { - const n = Number(sn.value); + if (daw.snValue !== undefined && daw.snValue !== null) { + const n = Number(daw.snValue); value = Number.isFinite(n) ? n : null; - sn.value = null; + daw.snValue = null; dirty = true; } - if (sn.note !== null && sn.note !== undefined) { - const n = Number(sn.note); + if (daw.snNote !== undefined && daw.snNote !== null) { + const n = Number(daw.snNote); if (n !== lastSnNote) { currentNote = Number.isFinite(n) ? n : null; lastSnNote = n; @@ -116,6 +123,9 @@ function sim({ sound, needsPaint }) { dirty = true; } } + if (simCount % 60 === 0) { + dawKeys = Object.keys(daw).filter(k => k.startsWith("sn") && daw[k] !== null).join(","); + } } if (flash > 0) { @@ -123,7 +133,7 @@ function sim({ sound, needsPaint }) { if (flash < 0.025) flash = 0; dirty = true; } - if (dirty) needsPaint(); + needsPaint(); // Always repaint for debug } function paint({ wipe, ink, screen }) { @@ -221,6 +231,9 @@ function paint({ wipe, ink, screen }) { // Title ink(200, 120, 170).write("spreadnob", { x: 6, y: 4 }, undefined, undefined, false, MINI_FONT); + + // Debug info + ink(255, 255, 0).write(`sim:${simCount} daw:${dawExists} keys:${dawKeys || "none"}`, { x: 6, y: h - 8 }, undefined, undefined, false, MINI_FONT); } function act({ event, screen, needsPaint }) { diff --git a/system/public/aesthetic.computer/lib/disk.mjs b/system/public/aesthetic.computer/lib/disk.mjs index dc452b9ae..0546a5f11 100644 --- a/system/public/aesthetic.computer/lib/disk.mjs +++ b/system/public/aesthetic.computer/lib/disk.mjs @@ -9798,8 +9798,8 @@ let pendingExportEvents = []; async function makeFrame({ data: { type, content } }) { // DEBUG: Log ALL messages starting with "pedal:" or "daw:" - if (type?.startsWith?.("pedal:") || type?.startsWith?.("daw:")) { - console.log("🎹🎹🎹 makeFrame received:", type, content); + if (type?.startsWith?.("pedal:") || type?.startsWith?.("daw:") || type?.startsWith?.("spreadnob:")) { + console.log("🎹🎹🎹 makeFrame received:", type, JSON.stringify(content)); } // Handle permission responses from bios.mjs