// // config.h // // Piano LED Visualizer - all tunable parameters. // // Shared by every platform backend (Circle on a Pi Zero, RP2040/RP2350 on a // Pico). Nothing here is platform-specific except the wiring block at the end. // // Every value in this file is a product decision that Phase 0 of // PIANO-LED-CIRCLE-PLAN.md exists to answer. Bench-test on Raspberry Pi OS // first, then transcribe the answers here and build once. // #ifndef _config_h #define _config_h // -------------------------------------------------------------------------- // Keybed and strip geometry (plan section 6) // -------------------------------------------------------------------------- // An 88-key keybed spans MIDI notes 21 (A0) through 108 (C8). #define MIDI_NOTE_MIN 21 #define MIDI_NOTE_MAX 108 #define KEY_COUNT (MIDI_NOTE_MAX - MIDI_NOTE_MIN + 1) // 88 // LEDs per key. At 144 LEDs/m, 2 per key spans 1.222m, which lines up with a // standard 88-key keybed almost exactly. #ifndef LEDS_PER_KEY #define LEDS_PER_KEY 2 #endif #define LED_COUNT (KEY_COUNT * LEDS_PER_KEY) // 176 // Strip orientation. Pixel 0 of a WS2812B strip is at the end the data line // enters. Decide this AFTER the strip is physically mounted, then flip this // one flag. // // 0 = pixel 0 is at the bass end -> led = (note - 21) * 2 // 1 = pixel 0 is at the treble end -> led = (108 - note) * 2 #ifndef STRIP_REVERSED #define STRIP_REVERSED 0 #endif // Global shift, in pixels, applied after mapping. Absorbs where the strip was // actually cut and where the profile ended up on the instrument - things the // geometry cannot know. Positive moves every key towards higher pixel indices. // Find it with calibration pattern 4 (single-LED walk); see README. #ifndef LED_OFFSET #define LED_OFFSET 0 #endif // Note-to-LED mapping. // // 1 = geometric. Derives each key's position from white-key geometry: // 52 white keys span the strip, so one white key is LED_COUNT/52 // pixels (~3.38 at 176 LEDs) with black keys on the boundaries. // 0 = linear. The plan's original (note - 21) * LEDS_PER_KEY. // // Linear is wrong on a real keybed, because semitones are not evenly spaced: // it drifts within each octave, worst at F, by up to ~0.87 LEDs (~6mm) even // after an optimal offset and scale. Geometric removes that. Keep linear only // to reproduce the original behaviour. #ifndef NOTE_MAP_GEOMETRIC #define NOTE_MAP_GEOMETRIC 1 #endif // Number of white keys spanned by the strip. 52 for a standard 88-key keybed. #ifndef WHITE_KEY_COUNT #define WHITE_KEY_COUNT 52 #endif // -------------------------------------------------------------------------- // Power safety (plan section 7) - NOT optional // -------------------------------------------------------------------------- // // 176 LEDs at full white draw ~60mA each = 10.56A theoretical maximum, against // a 6A supply. Real playing never approaches that (a ten-finger chord lights 20 // LEDs, ~1.2A), but a firmware bug that whites out the strip would brown out // the rail. These two clamps make that unreachable rather than unlikely. // Global brightness ceiling, applied to every channel of every pixel. // 0-255. At 96 a full-strip white would draw roughly 4A, still inside 6A. #ifndef GLOBAL_BRIGHTNESS #define GLOBAL_BRIGHTNESS 96 #endif // Hard cap on simultaneously lit keys. Beyond this, further held notes are // tracked but not lit, so current draw stays bounded no matter what arrives // on the wire. 20 keys is a ten-finger chord; 30 leaves room for pedal-held // passages without ever approaching the supply limit. #ifndef MAX_LIT_KEYS #define MAX_LIT_KEYS 30 #endif // -------------------------------------------------------------------------- // Colour (Phase 0 decides these against the actual diffuser) // -------------------------------------------------------------------------- // // Colours look substantially different through a diffuser than on bare strip. // Do not finalise these from a photo. // Colour for a played key, before brightness scaling. #ifndef NOTE_COLOR_R #define NOTE_COLOR_R 0 #endif #ifndef NOTE_COLOR_G #define NOTE_COLOR_G 140 #endif #ifndef NOTE_COLOR_B #define NOTE_COLOR_B 255 #endif // Distinct colour for a "next note to play" hint driven by learning software // on the PC (plan Phase 3). Reached over MIDI channel HINT_MIDI_CHANNEL. #ifndef HINT_COLOR_R #define HINT_COLOR_R 255 #endif #ifndef HINT_COLOR_G #define HINT_COLOR_G 80 #endif #ifndef HINT_COLOR_B #define HINT_COLOR_B 0 #endif // -------------------------------------------------------------------------- // Velocity response // -------------------------------------------------------------------------- // 1 = velocity scales pixel brightness, 0 = every key lights at full // GLOBAL_BRIGHTNESS regardless of how hard it was struck. #ifndef VELOCITY_SENSITIVE #define VELOCITY_SENSITIVE 1 #endif // Floor for velocity scaling, as a percentage. A pianissimo note should still // be clearly visible, so velocity maps onto [VELOCITY_FLOOR_PCT, 100] rather // than onto [0, 100]. #ifndef VELOCITY_FLOOR_PCT #define VELOCITY_FLOOR_PCT 35 #endif // -------------------------------------------------------------------------- // MIDI routing // -------------------------------------------------------------------------- // Channel carrying notes actually played on the piano. 0-15 on the wire // (channel 1 in a DAW), or MIDI_CHANNEL_ANY to accept every channel. #define MIDI_CHANNEL_ANY 0xFF #ifndef NOTE_MIDI_CHANNEL #define NOTE_MIDI_CHANNEL MIDI_CHANNEL_ANY #endif // Channel reserved for Phase 3 "light the next key" hints from the PC. Kept // separate from played notes so the two never overwrite each other. Set to // MIDI_CHANNEL_NONE to ignore hints entirely. #define MIDI_CHANNEL_NONE 0xFE #ifndef HINT_MIDI_CHANNEL #define HINT_MIDI_CHANNEL 15 // channel 16 in a DAW #endif // -------------------------------------------------------------------------- // Calibration (plan Phase 0) // -------------------------------------------------------------------------- // // This is a headless appliance with no console, so calibration is driven over // MIDI - the one channel that already exists. Send these CCs from the PC; see // tools/calibrate.sh. // // Patterns are a diagnostic overlay: while one is active it replaces the note // display entirely, and pattern 0 restores normal operation. // CC selecting the active pattern. #ifndef CALIB_CC_PATTERN #define CALIB_CC_PATTERN 20 #endif // CCs setting the pixel index for CALIB_PATTERN_WALK, as a 14-bit value: // index = (CC21 << 7) | CC22. #ifndef CALIB_CC_INDEX_HI #define CALIB_CC_INDEX_HI 21 #endif #ifndef CALIB_CC_INDEX_LO #define CALIB_CC_INDEX_LO 22 #endif #define CALIB_PATTERN_OFF 0 // normal operation #define CALIB_PATTERN_ENDS 1 // first and last pixel only #define CALIB_PATTERN_OCTAVES 2 // every C, to expose mapping drift #define CALIB_PATTERN_KEYS 3 // every key, alternating colour #define CALIB_PATTERN_WALK 4 // one pixel, chosen by CC21/CC22 #define CALIB_PATTERN_ALL 5 // every pixel, for voltage droop testing // -------------------------------------------------------------------------- // Hardware wiring - platform specific // -------------------------------------------------------------------------- // // Whichever board is used, plan section 7 still applies in full: the data line // needs a 74AHCT125 to reach 5V logic, the board ground must be tied to the LED // supply ground, and 5V must be injected at both ends of the strip. Neither // board can power the strip itself. #ifdef PLATFORM_PICO // RP2040 / RP2350. The PIO state machine can drive the WS2812B waveform from // any GPIO, so this is a free choice rather than a constraint. #ifndef WS2812_PIN #define WS2812_PIN 2 #endif #else // Circle on a Raspberry Pi. VERIFIED against circle/addon/WS28XX: CWS28XXStripe // clocks the WS2812B waveform out over SPI at a fixed 6.4MHz, encoding each LED // bit as one SPI byte. On SPI master device 0 that fixes the data line at // // MOSI = GPIO10 (BCM) = physical pin 19 // // It is not a free choice on this platform. #ifndef SPI_MASTER_DEVICE #define SPI_MASTER_DEVICE 0 #endif #endif #endif