Checked against the pinned Circle tree after the question of supporting Bluetooth and WiFi on the Pi alongside USB. Bluetooth is not possible on Circle, for two independent reasons: the stack was removed for legal reasons and was classic Bluetooth in any case, with no BLE or GATT anywhere in the tree, while MIDI over BLE is GATT; and the remaining HCI transport would need a USB dongle in host mode, which the gadget-only constraint already rules out. That costs nothing. The WU-BT10 does GATT MIDI to the PC, and the PC is the hub, so Bluetooth MIDI works today with no firmware change - it just terminates one hop earlier. Section 3 now describes it that way rather than as a spare part. WiFi is possible via addon/wlan but is a project rather than a flag: Circle lists WLAN as unknown on the Zero 2 W, ships no RTP-MIDI (only mDNS advertisement of _apple-midi._udp), needs firmware blobs off the card at boot, and its coexistence with gadget mode is unproven. Deferred, not rejected; that last point is what to settle first. Also marks Phase 1 done-but-unverified, and records that Phase 0 should bench rpi_ws281x in SPI mode on GPIO10, since the stock examples use GPIO18 over PWM and would prove the wrong wiring. Claude-Session: https://claude.ai/code/session_01TVCB25LBsmeteWvaSMz4Ne
299 lines
14 KiB
Markdown
299 lines
14 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 | Zero W also works. Zero 2 W is current and quad-core. 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.
|