261 lines
6.2 KiB
Arduino
261 lines
6.2 KiB
Arduino
#define FW_VER 1.0
|
||
#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 <EEPROM.h>
|
||
#include <Adafruit_MCP23X17.h>
|
||
#include <MIDI.h>
|
||
|
||
#if USE_OLED
|
||
#include <Wire.h>
|
||
#include <Adafruit_GFX.h>
|
||
#include <Adafruit_SSD1306.h>
|
||
#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;
|
||
updateOLED();
|
||
|
||
// 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(62, 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);
|
||
}
|
||
}
|
||
}
|
||
} |