#include <Wire.h>
#include <LiquidCrystal_I2C.h>
const int ionianFormula[] = {0, 2, 4, 5, 7, 9, 11};
const int lydianFormula[] = {0, 2, 4, 6, 7, 9, 11};
const int mixolydianFormula[] = {0, 2, 4, 5, 7, 9, 10};
const int dorianFormula[] = {0, 2, 3, 5, 7, 9, 10};
const int aeolianFormula[] = {0, 2, 3, 5, 7, 8, 10};
const int phrygianFormula[] = {0, 1, 3, 5, 7, 8, 10};
const int locrianFormula[] = {0, 1, 3, 5, 6, 8, 10};
int activeRootNote = 60;
int activeModeIndex = 1;
int standbyRootNote = 60;
int standbyModeIndex = 0;
const int* activeModeFormula = ionianFormula;
const int* standbyModeFormula = lydianFormula;
const int noteButtonPins[] = {53, 51, 49, 47, 45, 43, 41};
const int LCD1_NoteEncoderPinA = 57;
const int LCD1_NoteEncoderPinB = 58;
const int LCD1_modeEncoderPinA = 54;
const int LCD1_modeEncoderPinB = 55;
const int LCD2_NoteEncoderPinA = 36;
const int LCD2_NoteEncoderPinB = 38;
const int LCD2_modeEncoderPinA = 30;
const int LCD2_modeEncoderPinB = 32;
LiquidCrystal_I2C lcd1(0x27, 16, 2);
LiquidCrystal_I2C lcd2(0x3F, 16, 2);
bool lcd1Active = true;
bool lcd1NeedsUpdate = true;
bool lcd2NeedsUpdate = true;
void setup() {
Wire.begin();
// Initialize LCD screens
lcd1.init();
lcd1.backlight();
lcd1.clear();
lcd2.init();
lcd2.backlight();
lcd2.clear();
delay(500); // Delay to ensure LCDs have enough time to initialize
Serial.begin(9600);
activeModeFormula = ionianFormula;
for (int i = 0; i < 7; ++i) {
pinMode(noteButtonPins[i], INPUT_PULLUP);
}
pinMode(LCD1_NoteEncoderPinA, INPUT_PULLUP);
pinMode(LCD1_NoteEncoderPinB, INPUT_PULLUP);
pinMode(LCD1_modeEncoderPinA, INPUT_PULLUP);
pinMode(LCD1_modeEncoderPinB, INPUT_PULLUP);
pinMode(LCD2_NoteEncoderPinA, INPUT_PULLUP);
pinMode(LCD2_NoteEncoderPinB, INPUT_PULLUP);
pinMode(LCD2_modeEncoderPinA, INPUT_PULLUP);
pinMode(LCD2_modeEncoderPinB, INPUT_PULLUP);
}
void loop() {
int encoderValueA, encoderValueB;
if (lcd1Active) {
encoderValueA = digitalRead(LCD1_NoteEncoderPinA);
encoderValueB = digitalRead(LCD1_NoteEncoderPinB);
} else {
encoderValueA = digitalRead(LCD2_NoteEncoderPinA);
encoderValueB = digitalRead(LCD2_NoteEncoderPinB);
}
if (encoderValueA == HIGH && encoderValueB == LOW) {
if (lcd1Active) {
activeRootNote = (activeRootNote + 1) % 128;
} else {
standbyRootNote = (standbyRootNote + 1) % 128;
}
printRootNoteChange();
lcd1NeedsUpdate = lcd1Active;
lcd2NeedsUpdate = !lcd1Active;
delay(100);
} else if (encoderValueA == LOW && encoderValueB == HIGH) {
if (lcd1Active) {
activeRootNote = (activeRootNote - 1 + 128) % 128;
} else {
standbyRootNote = (standbyRootNote - 1 + 128) % 128;
}
printRootNoteChange();
lcd1NeedsUpdate = lcd1Active;
lcd2NeedsUpdate = !lcd1Active;
delay(100);
}
int noteButtonValues[7];
for (int i = 0; i < 7; ++i) {
noteButtonValues[i] = lcd1Active ? (activeRootNote + activeModeFormula[i]) : (standbyRootNote + standbyModeFormula[i]);
if (digitalRead(noteButtonPins[i]) == LOW) {
String noteName = getNoteName(noteButtonValues[i] % 12);
Serial.print("Button ");
Serial.print(i + 1);
Serial.print(" Pressed: Note Value = ");
Serial.print(noteButtonValues[i]);
Serial.print(" (");
Serial.print(noteName);
Serial.println(")");
delay(100);
}
}
int LCD1_modeEncoderValueA, LCD1_modeEncoderValueB;
if (lcd1Active) {
LCD1_modeEncoderValueA = digitalRead(LCD1_modeEncoderPinA);
LCD1_modeEncoderValueB = digitalRead(LCD1_modeEncoderPinB);
} else {
LCD1_modeEncoderValueA = digitalRead(LCD2_modeEncoderPinA);
LCD1_modeEncoderValueB = digitalRead(LCD2_modeEncoderPinB);
}
if (LCD1_modeEncoderValueA == HIGH && LCD1_modeEncoderValueB == LOW) {
if (lcd1Active) {
activeModeIndex = (activeModeIndex + 1) % 7;
activeModeFormula = getModeFormula(activeModeIndex);
} else {
standbyModeIndex = (standbyModeIndex + 1) % 7;
standbyModeFormula = getModeFormula(standbyModeIndex);
}
printModeChange();
lcd1NeedsUpdate = lcd1Active;
lcd2NeedsUpdate = !lcd1Active;
delay(100);
} else if (LCD1_modeEncoderValueA == LOW && LCD1_modeEncoderValueB == HIGH) {
if (lcd1Active) {
activeModeIndex = (activeModeIndex - 1 + 7) % 7;
activeModeFormula = getModeFormula(activeModeIndex);
} else {
standbyModeIndex = (standbyModeIndex - 1 + 7) % 7;
standbyModeFormula = getModeFormula(standbyModeIndex);
}
printModeChange();
lcd1NeedsUpdate = lcd1Active;
lcd2NeedsUpdate = !lcd1Active;
delay(100);
}
if (lcd1NeedsUpdate) {
updateLCD(&lcd1, activeRootNote, activeModeFormula);
lcd1NeedsUpdate = false;
}
if (lcd2NeedsUpdate) {
updateLCD(&lcd2, standbyRootNote, standbyModeFormula);
lcd2NeedsUpdate = false;
}
}
const int* getModeFormula(int modeIndex) {
switch (modeIndex) {
case 0: return lydianFormula;
case 1: return ionianFormula;
case 2: return mixolydianFormula;
case 3: return dorianFormula;
case 4: return aeolianFormula;
case 5: return phrygianFormula;
case 6: return locrianFormula;
default: return ionianFormula;
}
}
String getNoteName(int noteValue) {
String noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
return noteNames[noteValue];
}
void updateLCD(LiquidCrystal_I2C* lcd, int rootNote, const int* modeFormula) {
lcd->clear();
lcd->setCursor(0, 0);
String noteName = getNoteName(rootNote % 12);
String output = noteName + String(rootNote / 12);
output += " " + getModeName(modeFormula);
lcd->print(output);
lcd->setCursor(0, 1);
lcd->print(getScaleNotes(rootNote, modeFormula));
}
String getScaleNotes(int rootNote, const int* modeFormula) {
String scaleNotes = "";
for (int i = 0; i < 7; i++) {
int noteValue = (rootNote + modeFormula[i]) % 12;
String noteName = getNoteName(noteValue);
if (i == 0) {
scaleNotes += noteName;
} else {
if (noteName.endsWith("#")) {
if (scaleNotes.endsWith("#")) {
scaleNotes += noteName;
} else {
scaleNotes += " " + noteName;
}
} else {
scaleNotes += " " + noteName;
}
}
}
return scaleNotes;
}
String getModeName(const int* modeFormula) {
if (modeFormula == lydianFormula) return "Lydian";
if (modeFormula == ionianFormula) return "Ionian";
if (modeFormula == mixolydianFormula) return "Mixolydian";
if (modeFormula == dorianFormula) return "Dorian";
if (modeFormula == aeolianFormula) return "Aeolian";
if (modeFormula == phrygianFormula) return "Phrygian";
if (modeFormula == locrianFormula) return "Locrian";
return "Unknown";
}
void printRootNoteChange() {
Serial.print("Root Note Changed: Note Value = ");
Serial.print(activeRootNote);
Serial.print(" (");
Serial.print(getNoteName(activeRootNote % 12));
Serial.println(")");
}
void printModeChange() {
Serial.print("Mode Changed: ");
Serial.println(getModeName(activeModeFormula));
}