The BOM claimed "Zero W also works" as an aside. Checked it: Circle's CHANGELOG gives gadget support for "Zero (2) (W)", the parentheses including the plain Zero, and the README lists Raspberry Pi Zero as Tested with no caveat. build.sh already emits kernel.img for RASPPI=1, and the MIDI gadget's string descriptors are present in that image, so no build change is needed for either board. Keeping the Zero 2 W as the specified part on availability grounds. Records that a plain Zero would permanently close the WiFi option in section 3a, since it has no radio silicon at all. Claude-Session: https://claude.ai/code/session_01TVCB25LBsmeteWvaSMz4Ne
299 lines
15 KiB
Markdown
299 lines
15 KiB
Markdown
# Piano LED Visualizer on Circle (bare metal)
|
|
|
|
Kickoff document. Written 2026-08-27.
|
|
|
|
Everything below is split into **VERIFIED** (checked against a primary source this
|
|
session, with the source named) and **ASSUMED** (plausible, load-bearing, not yet
|
|
confirmed). Do not promote an ASSUMED item to fact without checking it.
|
|
|
|
---
|
|
|
|
## 1. Goal
|
|
|
|
An LED strip above the keys of a Casio Privia PX-S7000 that lights the key being
|
|
played, and can light the *next* key to play when driven by learning software.
|
|
|
|
Explicit non-goal: this is not a port of `onlaj/Piano-LED-Visualizer`. That project
|
|
is ~95% web UI, WiFi hotspot, song library, LCD menu, and MIDI file playback. In this
|
|
architecture the PC owns all of that. What remains is a few hundred lines of
|
|
greenfield firmware.
|
|
|
|
## 2. Architecture (settled)
|
|
|
|
```
|
|
Casio PX-S7000 --USB-B--> PC (Debian 13, EliteOne AIO) --USB--> Pi Zero (Circle) --> WS2812B strip
|
|
| ^
|
|
+-- Neothesia / sheet reader |
|
|
5V PSU +
|
|
```
|
|
|
|
The PC is the hub. It is the only thing that talks to the piano. ALSA's sequencer
|
|
fans the piano's input port out to both the on-screen software and the Pi.
|
|
|
|
The Pi presents itself to the PC as a **USB MIDI device**. From the PC's side it is
|
|
just another ALSA MIDI output port; `aconnect` the piano's input to it and the job
|
|
is done.
|
|
|
|
### Why this shape
|
|
|
|
- **No network stack on the Pi at all.** This was the deciding factor. An appliance
|
|
Linux box with an unauthenticated web UI that nobody ever patches is a real home
|
|
network risk, not a theoretical one. Circle with a USB gadget has no shell, no
|
|
sshd, no web server, no package manager, and no network interface. The category is
|
|
deleted rather than mitigated.
|
|
- **No 2.4GHz contention.** Pi Zero and Zero 2 W are 2.4GHz-only, which is the same
|
|
band as the WU-BT10's BLE. Using USB avoids reasoning about it.
|
|
- **Deterministic latency**, no scheduler, no WiFi power-save.
|
|
- **Still builds in five years.** A pinned Circle tree does. A Python app with pip
|
|
dependencies does not.
|
|
- **Power-cycle safe.** No writable filesystem to corrupt when the thing gets killed
|
|
at the wall.
|
|
|
|
### Consequence to be aware of
|
|
|
|
VERIFIED: *"Circle does not support OTG protocols, so the USB controller always works
|
|
in host or gadget mode and the connected peer must work in the opposite mode."*
|
|
(Circle CHANGELOG)
|
|
|
|
So the Pi is gadget-only in this build. It **cannot** also host a USB MIDI device,
|
|
which means it can never take MIDI directly from the piano. All MIDI arrives from the
|
|
PC. That is exactly the architecture above, so this costs nothing here, but it does
|
|
close off "plug the piano into the Pi" as a fallback.
|
|
|
|
## 3. Verified facts
|
|
|
|
**Circle USB MIDI gadget**
|
|
- Introduced in **Release 45.3 (2023-10-06)**: *"This release comes with initial USB
|
|
gadget (aka device, peripheral) mode support, which is used to implement an USB
|
|
MIDI (v1.0) gadget."*
|
|
- Class `CUSBMIDIGadget`, header `include/circle/usb/gadget/usbmidigadget.h`.
|
|
- Usage: instantiate `CUSBMIDIGadget` **instead of** `CUSBHCIDevice`, then call
|
|
`Initialize()` and `UpdatePlugAndPlay()` as in host mode.
|
|
- Supported models: *"Raspberry Pi models (3)A(+), Zero (2) (W) and 4B"*.
|
|
**The Zero and Zero 2 W are explicitly supported.** The MiniDexed docs mention only
|
|
Pi 3/4; that was incomplete, not a restriction.
|
|
- Source: https://github.com/rsta2/circle/blob/master/CHANGELOG.md and
|
|
https://circle-rpi.readthedocs.io/en/47.0/subsystems/usb.html
|
|
|
|
**Circle WS28XX driver** (`addon/WS28XX/`)
|
|
- `ws28xxstripe.cpp/h` - single-channel stripe driver
|
|
- `ws2812oversmi.cpp/h` - multi-channel driver over the Secondary Memory Interface
|
|
- `sample/` - example programs, including `sample/multichan/`
|
|
- Only the single-channel driver is needed here. One strip, one data line.
|
|
|
|
**Casio WU-BT10**
|
|
- Bluetooth 5.0. Profiles: **A2DP (audio)** and **GATT (MIDI over BLE)**. Codec SBC.
|
|
- USB bus powered, 0.25W, no separate supply.
|
|
- **This is the project's Bluetooth MIDI path**, and it terminates at the PC, not the
|
|
Pi. Piano -> WU-BT10 -> PC over BLE GATT, then PC -> Pi over USB as usual. See
|
|
section 3a for why it cannot terminate at the Pi.
|
|
- A2DP is also available for streaming audio from the PC to the piano's own speakers,
|
|
if wanted.
|
|
- Source: https://support.casio.com/en/support/answer.php?cid=008006001002&qid=104463&num=1
|
|
|
|
**Reference build** (https://yanniznik.com/building-a-led-piano-visualizer/)
|
|
- WS2812B, 144 LEDs/m, ~172 LEDs, 1.5-2m strip
|
|
- 5V 6A supply, 22-18 AWG wire, 5.5x2.5mm DC socket
|
|
- Aluminum LED profile with diffuser, double-sided tape
|
|
- ~$75-100 total. Includes a $30 iConnectivity mio MIDI interface **we do not need**,
|
|
because the PX-S7000 has USB-B.
|
|
|
|
## 3a. Bluetooth and WiFi on Circle
|
|
|
|
Checked against the pinned Circle tree (Step51) on 2026-08-27, after the question was
|
|
raised of supporting Bluetooth and WiFi on the Pi alongside USB.
|
|
|
|
**Bluetooth: not possible on Circle. Two independent blockers.**
|
|
|
|
VERIFIED: Circle's CHANGELOG states *"As announced the Bluetooth support has been
|
|
removed for legal reasons."* What remains, `lib/usb/usbbluetooth.cpp`, is a bare HCI
|
|
transport for a USB dongle with no stack above it. There is no BLE and no GATT
|
|
anywhere in the tree, and the removed stack was classic Bluetooth in any case. MIDI
|
|
over BLE *is* GATT, so even the deleted code would not have served.
|
|
|
|
VERIFIED: that transport would need a dongle in USB **host** mode, and Circle is
|
|
host-or-gadget and never both (see section 2). The Pi is a gadget here.
|
|
|
|
The consequence is not a loss. The WU-BT10 already does GATT MIDI to the PC, and the
|
|
PC is the hub, so Bluetooth MIDI is available today with no firmware change at all.
|
|
It simply terminates one hop earlier than the question assumed.
|
|
|
|
**WiFi: possible, but it is a project, not a flag.**
|
|
|
|
VERIFIED: `addon/wlan` is a Plan 9 derived BCM4343 driver (under its own Plan 9
|
|
license), usable with Circle's TCP/IP stack and a bundled WPA supplicant.
|
|
|
|
Four costs, all VERIFIED unless marked:
|
|
|
|
1. Circle's README lists the Raspberry Pi Zero 2 W as *"WLAN unknown for new
|
|
revision"*. The Zero W is Tested. That is the opposite way round from the BOM's
|
|
preference in section 5.
|
|
2. Circle ships mDNS advertisement for `_apple-midi._udp` but **not** the RTP-MIDI
|
|
protocol itself. The session layer would have to be written, or ported from
|
|
MiniDexed, which implements exactly this on Circle.
|
|
3. WLAN loads firmware blobs from `RPi-Distro/firmware-nonfree` off the SD card at
|
|
boot, which reintroduces filesystem reads and weakens the power-cycle-safe
|
|
property that section 2 was built around.
|
|
4. ASSUMED: that WLAN coexists with USB gadget mode. WLAN is SDIO and so it probably
|
|
does, but both `addon/wlan` samples pair it with USB *host*, so this is unproven
|
|
and would need a spike before anything is committed to.
|
|
|
|
And it reintroduces exactly the LAN attack surface that section 2 names as the
|
|
deciding factor for this architecture.
|
|
|
|
**Decision (2026-08-27): USB first.** Bluetooth is taken on the PC side via the
|
|
WU-BT10, at no cost. WiFi is deferred, not rejected; if it is revisited, cost 4 above
|
|
is the first thing to settle, because it may close the option outright.
|
|
|
|
## 4. Assumptions to verify before spending money
|
|
|
|
1. **PX-S7000 is class-compliant USB MIDI.** Almost certainly true for a 2022
|
|
instrument, but unconfirmed. Test: plug into the PC, run `aconnect -l`, look for it.
|
|
This is step one of Phase 0 and it gates everything.
|
|
2. **Exact GPIO/pin for `CWS28XXStripe`.** Circle's single-channel driver is believed
|
|
to clock the waveform out over SPI, which would put data on the MOSI pin, but the
|
|
pin and the peripheral were not confirmed. **Read `addon/WS28XX/sample/` before
|
|
wiring anything.**
|
|
3. **Mounting geometry.** See section 8. Unsolved and unbudgeted.
|
|
4. **PLV web UI authentication.** Only matters if Phase 0 uses that project rather
|
|
than a throwaway script. Assume it is unauthenticated until shown otherwise.
|
|
|
|
## 5. Bill of materials
|
|
|
|
| Item | Note |
|
|
|---|---|
|
|
| Raspberry Pi Zero 2 W | VERIFIED: the plain Zero and Zero W work too. `build.sh` already emits `kernel.img` for them, and the gadget's string descriptors are present in it, so either is a drop-in if Zero 2 W supply is a problem. Chosen anyway as the current and most available part; the quad core is headroom this workload does not need. Note a plain Zero would permanently close the WiFi option in section 3a. Headers not needed if the LED line is soldered. |
|
|
| microSD card | Circle boots from FAT32. Tiny card is fine. |
|
|
| WS2812B strip, 144 LEDs/m, 2m | Need 176 LEDs = 1.222m. Buy 2m, cut to length. |
|
|
| 5V PSU, 6A | See the power note in section 7 before assuming this is enough. |
|
|
| 74AHCT125 level shifter | See section 7. Do not skip this on the assumption 3.3V will work. |
|
|
| Aluminum LED profile + diffuser, ~1.3m | The article is right that bare strip looks bad. |
|
|
| DC barrel socket 5.5x2.5mm, quick connect | |
|
|
| 22-18 AWG wire | Including a second run for power injection at the far end. |
|
|
| USB cable, PC to Pi Zero | Micro-USB, and it must be a **data** cable. Plug into the Zero's **USB** port, not **PWR**. |
|
|
|
|
Roughly $50-70 with the mio omitted.
|
|
|
|
## 6. Note-to-LED mapping
|
|
|
|
88 keys span MIDI notes **21 (A0)** through **108 (C8)**.
|
|
|
|
At 2 LEDs per key: `176 LEDs`. At 144 LEDs/m that is **1.222m**, which is why 144/m
|
|
is the density everyone uses. It lines up with a standard 88-key keybed almost exactly.
|
|
|
|
```
|
|
led_index = (note - 21) * 2
|
|
```
|
|
|
|
Light `led_index` and `led_index + 1`. Clamp to `[0, 175]` and drop anything outside
|
|
21-108 rather than trusting the input.
|
|
|
|
If the strip is mounted with pixel 0 at the treble end, invert:
|
|
`led_index = (108 - note) * 2`. Decide this **after** the strip is physically mounted,
|
|
and make it a compile-time constant so it is one flag to flip.
|
|
|
|
## 7. Electrical notes
|
|
|
|
**Power budget, and why 6A is a bet.**
|
|
176 LEDs at full white draw ~60mA each = **10.56A theoretical maximum**. A 6A supply
|
|
does not cover that. It does not need to: playing a ten-finger chord lights 20 LEDs,
|
|
about 1.2A, so 6A is enormous headroom for real use.
|
|
|
|
But this makes a **global brightness clamp a firmware requirement, not a nicety**. A
|
|
bug that sets every pixel to white on a 6A supply browns out the rail. Cap global
|
|
brightness in the firmware, and cap the number of simultaneously lit pixels if you
|
|
ever add an idle animation.
|
|
|
|
**Level shifting.** WS2812B wants logic high at 0.7 x VDD, which is **3.5V** on a 5V
|
|
rail. The Pi's GPIO is **3.3V**. This is marginal by design. It frequently works and
|
|
then intermittently doesn't, which is the worst failure mode to debug. Use a
|
|
**74AHCT125** on the data line. This is the single most common cause of "the strip
|
|
flickers and I don't know why."
|
|
|
|
**Common ground.** The Pi's ground and the LED supply's ground must be tied together.
|
|
Non-optional.
|
|
|
|
**Power injection.** Feed 5V at both ends of the strip. Over 1.2m at 144/m the far end
|
|
otherwise drifts dim and yellow.
|
|
|
|
**Refresh floor.** WS2812B is 1.25us per bit, 24 bits per LED, so 30us per LED.
|
|
176 LEDs = **5.3ms** per full frame, plus a ~50us reset. Max ~190 Hz. Irrelevant for
|
|
this application but it is the hard latency floor, and it dominates the USB MIDI
|
|
transit time.
|
|
|
|
## 8. The actual hard part
|
|
|
|
The PX-S7000 is a slab. It has no shelf above the keybed the way an upright piano
|
|
does. The aluminum profile has to sit above the keys, parallel to them, at a fixed
|
|
offset, with nothing obvious to attach to.
|
|
|
|
**This is the part of the project that will consume the most time, and it is not a
|
|
software problem.** Solve it, or at least prototype it in cardboard, before ordering
|
|
electronics. Take measurements off the actual instrument.
|
|
|
|
## 9. Phases
|
|
|
|
### Phase 0 - bench validation on Linux (disposable, one afternoon)
|
|
|
|
The purpose is to separate "my wiring is wrong" from "my firmware is wrong." On the
|
|
first power-up of a strip you soldered yourself, that is the only question that
|
|
matters, and bare metal gives you no debugging surface to answer it.
|
|
|
|
Throw all of this away afterwards. It never gets mounted, never gets a hostname,
|
|
never sits on the LAN permanently.
|
|
|
|
1. Piano to PC over USB-B. Confirm it appears in `aconnect -l`.
|
|
2. Pi Zero on Raspberry Pi OS, `rpi_ws281x`, Python REPL. **Configure it for SPI, so
|
|
the data line is GPIO10 (physical pin 19).** The stock `rpi_ws281x` examples drive
|
|
the strip from GPIO18 over PWM, but the Circle firmware uses SPI on GPIO10. Bench
|
|
on the default pin and the wiring you prove is not the wiring you keep.
|
|
3. Confirm: pixel 0 is at the end you think it is; `(note-21)*2` lights the right key;
|
|
the far end holds voltage under load; the colors look right *through the diffuser*,
|
|
which is not the same as how they look bare.
|
|
4. Decide brightness, colors, and whether velocity modulates anything. These are
|
|
product decisions, and answering them here means the firmware gets written once.
|
|
|
|
Exit criteria: you know the hardware is good and you know exactly what you want the
|
|
firmware to do.
|
|
|
|
### Phase 1 - Circle firmware [DONE 2026-08-27, unverified on hardware]
|
|
|
|
Written and building; see `firmware/` and README.md. Both kernel images build clean
|
|
against Circle Step51. Steps 2 and 3 were resolved by reading the Circle sources
|
|
rather than by trial: the WS28XX driver clocks the strip over SPI, putting data on
|
|
MOSI = GPIO10 = physical pin 19, and the gadget lifecycle follows
|
|
`sample/29-miniorgan`, which already carries a `USB_GADGET_MODE` path.
|
|
|
|
Nothing here has touched a real LED or enumerated on a real PC. The riskiest untested
|
|
part is USB enumeration, and it is blocked by section 10 along with all of Phase 0.
|
|
|
|
|
|
1. Clone Circle, pin the release, build a stock sample for the Zero to confirm the
|
|
toolchain and the boot path.
|
|
2. Build and run `addon/WS28XX/sample/`. **Read it first for the pin assignment.**
|
|
3. Build and run a USB MIDI gadget sample. Confirm the PC enumerates it and it shows
|
|
up in `aconnect -l` on the PC side.
|
|
4. Join the two. `CUSBMIDIGadget` in, `CWS28XXStripe` out, the mapping from section 6
|
|
in between.
|
|
5. Global brightness clamp per section 7.
|
|
6. Compile-time flag for strip orientation.
|
|
|
|
The whole thing is a few hundred lines. The risk is in steps 2 and 3, not step 4.
|
|
|
|
### Phase 2 - mechanical
|
|
|
|
Per section 8. Can proceed in parallel with Phase 1, and should, because it has the
|
|
longest lead time and the most unknowns.
|
|
|
|
### Phase 3 - learning integration
|
|
|
|
Lighting the *next* note to play, driven by software on the PC. Deliberately deferred.
|
|
It is a protocol design question and it depends on what the PC ends up running.
|
|
|
|
## 10. Sequencing warning
|
|
|
|
At time of writing there is **no PC**. The EliteOne 800 G5 negotiation is unresolved.
|
|
Phase 0 step 1 cannot start without a machine.
|
|
|
|
Phase 2 does not depend on the PC and can start immediately with a tape measure.
|