# 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. - Not used in this build's MIDI path. Freed up for streaming audio from the PC to the piano's own speakers over A2DP, 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. ## 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. 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 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.