// // main.cpp // // Piano LED Visualizer on RP2040 / RP2350. // // The board is a USB MIDI device: the PC is the host and owns the piano // connection and everything else. From the PC's side this enumerates as an // ordinary ALSA MIDI port. See PIANO-LED-CIRCLE-PLAN.md section 2. // #include "pianoleds.h" #include "picostrip.h" #include "pico/stdlib.h" #include "tusb.h" static CPicoLEDStrip s_Strip (LED_COUNT, WS2812_PIN); static CPianoLEDs s_PianoLEDs (s_Strip); // -------------------------------------------------------------------------- // Status indicator // -------------------------------------------------------------------------- // // Most Waveshare RP2350 boards have an onboard WS2812 rather than a plain LED, // and the board header names the pin. Where it exists, it is the only feedback // this headless board has: it answers "did the firmware boot" and "did the host // enumerate us" before the strip is even wired. #if STATUS_LED_ENABLED && defined(PICO_DEFAULT_WS2812_PIN) #define HAVE_STATUS_LED 1 static CPicoLEDStrip s_Status (1, PICO_DEFAULT_WS2812_PIN); enum TStatus { StatusFault, // strip did not initialise StatusWaiting, // no USB host yet StatusReady, // enumerated, running normally StatusCalibrating // a calibration pattern is active }; static TStatus s_LastStatus = StatusFault; static bool s_bStatusValid = false; static void ShowStatus (TStatus Status) { if ( s_bStatusValid && Status == s_LastStatus) { return; // only touch the LED when it changes } const uint8_t B = STATUS_LED_BRIGHTNESS; switch (Status) { case StatusFault: s_Status.SetLED (0, B, 0, 0); break; case StatusWaiting: s_Status.SetLED (0, B, B / 2, 0); break; case StatusReady: s_Status.SetLED (0, 0, B, 0); break; case StatusCalibrating: s_Status.SetLED (0, 0, 0, B); break; } s_Status.Update (); s_LastStatus = Status; s_bStatusValid = true; } #else #define HAVE_STATUS_LED 0 static void ShowStatus (int) {} enum { StatusFault, StatusWaiting, StatusReady, StatusCalibrating }; #endif // Number of MIDI bytes carried by a USB MIDI event packet, indexed by its // Code Index Number (USB MIDI 1.0, table 4-1). 0 means "not a message we // forward". static const uint8_t s_CINLength[16] = { 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 }; // -------------------------------------------------------------------------- // TinyUSB device callbacks // -------------------------------------------------------------------------- extern "C" void tud_mount_cb (void) { // Fresh session: nothing should still be lit from the last one. s_PianoLEDs.AllOff (); } extern "C" void tud_umount_cb (void) { // Host went away, possibly mid-chord. Do not leave keys lit. s_PianoLEDs.AllOff (); } extern "C" void tud_suspend_cb (bool remote_wakeup_en) { (void) remote_wakeup_en; s_PianoLEDs.AllOff (); } extern "C" void tud_resume_cb (void) { s_PianoLEDs.AllOff (); } // -------------------------------------------------------------------------- // sleep_ms takes uint32_t, which is unsigned long on this target; the portable // header deliberately knows nothing about platform types. static void DelayMs (unsigned nMilliSeconds) { sleep_ms (nMilliSeconds); } static void PollMIDI (void) { uint8_t Packet[4]; while (tud_midi_available ()) { if (!tud_midi_packet_read (Packet)) { break; } // Packet[0] is cable number (high nibble) and Code Index Number // (low nibble); the message itself is in Packet[1..3]. unsigned nLength = s_CINLength[Packet[0] & 0x0F]; if (nLength == 0) { continue; } s_PianoLEDs.OnMIDIPacket (&Packet[1], nLength); } } int main (void) { stdio_init_all (); #if HAVE_STATUS_LED s_Status.Initialize (); #endif if (!s_Strip.Initialize ()) { // Sit here showing red. A dark board would look like a power // problem; this says the firmware ran and the strip did not. while (true) { ShowStatus (StatusFault); tight_loop_contents (); } } s_PianoLEDs.Initialize (); // Before USB, so the sweep runs on power alone and needs no PC. s_PianoLEDs.RunSelfTest (DelayMs); tusb_init (); while (true) { tud_task (); PollMIDI (); // Rendering blocks for the strip's frame time, so it runs here // rather than in the USB callback. Returns immediately when // nothing has changed. s_PianoLEDs.Update (); s_PianoLEDs.SetHostConnected (tud_mounted ()); if (!tud_mounted ()) { ShowStatus (StatusWaiting); } else if (s_PianoLEDs.GetCalibrationPattern () != CALIB_PATTERN_OFF) { ShowStatus (StatusCalibrating); } else { ShowStatus (StatusReady); } } return 0; }