Compare commits

..

No commits in common. "4ceb80b4f47b01527dfdf3b3dbc5b682ac4ad2c9" and "6b508ab86efcbb9b61cba13ff1accd153bacac35" have entirely different histories.

4 changed files with 5 additions and 26 deletions

View file

@ -1 +1 @@
0.0.65 0.0.64

View file

@ -1117,14 +1117,8 @@ async function loadFromDevice() {
/* Device audio (Phase 3): a connected PM_K-1 sends a USB-MIDI note per click; we voice it through /* 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. */ this page's synth, so the device drives sound out the computer's speakers, locked to its clock. */
let _midiAccess = null, _midiOn = false, _midiFlash = 0, _midiBeat = 0; let _midiAccess = null, _midiOn = false, _midiFlash = 0;
function _midiInputs() { return _midiAccess ? [..._midiAccess.inputs.values()] : []; } function _midiInputs() { return _midiAccess ? [..._midiAccess.inputs.values()] : []; }
function _heartbeat(on) { // tell the device a host is listening, so it shows "MIDI" + mutes its buzzer
clearInterval(_midiBeat); _midiBeat = 0;
if (on) _midiBeat = setInterval(() => {
if (_midiAccess) for (const out of _midiAccess.outputs.values()) { try { out.send([0xFE]); } catch (_) {} } // Active Sensing
}, 250);
}
function onDeviceMidi(e) { function onDeviceMidi(e) {
const d = e.data; if (!d || d.length < 3) return; const d = e.data; if (!d || d.length < 3) return;
if ((d[0] & 0xf0) === 0x90 && d[2] > 0) { // Note On if ((d[0] & 0xf0) === 0x90 && d[2] > 0) { // Note On
@ -1142,12 +1136,12 @@ function updateMidiBtn() {
b.classList.add("primary"); b.classList.add("primary");
} }
async function toggleDeviceAudio() { async function toggleDeviceAudio() {
if (_midiOn) { _midiOn = false; _heartbeat(false); _bindMidi(); updateMidiBtn(); return; } 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."); 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(); updateMidiBtn(); }; } } try { if (!_midiAccess) { _midiAccess = await navigator.requestMIDIAccess(); _midiAccess.onstatechange = () => { _bindMidi(); updateMidiBtn(); }; } }
catch (e) { return alert("MIDI access was denied."); } catch (e) { return alert("MIDI access was denied."); }
ensureAudio(); if (audioCtx && audioCtx.state === "suspended") audioCtx.resume(); ensureAudio(); if (audioCtx && audioCtx.state === "suspended") audioCtx.resume();
_midiOn = true; _heartbeat(true); _bindMidi(); updateMidiBtn(); _midiOn = true; _bindMidi(); updateMidiBtn();
const names = _midiInputs().map((i) => i.name || "MIDI"); const names = _midiInputs().map((i) => i.name || "MIDI");
alert(names.length 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 ON.\nMIDI input(s): " + names.join(", ") + "\nPress play on the device — the button pulses green on each note received."

View file

@ -289,8 +289,6 @@ class App:
self.i2c = busio.I2C(scl=P_SCL, sda=P_SDA, frequency=400_000) self.i2c = busio.I2C(scl=P_SCL, sda=P_SDA, frequency=400_000)
self.touch = GT911(self.i2c) self.touch = GT911(self.i2c)
self.midi = usb_midi.ports[1] if (MIDI_ENABLED and usb_midi and len(usb_midi.ports) > 1) else None self.midi = usb_midi.ports[1] if (MIDI_ENABLED and usb_midi and len(usb_midi.ports) > 1) else None
self.midi_in = usb_midi.ports[0] if (MIDI_ENABLED and usb_midi and len(usb_midi.ports) > 0) else None
self._mbuf = bytearray(16); self.midi_host = False; self.last_midi_in = 0.0
self.led = RGB(P_RGB) self.led = RGB(P_RGB)
self.buz = pwmio.PWMOut(P_BUZ, frequency=1600, variable_frequency=True, duty_cycle=0) self.buz = pwmio.PWMOut(P_BUZ, frequency=1600, variable_frequency=True, duty_cycle=0)
self.buz_off = 0 self.buz_off = 0
@ -323,7 +321,6 @@ class App:
self.g_bpm = displayio.Group(); root.append(self.g_bpm) # big tempo (right) self.g_bpm = displayio.Group(); root.append(self.g_bpm) # big tempo (right)
self.g_run = displayio.Group(); root.append(self.g_run) # RUN / STOP (left) self.g_run = displayio.Group(); root.append(self.g_run) # RUN / STOP (left)
self.g_name = displayio.Group(); root.append(self.g_name) # item index + name self.g_name = displayio.Group(); root.append(self.g_name) # item index + name
self.g_midi = displayio.Group(); root.append(self.g_midi) # "MIDI" indicator (top-right) when a host is listening
self.g_grid = displayio.Group(); root.append(self.g_grid) # lanes × step pads self.g_grid = displayio.Group(); root.append(self.g_grid) # lanes × step pads
# buttons (rects static; labels in per-button groups so play can toggle) # buttons (rects static; labels in per-button groups so play can toggle)
bw, bh = 96, 56; gap = (WIDTH - 3*bw)//4; xs = [gap, gap*2+bw, gap*3+bw*2] bw, bh = 96, 56; gap = (WIDTH - 3*bw)//4; xs = [gap, gap*2+bw, gap*3+bw*2]
@ -429,7 +426,7 @@ class App:
if adv and li < len(self.lane_pads): self._move_playhead(li, L['step']) if adv and li < len(self.lane_pads): self._move_playhead(li, L['step'])
if fired: if fired:
best = max(fired, key=lambda l: PRIO.get(l, 0)) best = max(fired, key=lambda l: PRIO.get(l, 0))
if not MUTE_BUZZER and not self.midi_host: self.click(best) # computer plays it instead if not MUTE_BUZZER: self.click(best)
self.flash(best) self.flash(best)
if self.rgb != (0, 0, 0): if self.rgb != (0, 0, 0):
r, g, b = self.rgb; r = r*7//10; g = g*7//10; b = b*7//10 r, g, b = self.rgb; r = r*7//10; g = g*7//10; b = b*7//10
@ -464,16 +461,6 @@ class App:
self._touchDown = True; self.hit(pt[0], pt[1]) self._touchDown = True; self.hit(pt[0], pt[1])
elif self._touchDown and (nowms - self._touchSeen) > 0.14: elif self._touchDown and (nowms - self._touchSeen) > 0.14:
self._touchDown = False self._touchDown = False
# MIDI host present? the editor sends a heartbeat (Active Sensing) while "Device audio" is on
if self.midi_in is not None:
try:
if self.midi_in.readinto(self._mbuf): self.last_midi_in = nowms
except Exception: pass
host = bool(self.last_midi_in) and (nowms - self.last_midi_in) < 1.0
if host != self.midi_host:
self.midi_host = host
if host: self.buz.duty_cycle = 0 # silence the buzzer when the computer takes over
self.led_off(); self.draw_midi()
def hit(self, x, y): def hit(self, x, y):
for bx, by, bw, bh, key in self.buttons: for bx, by, bw, bh, key in self.buttons:
@ -494,8 +481,6 @@ class App:
C_GREEN if self.running else C_MUTE, C_BG, FONT_M) C_GREEN if self.running else C_MUTE, C_BG, FONT_M)
self._place(self.g_name, "%d/%d %s" % (self.idx+1, len(self.programs), self.name[:18]), self._place(self.g_name, "%d/%d %s" % (self.idx+1, len(self.programs), self.name[:18]),
12, 112, C_TXT, C_BG, FONT_M) 12, 112, C_TXT, C_BG, FONT_M)
def draw_midi(self):
self._place(self.g_midi, "MIDI" if self.midi_host else "", 0, 12, C_GREEN, C_BG, FONT_M, right_edge=WIDTH-12)
# ---------- pad grid (each lane = a row of step pads; playhead lit as it plays) ---------- # ---------- pad grid (each lane = a row of step pads; playhead lit as it plays) ----------
def _padbase(self, L, s): def _padbase(self, L, s):