// // Host-side tests for the note-to-LED mapping and the power clamps. // Compiles the real firmware/pianoleds.cpp against stubbed Circle headers. // #define private public // inspect the captured strip state #include "pianoleds.h" #undef private #include #include static int g_nFail = 0; static void Check (const char *pName, bool bCond) { printf ("%-58s %s\n", pName, bCond ? "ok" : "FAIL"); if (!bCond) g_nFail++; } static unsigned CountLit (CPianoLEDs &L) { unsigned n = 0; for (auto &p : L.m_Stripe.m_Pixels) if (p[0] || p[1] || p[2]) n++; return n; } static bool Dark (CPianoLEDs &L, unsigned i) { auto &p = L.m_Stripe.m_Pixels.at (i); return !p[0] && !p[1] && !p[2]; } // every pixel of one key's span is lit static bool Span (CPianoLEDs &L, unsigned nBase) { for (unsigned i = 0; i < LEDS_PER_KEY; i++) if (Dark (L, nBase + i)) return false; return true; } static unsigned LedFor (u8 ucNote) { unsigned nKey = ucNote - MIDI_NOTE_MIN; #if STRIP_REVERSED return (KEY_COUNT - 1 - nKey) * LEDS_PER_KEY; #else return nKey * LEDS_PER_KEY; #endif } int main (void) { printf ("STRIP_REVERSED=%d LED_COUNT=%d MAX_LIT_KEYS=%d GLOBAL_BRIGHTNESS=%d\n\n", STRIP_REVERSED, LED_COUNT, MAX_LIT_KEYS, GLOBAL_BRIGHTNESS); CUSBMIDIDevice MIDI; CPianoLEDs LEDs; LEDs.Initialize (); LEDs.AttachMIDIDevice (&MIDI); // --- lowest key, A0 = note 21 ------------------------------------- MIDI.Inject (0x90, 21, 127); LEDs.Update (); #if STRIP_REVERSED unsigned nLow = (KEY_COUNT - 1) * LEDS_PER_KEY; // 174 #else unsigned nLow = 0; #endif Check ("note 21 lights its whole key span", Span (LEDs, nLow)); Check ("note 21 lights exactly LEDS_PER_KEY LEDs", CountLit (LEDs) == LEDS_PER_KEY); // --- highest key, C8 = note 108 ----------------------------------- MIDI.Inject (0x80, 21, 0); MIDI.Inject (0x90, 108, 127); LEDs.Update (); #if STRIP_REVERSED unsigned nHigh = 0; #else unsigned nHigh = (KEY_COUNT - 1) * LEDS_PER_KEY; // 174 #endif Check ("note 108 lights its whole key span", Span (LEDs, nHigh)); Check ("note 108 lights exactly LEDS_PER_KEY LEDs", CountLit (LEDs) == LEDS_PER_KEY); Check ("the two extremes are at opposite ends", nLow != nHigh); // --- note off ------------------------------------------------------ MIDI.Inject (0x80, 108, 0); LEDs.Update (); Check ("note off extinguishes the key", CountLit (LEDs) == 0); // --- note on with velocity 0 is a note off ------------------------- MIDI.Inject (0x90, 60, 100); LEDs.Update (); Check ("note on lights middle C", CountLit (LEDs) == LEDS_PER_KEY); MIDI.Inject (0x90, 60, 0); LEDs.Update (); Check ("note on velocity 0 acts as note off", CountLit (LEDs) == 0); // --- out-of-range notes are dropped, not clamped into the strip ---- MIDI.Inject (0x90, 20, 127); MIDI.Inject (0x90, 109, 127); MIDI.Inject (0x90, 0, 127); MIDI.Inject (0x90, 127, 127); LEDs.Update (); Check ("notes outside 21-108 are ignored", CountLit (LEDs) == 0); // --- brightness ceiling -------------------------------------------- for (u8 n = MIDI_NOTE_MIN; n <= MIDI_NOTE_MAX; n++) MIDI.Inject (0x90, n, 127); LEDs.Update (); bool bWithinCeiling = true; #if GLOBAL_BRIGHTNESS < 255 // at 255 a u8 channel cannot exceed the ceiling by construction for (auto &p : LEDs.m_Stripe.m_Pixels) for (int c = 0; c < 3; c++) if (p[c] > GLOBAL_BRIGHTNESS) bWithinCeiling = false; #endif Check ("no channel ever exceeds GLOBAL_BRIGHTNESS", bWithinCeiling); // --- simultaneous-key cap ------------------------------------------ Check ("all 88 keys held stays within MAX_LIT_KEYS", CountLit (LEDs) <= MAX_LIT_KEYS * LEDS_PER_KEY); // --- all notes off -------------------------------------------------- MIDI.Inject (0xB0, 123, 0); LEDs.Update (); Check ("CC 123 (all notes off) clears the strip", CountLit (LEDs) == 0); for (u8 n = MIDI_NOTE_MIN; n <= MIDI_NOTE_MAX; n++) MIDI.Inject (0x90, n, 127); MIDI.Inject (0xB0, 120, 0); LEDs.Update (); Check ("CC 120 (all sound off) clears the strip", CountLit (LEDs) == 0); // --- velocity sensitivity ------------------------------------------- MIDI.Inject (0x90, 60, 127); LEDs.Update (); auto Loud = LEDs.m_Stripe.m_Pixels.at (LedFor (60)); MIDI.Inject (0x90, 60, 1); LEDs.Update (); auto Soft = LEDs.m_Stripe.m_Pixels.at (LedFor (60)); #if VELOCITY_SENSITIVE Check ("a soft note is dimmer than a loud one", Soft[2] < Loud[2]); Check ("a soft note is still visible", Soft[2] > 0); #else Check ("velocity does not change brightness", Soft[2] == Loud[2]); #endif MIDI.Inject (0x80, 60, 0); // --- hint channel ---------------------------------------------------- #if HINT_MIDI_CHANNEL != MIDI_CHANNEL_NONE MIDI.Inject (0x90 | HINT_MIDI_CHANNEL, 64, 127); LEDs.Update (); auto Hint = LEDs.m_Stripe.m_Pixels.at (LedFor (64)); Check ("a hint note lights in the hint colour", Hint != Loud && (Hint[0] || Hint[1] || Hint[2])); // a key actually played wins over a hint on the same key MIDI.Inject (0x90, 64, 127); LEDs.Update (); auto Both = LEDs.m_Stripe.m_Pixels.at (LedFor (64)); Check ("a played note overrides a hint on the same key", Both == Loud); // releasing the played note falls back to the still-pending hint MIDI.Inject (0x80, 64, 0); LEDs.Update (); auto Back = LEDs.m_Stripe.m_Pixels.at (LedFor (64)); Check ("releasing a played note reveals the hint again", Back == Hint); #endif // --- reconnect clears held notes ------------------------------------- MIDI.Inject (0x90, 60, 127); LEDs.AttachMIDIDevice (&MIDI); LEDs.Update (); Check ("re-enumeration clears notes held at suspend", CountLit (LEDs) == 0); // --- short packets are not parsed as notes ---------------------------- u8 Short[1] = {0xF8}; // clock, 1 byte MIDI.m_pHandler (0, Short, 1, 1, MIDI.m_pParam); LEDs.Update (); Check ("a 1-byte realtime message lights nothing", CountLit (LEDs) == 0); printf ("\n%s\n", g_nFail ? "FAILURES" : "all tests passed"); return g_nFail != 0; }