Files
purrChestrion/purrChestrion-arduino/purrChestrion-arduino.ino
T

261 lines
6.2 KiB
Arduino
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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; // 015
};
// 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);
}
}
}
}