#include <Wire.h>
#include <LiquidCrystal_I2C.h>
int lcd1Bank = 1;
int lcd2Bank = 1;
const int ionianFormula[] = {0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24, 26, 28, 29, 31, 33, 35, 36, 38, 40, 41, 43, 45, 47, 48, 50, 52, 53, 55, 57, 59, 60};
const int lydianFormula[] = {0, 2, 4, 6, 7, 9, 11, 12, 14, 16, 18, 19, 21, 23, 24, 26, 28, 30, 31, 33, 35, 36, 38, 40, 42, 43, 45, 47, 48, 50, 52, 54, 55, 57, 59, 60};
const int mixolydianFormula[] = {0, 2, 4, 5, 7, 9, 10, 12, 14, 16, 17, 19, 21, 22, 24, 26, 28, 29, 31, 33, 34, 36, 38, 40, 41, 43, 45, 46, 48, 50, 52, 53, 55, 57, 58, 60};
const int dorianFormula[] = {0, 2, 3, 5, 7, 9, 10, 12, 14, 15, 17, 19, 21, 22, 24, 26, 27, 29, 31, 33, 34, 36, 38, 39, 41, 43, 45, 46, 48, 50, 51, 53, 55, 57, 58, 60};
const int aeolianFormula[] = {0, 2, 3, 5, 7, 8, 10, 12, 14, 15, 17, 19, 20, 22, 24, 26, 27, 29, 31, 32, 34, 36, 38, 39, 41, 43, 44, 46, 48, 50, 51, 53, 55, 56, 58, 60};
const int phrygianFormula[] = {0, 1, 3, 5, 7, 8, 10, 12, 13, 15, 17, 19, 20, 22, 24, 25, 27, 29, 31, 32, 34, 36, 37, 39, 41, 43, 44, 46, 48, 49, 51, 53, 55, 56, 58, 60};
const int locrianFormula[] = {0, 1, 3, 5, 6, 8, 10, 12, 13, 15, 17, 18, 20, 22, 24, 25, 27, 29, 30, 32, 34, 36, 37, 39, 41, 42, 44, 46, 48, 49, 51, 53, 54, 56, 58, 60};
int activeRootNote = 24;
int activeModeIndex = 1;
int standbyRootNote = 24;
int standbyModeIndex = 0;
const int* activeModeFormula = ionianFormula;
const int* standbyModeFormula = lydianFormula;
const int noteButtonPins[] = {53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 22, 24, 26, 28, 21, 20, 19, 18, 17, 16, 15, 14, 2, 3, 4, 5, 6, 7, 8, 9};
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;
//footswitch
const int togglePin = A15;
bool profileToggle = false; // Variable to keep track of the button state
void setup() {
Wire.begin();
// Initialize LCD screens
lcd1.init();
lcd1.backlight();
lcd1.clear();
lcd2.init();
lcd2.backlight();
lcd2.clear();
// Set initial led pin states based on lcd1Active
if (lcd1Active) {
digitalWrite(A2, HIGH); // Illuminate LED on pin A2 for Profile 1
digitalWrite(34, LOW); // Turn off LED on pin 34
} else {
digitalWrite(A2, LOW); // Turn off LED on pin A2
digitalWrite(34, HIGH); // Illuminate LED on pin 34 for Profile 2
}
delay(500); // Delay to ensure LCDs have enough time to initialize
Serial.begin(9600);
activeModeFormula = ionianFormula;
for (int i = 0; i < 36; ++i) { // Update this line
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);
pinMode(togglePin, INPUT_PULLUP);
pinMode(A2, OUTPUT);
pinMode(34, OUTPUT);
pinMode(A5, INPUT_PULLUP); //encoder button 1
pinMode(40, INPUT_PULLUP); //encoder button 2
}
void loop() {
int lcd2BankSwitch = digitalRead(40);
if (lcd2BankSwitch == LOW) {
lcd2Bank = lcd2Bank % 4 + 1;
lcd2NeedsUpdate = true;
delay(100);
}
int toggleState = digitalRead(togglePin);
if (toggleState == LOW && !profileToggle) { // Button pressed and profile not yet toggled
profileToggle = true; // Set profileToggle to true to prevent continuous toggling
lcd1Active = !lcd1Active; // Toggle the active LCD
Serial.println(lcd1Active ? "Profile1" : "Profile2");
delay(500); // Debouncing
// Managing Pin Voltage based on Active Profile
if (lcd1Active) { // Profile 1 is active
digitalWrite(A2, HIGH); // Apply 5V to pin A2
digitalWrite(34, LOW); // No voltage on pin 34
} else { // Profile 2 is active
digitalWrite(A2, LOW); // No voltage on pin A2
digitalWrite(34, HIGH); // Apply 5V to pin 34
}
} else if (toggleState == HIGH && profileToggle) { // Button released and profile toggled
profileToggle = false; // Reset profileToggle for the next button press
}
int noteButtonValues[36];
for (int i = 0; i < 36; ++i) {
noteButtonValues[i] = lcd1Active ? (activeRootNote + activeModeFormula[i % 36]) : (standbyRootNote + standbyModeFormula[i % 36]);
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);
}
}
// LCD1 Encoder Logic
int LCD1_encoderValueA = digitalRead(LCD1_NoteEncoderPinA);
int LCD1_encoderValueB = digitalRead(LCD1_NoteEncoderPinB);
// LCD1 Encoder Logic for Root Note
if (LCD1_encoderValueA == HIGH && LCD1_encoderValueB == LOW) {
activeRootNote = constrain(activeRootNote - 1, 0, 67);
Serial.print("LCD1 Root Note: ");
Serial.println(activeRootNote);
lcd1NeedsUpdate = true;
delay(100);
} else if (LCD1_encoderValueA == LOW && LCD1_encoderValueB == HIGH) {
activeRootNote = constrain(activeRootNote + 1, 0, 67);
Serial.print("LCD1 Root Note: ");
Serial.println(activeRootNote);
lcd1NeedsUpdate = true;
delay(100);
}
int LCD1_modeEncoderValueA = digitalRead(LCD1_modeEncoderPinA);
int LCD1_modeEncoderValueB = digitalRead(LCD1_modeEncoderPinB);
if (LCD1_modeEncoderValueA == HIGH && LCD1_modeEncoderValueB == LOW) {
activeModeIndex = (activeModeIndex + 1) % 7;
activeModeFormula = getModeFormula(activeModeIndex);
Serial.print("LCD1 Mode: ");
Serial.println(activeModeIndex);
lcd1NeedsUpdate = true;
delay(100);
} else if (LCD1_modeEncoderValueA == LOW && LCD1_modeEncoderValueB == HIGH) {
activeModeIndex = (activeModeIndex - 1 + 7) % 7;
activeModeFormula = getModeFormula(activeModeIndex);
Serial.print("LCD1 Mode: ");
Serial.println(activeModeIndex);
lcd1NeedsUpdate = true;
delay(100);
}
// LCD2 Encoder Logic
int LCD2_encoderValueA = digitalRead(LCD2_NoteEncoderPinA);
int LCD2_encoderValueB = digitalRead(LCD2_NoteEncoderPinB);
// LCD2 Encoder Logic for Root Note
if (LCD2_encoderValueA == HIGH && LCD2_encoderValueB == LOW) {
standbyRootNote = constrain(standbyRootNote - 1, 0, 67);
Serial.print("LCD2 Root Note: ");
Serial.println(standbyRootNote);
lcd2NeedsUpdate = true;
delay(100);
} else if (LCD2_encoderValueA == LOW && LCD2_encoderValueB == HIGH) {
standbyRootNote = constrain(standbyRootNote + 1, 0, 67);
Serial.print("LCD2 Root Note: ");
Serial.println(standbyRootNote);
lcd2NeedsUpdate = true;
delay(100);
}
int LCD2_modeEncoderValueA = digitalRead(LCD2_modeEncoderPinA);
int LCD2_modeEncoderValueB = digitalRead(LCD2_modeEncoderPinB);
if (LCD2_modeEncoderValueA == HIGH && LCD2_modeEncoderValueB == LOW) {
standbyModeIndex = (standbyModeIndex + 1) % 7;
standbyModeFormula = getModeFormula(standbyModeIndex);
Serial.print("LCD2 Mode: ");
Serial.println(standbyModeIndex);
lcd2NeedsUpdate = true;
delay(100);
} else if (LCD2_modeEncoderValueA == LOW && LCD2_modeEncoderValueB == HIGH) {
standbyModeIndex = (standbyModeIndex - 1 + 7) % 7;
standbyModeFormula = getModeFormula(standbyModeIndex);
Serial.print("LCD2 Mode: ");
Serial.println(standbyModeIndex);
lcd2NeedsUpdate = true;
delay(100);
}
// Bank switch logic
int lcd1BankSwitch = digitalRead(A5);
if (lcd1BankSwitch == LOW) {
lcd1Bank = lcd1Bank % 4 + 1;
lcd1NeedsUpdate = true;
delay(100);
}
// Update LCDs
if (lcd1NeedsUpdate) {
updateLCD(&lcd1, activeRootNote, activeModeFormula, lcd1Bank);
lcd1NeedsUpdate = false;
}
if (lcd2NeedsUpdate) {
updateLCD(&lcd2, standbyRootNote, standbyModeFormula, lcd2Bank);
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, int bank) {
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));
lcd->setCursor(15, 0); // Position cursor at the last character of the first row
lcd->print(bank); // Display the bank number
}
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));
}