Phase 3: USB-MIDI audio — play the device through the computer's speakers
Firmware (pico-cp/code.py): on every click, send a USB-MIDI note-on per firing lane —
GM drum note by voice (SOUND_GM), velocity by level (accent/normal/ghost) — via the
default-enabled usb_midi.ports[1]. Polyphonic, so the computer plays the full groove.
New CONFIG: MIDI_ENABLED (default on), MUTE_BUZZER (silence the buzzer when using
computer audio).
Editor (editor.html): a '🎹 Device audio' toggle uses the Web MIDI API
(requestMIDIAccess) to voice incoming notes through the existing synth — Note-On ->
GM_NUM[note] / velocity-to-gain -> playInstrument(). The device is the clock; the
browser is the sound module, locked in sync. Chrome/Edge.
Verified: firmware emits the right notes (kick+hat on beat 1 of four-on-the-floor,
snare's rest skipped); editor loads clean with the toggle + handlers present. Docs
(info-kit, both READMEs) updated. The on-device buzzer/screen still work standalone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d9a2be7389
commit
7ccc75e399
6 changed files with 75 additions and 9 deletions
10
README.md
10
README.md
|
|
@ -185,7 +185,12 @@ flashing steps. Firmware lives in **`pico/`**:
|
||||||
WS2812 RGB, PWM buzzer, ADC joystick, baked anti‑aliased fonts, and the polymeter engine.
|
WS2812 RGB, PWM buzzer, ADC joystick, baked anti‑aliased fonts, and the polymeter engine.
|
||||||
It parses the same program strings as the web editor. Flash MicroPython, copy `main.py`,
|
It parses the same program strings as the web editor. Flash MicroPython, copy `main.py`,
|
||||||
edit the `PROGRAMS` list to change grooves. Download: `/pico-main.py`.
|
edit the `PROGRAMS` list to change grooves. Download: `/pico-main.py`.
|
||||||
- **`pico/gen_font.py`** — generates the baked anti‑aliased fonts embedded in the firmware.
|
- **`pico/gen_font.py`** — generates the baked anti‑aliased fonts (used by both firmwares).
|
||||||
|
- **`pico-cp/`** — a **CircuitPython** edition (download `/pm_k1_circuitpy.zip`): the Pico mounts as a
|
||||||
|
USB drive carrying the firmware + your `programs.json` + a copy of the editor, with a full lanes/pads
|
||||||
|
touchscreen display. Design grooves on the web and **Save to device** straight onto the drive (the
|
||||||
|
editor's ⋯ menu), and play it **out your computer's speakers over USB‑MIDI** (the editor's
|
||||||
|
**🎹 Device audio** button). The MicroPython build stays the simple, no‑computer option.
|
||||||
|
|
||||||
## Keyboard shortcuts
|
## Keyboard shortcuts
|
||||||
|
|
||||||
|
|
@ -261,7 +266,8 @@ tags the current commit `v<VERSION>` (requires a clean tree). Push the tag, then
|
||||||
| `embed.html` · `embed.js` | embed docs and the drop‑in widget loader |
|
| `embed.html` · `embed.js` | embed docs and the drop‑in widget loader |
|
||||||
| `src/` | shared partials inlined into every page: `engine.js`, `setlists.js`, `base.css`, `header.html`, `footer.html`, `chrome.js`, `progbox.{html,js}`, `infoembed.{html,js}` |
|
| `src/` | shared partials inlined into every page: `engine.js`, `setlists.js`, `base.css`, `header.html`, `footer.html`, `chrome.js`, `progbox.{html,js}`, `infoembed.{html,js}` |
|
||||||
| `assets/` | base64 blobs inlined at build (`favicon`, `logo-dark`, `logo-light`) |
|
| `assets/` | base64 blobs inlined at build (`favicon`, `logo-dark`, `logo-light`) |
|
||||||
| `pico/` | PM_K‑1 firmware: `main.py` (MicroPython), `gen_font.py` (font generator), `README.md` |
|
| `pico/` | PM_K‑1 MicroPython firmware: `main.py`, `gen_font.py` (font generator), `README.md` |
|
||||||
|
| `pico-cp/` | PM_K‑1 CircuitPython edition: `code.py`, `programs.json`, `font_*.bin`, `README.md` (bundled + served as `/pm_k1_circuitpy.zip`) |
|
||||||
| `build.sh` | resolve markers → self‑contained `dist/` pages (+ `pico-main.py`) |
|
| `build.sh` | resolve markers → self‑contained `dist/` pages (+ `pico-main.py`) |
|
||||||
| `deploy.sh` | build, then publish to the Caddy web root |
|
| `deploy.sh` | build, then publish to the Caddy web root |
|
||||||
| `release.sh` | tag a formal version |
|
| `release.sh` | tag a formal version |
|
||||||
|
|
|
||||||
26
editor.html
26
editor.html
|
|
@ -272,7 +272,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ctx" id="ctxDisplay"> </div>
|
<div class="ctx" id="ctxDisplay"> </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btnrow" style="margin-top:10px"><button class="primary" id="startBtn">▶ Start</button><button id="tapBtn">Tap</button><span id="saveItemWrap" style="display:inline-flex" title="Select a set-list item to enable Save"><button id="saveItemBtn" disabled>💾 Save</button></span></div>
|
<div class="btnrow" style="margin-top:10px"><button class="primary" id="startBtn">▶ Start</button><button id="tapBtn">Tap</button><span id="saveItemWrap" style="display:inline-flex" title="Select a set-list item to enable Save"><button id="saveItemBtn" disabled>💾 Save</button></span><button id="midiBtn" title="Play a connected PM_K-1 device through this computer's speakers (Web MIDI · Chrome/Edge)">🎹 Device audio</button></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="flex:1; min-width:200px">
|
<div style="flex:1; min-width:200px">
|
||||||
|
|
@ -1115,6 +1115,29 @@ async function loadFromDevice() {
|
||||||
inp.click();
|
inp.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
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 (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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); } }
|
||||||
|
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; } }
|
||||||
|
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.)");
|
||||||
|
}
|
||||||
|
|
||||||
// Apply a shared link on load. Returns true if it set the metronome state.
|
// Apply a shared link on load. Returns true if it set the metronome state.
|
||||||
function applyHashShare() {
|
function applyHashShare() {
|
||||||
const h = location.hash || "";
|
const h = location.hash || "";
|
||||||
|
|
@ -1340,6 +1363,7 @@ $("importBtn").addEventListener("click", () => { $("trayMenu").hidden = true; $(
|
||||||
$("importFile").addEventListener("change", (e) => { if (e.target.files[0]) importAll(e.target.files[0]); e.target.value = ""; });
|
$("importFile").addEventListener("change", (e) => { if (e.target.files[0]) importAll(e.target.files[0]); e.target.value = ""; });
|
||||||
$("saveDeviceBtn").addEventListener("click", () => { $("trayMenu").hidden = true; saveToDevice(); });
|
$("saveDeviceBtn").addEventListener("click", () => { $("trayMenu").hidden = true; saveToDevice(); });
|
||||||
$("loadDeviceBtn").addEventListener("click", () => { $("trayMenu").hidden = true; loadFromDevice(); });
|
$("loadDeviceBtn").addEventListener("click", () => { $("trayMenu").hidden = true; loadFromDevice(); });
|
||||||
|
$("midiBtn").addEventListener("click", toggleDeviceAudio);
|
||||||
$("clearLogBtn").addEventListener("click", () => { $("trayMenu").hidden = true; clearLog(); });
|
$("clearLogBtn").addEventListener("click", () => { $("trayMenu").hidden = true; clearLog(); });
|
||||||
$("resetAllBtn").addEventListener("click", () => { $("trayMenu").hidden = true; resetAll(); });
|
$("resetAllBtn").addEventListener("click", () => { $("trayMenu").hidden = true; resetAll(); });
|
||||||
$("shareSettingsBtn").addEventListener("click", () => { $("trayMenu").hidden = true; shareSettings(); });
|
$("shareSettingsBtn").addEventListener("click", () => { $("trayMenu").hidden = true; shareSettings(); });
|
||||||
|
|
|
||||||
|
|
@ -135,8 +135,9 @@
|
||||||
<p class="sub">An alternative firmware that makes the Pico mount as a <b>USB drive</b> carrying the
|
<p class="sub">An alternative firmware that makes the Pico mount as a <b>USB drive</b> carrying the
|
||||||
firmware, your tracks (<code>programs.json</code>) and a copy of this editor — design grooves on the
|
firmware, your tracks (<code>programs.json</code>) and a copy of this editor — design grooves on the
|
||||||
web and write them straight to the device, no Thonny. A full lanes/pads display with anti‑aliased
|
web and write them straight to the device, no Thonny. A full lanes/pads display with anti‑aliased
|
||||||
text drives the touchscreen. The MicroPython firmware above stays the simple, rock‑solid option.
|
text drives the touchscreen, and it plays out your <b>computer's speakers over USB‑MIDI</b> (the
|
||||||
(USB‑MIDI audio out to your computer's speakers is the next step.)</p>
|
editor's <b>🎹 Device audio</b> button voices it, locked to the device clock). The MicroPython
|
||||||
|
firmware above stays the simple, rock‑solid option.</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="dl" href="/pm_k1_circuitpy.zip" download>Download CircuitPython bundle ↓</a>
|
<a class="dl" href="/pm_k1_circuitpy.zip" download>Download CircuitPython bundle ↓</a>
|
||||||
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/pico-cp" target="_blank" rel="noopener">Source + README ↗</a>
|
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/pico-cp" target="_blank" rel="noopener">Source + README ↗</a>
|
||||||
|
|
@ -149,6 +150,8 @@
|
||||||
set‑list <b>⋯</b> menu → <b>📟 Save to device</b> → pick the <code>CIRCUITPY</code> drive. The Pico
|
set‑list <b>⋯</b> menu → <b>📟 Save to device</b> → pick the <code>CIRCUITPY</code> drive. The Pico
|
||||||
auto‑reloads with your grooves. (In Chrome/Edge it writes straight to the drive; otherwise it
|
auto‑reloads with your grooves. (In Chrome/Edge it writes straight to the drive; otherwise it
|
||||||
downloads <code>programs.json</code> to drag on.) <b>📥 Load from device</b> reads it back.</li>
|
downloads <code>programs.json</code> to drag on.) <b>📥 Load from device</b> reads it back.</li>
|
||||||
|
<li><b>Play through your computer:</b> in the editor (Chrome/Edge) click <b>🎹 Device audio</b>, then
|
||||||
|
press play on the device — its full groove sounds through your speakers over USB‑MIDI, in sync.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,14 @@ same program‑string language as <https://metronome.varasys.io>.
|
||||||
3. It starts immediately. Editing `programs.json` (or re‑saving it from the editor) makes CircuitPython
|
3. It starts immediately. Editing `programs.json` (or re‑saving it from the editor) makes CircuitPython
|
||||||
**auto‑reload** with the new tracks.
|
**auto‑reload** with the new tracks.
|
||||||
|
|
||||||
|
## Play through the computer's speakers (USB-MIDI)
|
||||||
|
|
||||||
|
The board also shows up as a **USB-MIDI** device and sends a note on every click (a GM drum note per
|
||||||
|
lane, velocity by accent). Open the [editor](https://metronome.varasys.io/editor.html) in **Chrome/Edge**,
|
||||||
|
click **🎹 Device audio**, grant MIDI access, then press play *on the device* — the editor voices the
|
||||||
|
groove through its full synth, out your computer's speakers, locked to the device's clock. Set
|
||||||
|
`MUTE_BUZZER = True` in `code.py` if you'd rather silence the on-board buzzer while doing this.
|
||||||
|
|
||||||
## Controls (same as the MicroPython build)
|
## Controls (same as the MicroPython build)
|
||||||
|
|
||||||
- **Touch:** on‑screen `◀◀ / ▶ / ▶▶` (prev · play/stop · next) and `− / TAP / +`.
|
- **Touch:** on‑screen `◀◀ / ▶ / ▶▶` (prev · play/stop · next) and `− / TAP / +`.
|
||||||
|
|
@ -51,6 +59,7 @@ on. **📥 Load from device** reads a `programs.json` back into a new set list.
|
||||||
- **Taps land wrong:** set `TOUCH_DEBUG = True`, watch the serial output, then set
|
- **Taps land wrong:** set `TOUCH_DEBUG = True`, watch the serial output, then set
|
||||||
`TOUCH_SWAP_XY` / `TOUCH_INVERT_X` / `TOUCH_INVERT_Y`.
|
`TOUCH_SWAP_XY` / `TOUCH_INVERT_X` / `TOUCH_INVERT_Y`.
|
||||||
- **Joystick reversed:** toggle `JOY_INVERT_X` / `JOY_INVERT_Y`.
|
- **Joystick reversed:** toggle `JOY_INVERT_X` / `JOY_INVERT_Y`.
|
||||||
|
- **Computer audio:** `MIDI_ENABLED` (default on) sends the MIDI notes; `MUTE_BUZZER` silences the buzzer.
|
||||||
- **LED too bright / too dim:** change `LED_BRIGHTNESS` (0..1, default 0.15).
|
- **LED too bright / too dim:** change `LED_BRIGHTNESS` (0..1, default 0.15).
|
||||||
- **Screen tearing:** the SPI panel has no tearing-effect sync; `SPI_BAUD` (default 62.5 MHz) is pushed fast
|
- **Screen tearing:** the SPI panel has no tearing-effect sync; `SPI_BAUD` (default 62.5 MHz) is pushed fast
|
||||||
to minimise it — lower it only if the display is unstable.
|
to minimise it — lower it only if the display is unstable.
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -4,8 +4,9 @@
|
||||||
#
|
#
|
||||||
# WHY CIRCUITPYTHON: the board then mounts as a USB drive (CIRCUITPY) carrying this code, your
|
# WHY CIRCUITPYTHON: the board then mounts as a USB drive (CIRCUITPY) carrying this code, your
|
||||||
# tracks (programs.json) and a copy of the editor — edit on the web, "Save to device" writes
|
# tracks (programs.json) and a copy of the editor — edit on the web, "Save to device" writes
|
||||||
# programs.json here, and CircuitPython auto-reloads with the new grooves. (USB-MIDI audio out
|
# programs.json here, and CircuitPython auto-reloads with the new grooves. It also sends USB-MIDI
|
||||||
# to the computer comes in a later phase.) Runs the SAME program strings as metronome.varasys.io.
|
# (a note per click) so the web editor can play it out the computer's speakers ("Device audio").
|
||||||
|
# Runs the SAME program strings as metronome.varasys.io.
|
||||||
#
|
#
|
||||||
# INSTALL: flash CircuitPython (https://circuitpython.org/board/raspberry_pi_pico/), then copy
|
# INSTALL: flash CircuitPython (https://circuitpython.org/board/raspberry_pi_pico/), then copy
|
||||||
# this file as code.py plus programs.json onto the CIRCUITPY drive. It runs on boot.
|
# this file as code.py plus programs.json onto the CIRCUITPY drive. It runs on boot.
|
||||||
|
|
@ -26,10 +27,16 @@ try:
|
||||||
import neopixel_write # core module on RP2040 — drives WS2812 with no external library
|
import neopixel_write # core module on RP2040 — drives WS2812 with no external library
|
||||||
except ImportError:
|
except ImportError:
|
||||||
neopixel_write = None
|
neopixel_write = None
|
||||||
|
try:
|
||||||
|
import usb_midi # default-enabled on RP2040 — sends a MIDI note per click to the computer
|
||||||
|
except ImportError:
|
||||||
|
usb_midi = None
|
||||||
|
|
||||||
# ============================== CONFIG (tweak if needed) ==============================
|
# ============================== CONFIG (tweak if needed) ==============================
|
||||||
SPI_BAUD = 62_500_000 # faster SPI = smaller tearing window; drop to 40_000_000 if unstable
|
SPI_BAUD = 62_500_000 # faster SPI = smaller tearing window; drop to 40_000_000 if unstable
|
||||||
LED_BRIGHTNESS = 0.15 # WS2812 sits right next to you — keep it dim (0..1)
|
LED_BRIGHTNESS = 0.15 # WS2812 sits right next to you — keep it dim (0..1)
|
||||||
|
MIDI_ENABLED = True # send a USB-MIDI note per click (play via the web editor's "Device audio")
|
||||||
|
MUTE_BUZZER = False # silence the on-board buzzer (e.g. when using computer audio)
|
||||||
WIDTH, HEIGHT = 320, 480
|
WIDTH, HEIGHT = 320, 480
|
||||||
MADCTL = 0x48 # portrait; 0x48 swaps R/B for this BGR panel (cyan reads cyan). Use 0x40 if reversed.
|
MADCTL = 0x48 # portrait; 0x48 swaps R/B for this BGR panel (cyan reads cyan). Use 0x40 if reversed.
|
||||||
INVERT_COLORS = True # most ST7796 modules need inversion ON; set False if colours look negative
|
INVERT_COLORS = True # most ST7796 modules need inversion ON; set False if colours look negative
|
||||||
|
|
@ -63,6 +70,14 @@ C_BG, C_PANEL, C_TXT, C_MUTE = 0x06090E, 0x1C222C, 0xC7D0DB, 0x788494
|
||||||
C_CYAN, C_AMBER, C_GREEN, C_DIM = 0x0AB3F7, 0xFF9B2E, 0x2FE07A, 0x243240
|
C_CYAN, C_AMBER, C_GREEN, C_DIM = 0x0AB3F7, 0xFF9B2E, 0x2FE07A, 0x243240
|
||||||
C_BTN = 0x1C222C
|
C_BTN = 0x1C222C
|
||||||
LEVEL_RGB = {2: (255, 110, 0), 1: (0, 150, 255), 3: (130, 70, 255)}
|
LEVEL_RGB = {2: (255, 110, 0), 1: (0, 150, 255), 3: (130, 70, 255)}
|
||||||
|
# voice -> General-MIDI note (USB-MIDI bridge), and level -> MIDI velocity
|
||||||
|
SOUND_GM = {"kick":36,"kick808":36,"kick909":36, "snare":38,"snare808":38,"snare909":38,
|
||||||
|
"clap":39,"clap808":39,"clap909":39, "rim":37, "hatClosed":42,"hat808":42,"hat909":42,
|
||||||
|
"hatOpen":46,"openHat808":46, "ride":51,"ride909":51, "crash":49,"crash909":49,
|
||||||
|
"tomLow":41,"tom808":45,"tomMid":45,"tomHigh":48, "tambourine":54,
|
||||||
|
"cowbell":56,"cowbell808":56, "woodblock":76,"jamblock":76, "claves":75, "beep":37}
|
||||||
|
GM_DEFAULT = 37
|
||||||
|
MIDI_VEL = {2: 120, 1: 90, 3: 45} # accent / normal / ghost
|
||||||
MAXLANES = 5 # lanes shown on the pad grid (extras still play)
|
MAXLANES = 5 # lanes shown on the pad grid (extras still play)
|
||||||
PAD_DIM = (0x10161E, 0x0A3A52, 0x4A3010, 0x2A1D4A) # idle pad: mute / normal / accent / ghost
|
PAD_DIM = (0x10161E, 0x0A3A52, 0x4A3010, 0x2A1D4A) # idle pad: mute / normal / accent / ghost
|
||||||
PAD_LIT = (0x39414D, 0x0AB3F7, 0xFF9B2E, 0x967BFF) # playhead pad: mute / normal / accent / ghost
|
PAD_LIT = (0x39414D, 0x0AB3F7, 0xFF9B2E, 0x967BFF) # playhead pad: mute / normal / accent / ghost
|
||||||
|
|
@ -273,6 +288,7 @@ class App:
|
||||||
self.display = make_display()
|
self.display = make_display()
|
||||||
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.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
|
||||||
|
|
@ -357,6 +373,10 @@ class App:
|
||||||
def led_off(self):
|
def led_off(self):
|
||||||
self.rgb = (0, 0, 0)
|
self.rgb = (0, 0, 0)
|
||||||
self.led.set(0, 0, 0)
|
self.led.set(0, 0, 0)
|
||||||
|
def midi_send(self, note, vel): # device-as-conductor: a note per click to the computer
|
||||||
|
if self.midi is None: return
|
||||||
|
try: self.midi.write(bytes([0x90, note, vel])) # Note On (percussive — no Note Off needed)
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
# ---------- transport ----------
|
# ---------- transport ----------
|
||||||
def toggle(self):
|
def toggle(self):
|
||||||
|
|
@ -393,11 +413,15 @@ class App:
|
||||||
while now >= L['next']:
|
while now >= L['next']:
|
||||||
L['step'] = (L['step'] + 1) % L['steps']
|
L['step'] = (L['step'] + 1) % L['steps']
|
||||||
lvl = 0 if L['mute'] else L['levels'][L['step']]
|
lvl = 0 if L['mute'] else L['levels'][L['step']]
|
||||||
if lvl > 0: fired.append(lvl)
|
if lvl > 0:
|
||||||
|
fired.append(lvl)
|
||||||
|
self.midi_send(SOUND_GM.get(L['sound'], GM_DEFAULT), MIDI_VEL.get(lvl, 90)) # one note per lane
|
||||||
L['next'] += L['dur']; adv = True
|
L['next'] += L['dur']; adv = True
|
||||||
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)); self.click(best); self.flash(best)
|
best = max(fired, key=lambda l: PRIO.get(l, 0))
|
||||||
|
if not MUTE_BUZZER: self.click(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
|
||||||
self.rgb = (r, g, b) if (r+g+b) > 12 else (0, 0, 0)
|
self.rgb = (r, g, b) if (r+g+b) > 12 else (0, 0, 0)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue