#define FW_VER 1.01 #define USE_OLED true // OLED displays #define USE_ENCODER true #define EEPROM_MIDI_CHANNEL_ADDR 0 // Address to save MIDI channel in EEPROM // include libraries #include #include #include #if USE_OLED #include #include #include #include "font.h" #include "gfx.h" #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 32 Adafruit_SSD1306 oled(SCREEN_WIDTH,SCREEN_HEIGHT,&Wire,-1); #endif #if USE_ENCODER #define ENCODER_CLK 2 #define ENCODER_DT 3 int lastClk = HIGH; #endif unsigned long lastChannelChangeTime = 0; bool channelChanged = false; const unsigned long saveDelay = 2000; // 2 seconds bool showSavedMessage = false; unsigned long savedMessageStart = 0; const unsigned long savedMessageDuration = 3000; // 1 second // Instantiate libraries Adafruit_MCP23X17 mcp1; // First MCP23017 Adafruit_MCP23X17 mcp2; // Second MCP23017 // Default hardware MIDI. SoftwareSerial is too slow... MIDI_CREATE_DEFAULT_INSTANCE(); // Default MIDI channel byte currentMidiChannel = MIDI_CHANNEL_OMNI; //8; // Default MIDI channel byte savedChannel = EEPROM.read(EEPROM_MIDI_CHANNEL_ADDR); // Struct for 2 MCP chips. struct MCPOutput { uint8_t chip; // 0 = mcp1, 1 = mcp2 uint8_t pin; // 0–15 }; // LED shows MIDI activity unsigned long ledOnTime = 0; bool ledActive = false; const unsigned long ledDuration = 50; // flash for 50ms // Note to pin mapping. {chip, pin} // One would be able to add up to 8 MCPs, but 2 is plenty for a Meowsic. MCPOutput noteToMCP[32] = { // MCP1 – MIDI 57 to 72 {0, 0}, // 57 A3 {0, 1}, // 58 A#3 {0, 2}, // 59 B3 {0, 3}, // 60 C4 {0, 4}, // 61 C#4 {0, 5}, // 62 D4 {0, 6}, // 63 D#4 {0, 7}, // 64 E4 {0, 8}, // 65 F4 {0, 9}, // 66 F#4 {0, 10}, // 67 G4 {0, 11}, // 68 G#4 {0, 12}, // 69 A4 {0, 13}, // 70 A#4 {0, 14}, // 71 B4 {0, 15}, // 72 C5 // MCP2 – MIDI 73 to 88 {1, 0}, // 73 C#5 {1, 1}, // 74 D5 {1, 2}, // 75 D#5 {1, 3}, // 76 E5 {1, 4}, // 77 F5 {1, 5}, // 78 F#5 {1, 6}, // 79 G5 {1, 7}, // 80 G#5 {1, 8}, // 81 A5 {1, 9}, // 82 A#5 {1, 10}, // 83 B5 {1, 11}, // 84 C6 {1, 12}, // 85 C#6 {1, 13}, // 86 D6 {1, 14}, // 87 D#6 {1, 15} // 88 E6 }; void updateOLED() { #if USE_OLED oled.clearDisplay(); oled.drawBitmap(0, 12, midich, MIDICH_WIDTH, MIDICH_HEIGHT, 1); oled.setCursor(86, 32); oled.setTextColor(SSD1306_WHITE); if (currentMidiChannel == MIDI_CHANNEL_OMNI) { oled.print("ALL"); } else { oled.print(currentMidiChannel); } if (showSavedMessage) { oled.drawBitmap(88, 0, savetorom, SAVETOROM_WIDTH, SAVETOROM_HEIGHT, 1); } oled.display(); #endif } void checkEncoder() { #if USE_ENCODER int newClk = digitalRead(ENCODER_CLK); if (newClk != lastClk && newClk == LOW) { int newChannel = currentMidiChannel; if (digitalRead(ENCODER_DT) == LOW) { newChannel++; } else { newChannel--; } if (newChannel > 16) newChannel = 0; if (newChannel < 0) newChannel = 16; if (newChannel != currentMidiChannel) { currentMidiChannel = newChannel; MIDI.begin(currentMidiChannel); // Reset MIDI, begin on current channel #if USE_OLED updateOLED(); #endif // Mark that channel changed and restart delay timer lastChannelChangeTime = millis(); channelChanged = true; } } lastClk = newClk; #endif } void setup() { // Validate saved midi channel in EEPROM if (savedChannel <= 16) { currentMidiChannel = savedChannel; } else { currentMidiChannel = MIDI_CHANNEL_OMNI; } #if USE_OLED if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // I2C addr 0x3C // Don't halt if missing, just skip display } else { oled.clearDisplay(); oled.drawBitmap(0, 0, bootuplogo, BOOTLOGO_WIDTH, BOOTLOGO_HEIGHT, 1); oled.setCursor(54, 18); oled.setTextColor(SSD1306_WHITE); oled.print("FW ver. " + String(FW_VER)); oled.display(); delay(2500); oled.setFont(&UiFont); } #endif #if USE_ENCODER pinMode(ENCODER_CLK, INPUT_PULLUP); pinMode(ENCODER_DT, INPUT_PULLUP); #endif // Midi Activity LED on Pin 13 (aka LED_BUILTIN) pinMode(LED_BUILTIN, OUTPUT); // Initialize MCP23017s mcp1.begin_I2C(0x20); mcp2.begin_I2C(0x21); // Initialize MIDI MIDI.begin(currentMidiChannel); MIDI.setHandleNoteOn(handleNoteOn); MIDI.setHandleNoteOff(handleNoteOff); for (int i = 0; i < 16; i++) { mcp1.pinMode(i, OUTPUT); mcp1.digitalWrite(i, LOW); mcp2.pinMode(i, OUTPUT); mcp2.digitalWrite(i, LOW); } updateOLED(); } void loop() { MIDI.read(); #if USE_ENCODER checkEncoder(); #endif // Save channel if change timeout has passed if (channelChanged && (millis() - lastChannelChangeTime >= saveDelay)) { EEPROM.update(EEPROM_MIDI_CHANNEL_ADDR, currentMidiChannel); channelChanged = false; // Show "Saved" message showSavedMessage = true; savedMessageStart = millis(); #if USE_OLED updateOLED(); #endif } if (showSavedMessage && (millis() - savedMessageStart >= savedMessageDuration)) { showSavedMessage = false; #if USE_OLED updateOLED(); // Return to normal channel display #endif } // LED feedback if (ledActive && millis() - ledOnTime >= ledDuration) { digitalWrite(LED_BUILTIN, LOW); ledActive = false; } } void handleNoteOn(byte channel, byte pitch, byte velocity) { if (currentMidiChannel == MIDI_CHANNEL_OMNI || channel == currentMidiChannel) { if (pitch >= 57 && pitch <= 88) { MCPOutput out = noteToMCP[pitch - 57]; if (out.chip == 0) { mcp1.digitalWrite(out.pin, HIGH); } else if (out.chip == 1) { mcp2.digitalWrite(out.pin, HIGH); } } } // Flash LED on Note On digitalWrite(LED_BUILTIN, HIGH); ledOnTime = millis(); ledActive = true; } void handleNoteOff(byte channel, byte pitch, byte velocity) { if (currentMidiChannel == MIDI_CHANNEL_OMNI || channel == currentMidiChannel) { if (pitch >= 57 && pitch <= 88) { MCPOutput out = noteToMCP[pitch - 57]; if (out.chip == 0) { mcp1.digitalWrite(out.pin, LOW); } else if (out.chip == 1) { mcp2.digitalWrite(out.pin, LOW); } } } }