From aaf5c4d2600c5b72c3459e23acfe3e0582d14518 Mon Sep 17 00:00:00 2001 From: Me Here Date: Thu, 28 May 2026 22:55:57 -0500 Subject: [PATCH] editor: MIDI 'Device audio' diagnostics (show device name + pulse on note) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User reported no computer audio + 'no device being controlled'. Add visibility to diagnose: the button now shows the connected MIDI input's name (or 'no device'), the toggle alert lists detected inputs, and the button pulses green on each Note-On received — so it's clear whether the device is seen and whether notes are arriving. Also call ensureAudio() in the message handler as a guard. Co-Authored-By: Claude Opus 4.7 (1M context) --- editor.html | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/editor.html b/editor.html index d9f4ce4..83ca38a 100644 --- a/editor.html +++ b/editor.html @@ -1117,25 +1117,35 @@ async function loadFromDevice() { /* Device audio (Phase 3): a connected PM_K-1 sends a USB-MIDI note per click; we voice it through this page's synth, so the device drives sound out the computer's speakers, locked to its clock. */ -let _midiAccess = null, _midiOn = false; +let _midiAccess = null, _midiOn = false, _midiFlash = 0; +function _midiInputs() { return _midiAccess ? [..._midiAccess.inputs.values()] : []; } function onDeviceMidi(e) { const d = e.data; if (!d || d.length < 3) return; if ((d[0] & 0xf0) === 0x90 && d[2] > 0) { // Note On const v = d[2], gain = v >= 110 ? 1.0 : v >= 70 ? 0.6 : 0.25; // accent / normal / ghost - try { playInstrument(GM_NUM[d[1]] || "beep", audioCtx.currentTime, gain); } catch (_) {} + try { ensureAudio(); playInstrument(GM_NUM[d[1]] || "beep", audioCtx.currentTime, gain); } catch (_) {} + const b = $("midiBtn"); if (b) { b.style.boxShadow = "0 0 0 2px #2fe07a"; clearTimeout(_midiFlash); _midiFlash = setTimeout(() => b.style.boxShadow = "", 90); } } } -function _bindMidi() { if (_midiAccess) for (const inp of _midiAccess.inputs.values()) inp.onmidimessage = _midiOn ? onDeviceMidi : null; } -function updateMidiBtn() { const b = $("midiBtn"); if (b) { b.textContent = _midiOn ? "🎹 Device audio: ON" : "🎹 Device audio"; b.classList.toggle("primary", _midiOn); } } +function _bindMidi() { for (const inp of _midiInputs()) inp.onmidimessage = _midiOn ? onDeviceMidi : null; } +function updateMidiBtn() { + const b = $("midiBtn"); if (!b) return; + if (!_midiOn) { b.textContent = "🎹 Device audio"; b.classList.remove("primary"); b.style.boxShadow = ""; return; } + const names = _midiInputs().map((i) => i.name || "MIDI"); + b.textContent = names.length ? "🎹 " + names[0].slice(0, 16) : "🎹 no device"; // shows the connected MIDI device + b.classList.add("primary"); +} async function toggleDeviceAudio() { if (_midiOn) { _midiOn = false; _bindMidi(); updateMidiBtn(); return; } if (!navigator.requestMIDIAccess) return alert("Playing the device through this computer needs the Web MIDI API — use Chrome or Edge."); - try { if (!_midiAccess) { _midiAccess = await navigator.requestMIDIAccess(); _midiAccess.onstatechange = _bindMidi; } } + try { if (!_midiAccess) { _midiAccess = await navigator.requestMIDIAccess(); _midiAccess.onstatechange = () => { _bindMidi(); updateMidiBtn(); }; } } catch (e) { return alert("MIDI access was denied."); } ensureAudio(); if (audioCtx && audioCtx.state === "suspended") audioCtx.resume(); _midiOn = true; _bindMidi(); updateMidiBtn(); - if (![..._midiAccess.inputs.values()].length) - alert("No MIDI device detected yet — plug in the PM_K-1 (CircuitPython firmware) and press play on it. (It stays armed; new devices connect automatically.)"); + const names = _midiInputs().map((i) => i.name || "MIDI"); + alert(names.length + ? "Device audio ON.\nMIDI input(s): " + names.join(", ") + "\nPress play on the device — the button pulses green on each note received." + : "Device audio is armed, but NO MIDI input is connected.\nPlug in the PM_K-1 running the CircuitPython firmware — it should appear here as a MIDI device. (New devices connect automatically.)"); } // Apply a shared link on load. Returns true if it set the metronome state.