// // pianoleds.cpp // #include "pianoleds.h" #include #include // MIDI status nibbles #define MIDI_NOTE_OFF 0x80 #define MIDI_NOTE_ON 0x90 #define MIDI_CONTROL_CHANGE 0xB0 // Control numbers that mean "stop everything" #define MIDI_CC_ALL_SOUND_OFF 120 #define MIDI_CC_ALL_NOTES_OFF 123 CPianoLEDs::CPianoLEDs (void) : m_Stripe (WS2812B, LED_COUNT, 4000000, SPI_MASTER_DEVICE), m_bDirty (TRUE) { memset ((void *) m_KeyVelocity, 0, sizeof m_KeyVelocity); memset ((void *) m_HintVelocity, 0, sizeof m_HintVelocity); } CPianoLEDs::~CPianoLEDs (void) { } boolean CPianoLEDs::Initialize (void) { if (!m_Stripe.Initialize ()) { return FALSE; } // Start from a known-dark strip rather than whatever the pixels held // when power came up. return m_Stripe.Blackout (); } void CPianoLEDs::AttachMIDIDevice (CUSBMIDIDevice *pMIDIDevice) { assert (pMIDIDevice != 0); // The gadget destroys and recreates its CUSBMIDIDevice across a suspend, // so any notes held at that moment would otherwise stay lit forever. AllOff (); pMIDIDevice->RegisterPacketHandler (MIDIPacketHandler, this); } void CPianoLEDs::MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength, unsigned nDevice, void *pParam) { CPianoLEDs *pThis = static_cast (pParam); assert (pThis != 0); pThis->OnMIDIPacket (pPacket, nLength); } void CPianoLEDs::OnMIDIPacket (const u8 *pPacket, unsigned nLength) { // Circle hands us one already-framed MIDI message of 1-3 bytes. Anything // shorter than a channel message cannot be a note event. if (nLength < 3) { return; } u8 ucStatus = pPacket[0] & 0xF0; u8 ucChannel = pPacket[0] & 0x0F; switch (ucStatus) { case MIDI_NOTE_ON: // Note On with velocity 0 is the conventional Note Off. SetKey (pPacket[1], pPacket[2], ChannelMatches (ucChannel, HINT_MIDI_CHANNEL)); break; case MIDI_NOTE_OFF: SetKey (pPacket[1], 0, ChannelMatches (ucChannel, HINT_MIDI_CHANNEL)); break; case MIDI_CONTROL_CHANGE: if ( pPacket[1] == MIDI_CC_ALL_SOUND_OFF || pPacket[1] == MIDI_CC_ALL_NOTES_OFF) { AllOff (); } break; default: break; } } void CPianoLEDs::SetKey (u8 ucNote, u8 ucVelocity, boolean bHint) { // Drop anything off the ends of the keybed rather than trusting the // input; an out-of-range note would index past the strip. if ( ucNote < MIDI_NOTE_MIN || ucNote > MIDI_NOTE_MAX) { return; } unsigned nKey = ucNote - MIDI_NOTE_MIN; if (bHint) { m_HintVelocity[nKey] = ucVelocity; } else { m_KeyVelocity[nKey] = ucVelocity; } m_bDirty = TRUE; } void CPianoLEDs::AllOff (void) { memset ((void *) m_KeyVelocity, 0, sizeof m_KeyVelocity); memset ((void *) m_HintVelocity, 0, sizeof m_HintVelocity); m_bDirty = TRUE; } boolean CPianoLEDs::ChannelMatches (u8 ucChannel, u8 ucWanted) { if (ucWanted == MIDI_CHANNEL_NONE) { return FALSE; } if (ucWanted == MIDI_CHANNEL_ANY) { return TRUE; } return ucChannel == ucWanted; } u8 CPianoLEDs::Scale (u8 ucChannel, u8 ucVelocity) { unsigned nValue = ucChannel; // Global brightness ceiling. This is the clamp that keeps a whited-out // strip inside the supply's current budget; see config.h. nValue = nValue * GLOBAL_BRIGHTNESS / 255; #if VELOCITY_SENSITIVE // Map velocity 1-127 onto [VELOCITY_FLOOR_PCT, 100] percent, so even the // softest note stays visible. unsigned nPercent = VELOCITY_FLOOR_PCT + (100 - VELOCITY_FLOOR_PCT) * ucVelocity / 127; nValue = nValue * nPercent / 100; #endif return (u8) nValue; } void CPianoLEDs::Update (void) { if (!m_bDirty) { return; } // Clear the flag before reading state, not after. An event arriving // mid-render then leaves the flag set and we render again next pass, // rather than being dropped. m_bDirty = FALSE; unsigned nLit = 0; for (unsigned nKey = 0; nKey < KEY_COUNT; nKey++) { u8 ucVelocity = m_KeyVelocity[nKey]; boolean bHint = FALSE; if (ucVelocity == 0) { // A key being played wins over a "next note" hint on it. ucVelocity = m_HintVelocity[nKey]; bHint = TRUE; } u8 ucRed = 0; u8 ucGreen = 0; u8 ucBlue = 0; // Bound the number of simultaneously lit keys, so no sequence of // MIDI events can drive the strip past the supply's budget. if ( ucVelocity != 0 && nLit < MAX_LIT_KEYS) { nLit++; if (bHint) { ucRed = Scale (HINT_COLOR_R, ucVelocity); ucGreen = Scale (HINT_COLOR_G, ucVelocity); ucBlue = Scale (HINT_COLOR_B, ucVelocity); } else { ucRed = Scale (NOTE_COLOR_R, ucVelocity); ucGreen = Scale (NOTE_COLOR_G, ucVelocity); ucBlue = Scale (NOTE_COLOR_B, ucVelocity); } } #if STRIP_REVERSED unsigned nBase = (KEY_COUNT - 1 - nKey) * LEDS_PER_KEY; #else unsigned nBase = nKey * LEDS_PER_KEY; #endif for (unsigned i = 0; i < LEDS_PER_KEY; i++) { unsigned nLED = nBase + i; assert (nLED < LED_COUNT); m_Stripe.SetLED (nLED, ucRed, ucGreen, ucBlue); } } m_Stripe.Update (); }