#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
////////////////////////////////////PIANO ROLL RELATED CODE TO BE REPLACED BY DIRECT MIDI IN CODE////////////////////////////////////
// Piano Roll Definitions
const int pianoRollPins[] = {23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 52};
const char* pianoRollNotes[] = {"C60", "C#61", "D62", "D#63", "E64", "F65", "F#66", "G67", "G#68", "A69", "A#70", "B71", "C72", "C#73", "D74", "D#75", "E76"};
const int numPianoRollKeys = sizeof(pianoRollPins) / sizeof(pianoRollPins[0]);
bool prevPianoRollButtonState[numPianoRollKeys] = {0};
unsigned long pianoRollButtonPressTimestamp[numPianoRollKeys] = {0};
/////////////////ENCODER STATE AND POSITION VARIABLES//////////////////////////
byte encoderStates[] = {0b00, 0b01, 0b11, 0b10};
byte prevState_LCD1_Note = 0;
byte prevState_Profile1_Mode = 0;
int encoderPosition_LCD1_Note = 0;
int encoderPosition_Profile1_Mode = 0;
byte prevState_LCD2_Note = 0;
byte prevState_LCD2_Mode = 0;
int encoderPosition_LCD2_Note = 0;
int encoderPosition_LCD2_Mode = 0;
byte prevState_notefilterlcd_Root = 0;
byte prevState_notefilterlcd_Mode = 0;
int encoderPosition_notefilterlcd_Root = 0;
int encoderPosition_notefilterlcd_Mode = 0;
/////////////////MUSICAL SCALES DEFINITIONS//////////////////////////
// Definitions for different musical scales in various banks
//Bank 1 Modes of Diatonic Scale
const int ionianFormula[] = {0, 2, 4, 5, 7, 9, 11,}; //(Checked for accuracy manually)
const int lydianFormula[] = {0, 2, 4, 6, 7, 9, 11,}; //(Checked for accuracy manually)
const int mixolydianFormula[] = {0, 2, 4, 5, 7, 9, 10,}; //(Checked for accuracy manually)
const int dorianFormula[] = {0, 2, 3, 5, 7, 9, 10,}; //(Checked for accuracy manually)
const int aeolianFormula[] = {0, 2, 3, 5, 7, 8, 10,}; //(Checked for accuracy manually)
const int phrygianFormula[] = {0, 1, 3, 5, 7, 8, 10,}; //(Checked for accuracy manually)
const int locrianFormula[] = {0, 1, 3, 5, 6, 8, 10,}; //(Checked for accuracy manually)
//Bank 2 Modes of Melodic Minor
const int melodicMinorFormula[] = {0, 2, 3, 5, 7, 9, 11,}; //(Checked for accuracy manually)
const int dorianb2Formula[] = {0, 1, 3, 5, 7, 9, 10,}; //(Checked for accuracy manually)
const int lydianAugmentedFormula[] = {0, 2, 4, 6, 8, 9, 11,}; //(Checked for accuracy manually)
const int lydianDominantFormula[] = {0, 2, 4, 6, 7, 9, 10,}; //(Checked for accuracy manually)
const int mixolydianb6Formula[] = {0, 2, 4, 5, 7, 8, 10,}; //(Checked for accuracy manually)
const int Aeolianb5[] = {0, 2, 3, 5, 6, 8, 10,}; //(Checked for accuracy manually)
const int superlocrianFormula[] = {0, 1, 3, 4, 6, 8, 10,}; //(Checked for accuracy manually)
//Bank 3 Modes of Harmonic Minor
const int harmonicMinorFormula[] = {0, 2, 3, 5, 7, 8, 11,}; //(Checked for accuracy manually)
const int locrianSharp6Formula[] = {0, 1, 3, 5, 6, 9, 10,}; //(Checked for accuracy manually)
const int ionianAugmentedFormula[] = {0, 2, 4, 5, 8, 9, 11,}; //(Checked for accuracy manually)
const int romanianMinorFormula[] = {0, 2, 3, 6, 7, 9, 10,}; //(Checked for accuracy manually)
const int phrygianDominantFormula[] = {0, 1, 4, 5, 7, 8, 10,}; //(Checked for accuracy manually)
const int lydianSharp2Formula[] = {0, 3, 4, 6, 7, 9, 11,}; //(Checked for accuracy manually)
const int ultralocrianFormula[] = {0, 1, 3, 4, 6, 8, 9,}; //(Checked for accuracy manually)
//Bank 4 Modes of Harmonic Major
const int harmonicMajorFormula[] = {0, 2, 4, 5, 7, 8, 11,}; //(Checked for accuracy manually)
const int dorianFlat5Formula[] = {0, 2, 3, 5, 6, 9, 10,}; //(Checked for accuracy manually)
const int phrygianFlat4Formula[] = {0, 1, 3, 4, 7, 8, 10,}; //(Checked for accuracy manually)
const int lydianMinorFormula[] = {0, 2, 3, 6, 7, 9, 11,}; //(Checked for accuracy manually)
const int mixolydianFlat2Formula[] = {0, 1, 4, 5, 7, 9, 10,}; //(Checked for accuracy manually)
const int lydianAugmentedSharp2Formula[] = {0, 3, 4, 6, 8, 9, 11,}; //(Checked for accuracy manually)
const int locrianDoubleFlat7Formula[] = {0, 1, 3, 5, 6, 8, 9,}; //(Checked for accuracy manually)
//Bank 5 Modes of Double Harmonic Major
const int DoubleHarmonicMajorFormula[] = {0, 1, 4, 5, 7, 8, 11,}; //(Checked for accuracy manually)
const int LydianSharp2Sharp6Formula[] = {0, 3, 4, 6, 7, 10, 11,}; //(Checked for accuracy manually)
const int UltraPhrygianFormula[] = {0, 1, 3, 4, 7, 8, 9,}; //(Checked for accuracy manually)
const int GypsyMinorFormula[] = {0, 2, 3, 6, 7, 8, 11,}; //(Checked for accuracy manually)
const int OrientalFormula[] = {0, 1, 4, 5, 6, 9, 10,}; //(Checked for accuracy manually)
const int IonianSharp2Sharp6[] = {0, 3, 4, 5, 8, 9, 11,}; //(Checked for accuracy manually)
const int LocrianDoubleflat3Doubleflat7Formula[] = {0, 1, 2, 5, 6, 8, 9,}; //(Checked for accuracy manually)
/////////////////MODE FORMULA FUNCTION//////////////////////////
// Function to get the mode formula based on bank and mode index
const int* getModeFormula(int bank, int modeIndex) {
switch (bank) {
case 1:
switch (modeIndex) {
case 0: return ionianFormula;
case 1: return dorianFormula;
case 2: return phrygianFormula;
case 3: return lydianFormula;
case 4: return mixolydianFormula;
case 5: return aeolianFormula;
case 6: return locrianFormula;
}
break;
case 2:
switch (modeIndex) {
case 0: return melodicMinorFormula;
case 1: return dorianb2Formula;
case 2: return lydianAugmentedFormula;
case 3: return lydianDominantFormula;
case 4: return mixolydianb6Formula;
case 5: return Aeolianb5;
case 6: return superlocrianFormula;
}
break;
case 3:
switch (modeIndex) {
case 0: return harmonicMinorFormula;
case 1: return locrianSharp6Formula;
case 2: return ionianAugmentedFormula;
case 3: return romanianMinorFormula;
case 4: return phrygianDominantFormula;
case 5: return lydianSharp2Formula;
case 6: return ultralocrianFormula;
}
break;
case 4:
switch (modeIndex) {
case 0: return harmonicMajorFormula;
case 1: return dorianFlat5Formula;
case 2: return phrygianFlat4Formula;
case 3: return lydianMinorFormula;
case 4: return mixolydianFlat2Formula;
case 5: return lydianAugmentedSharp2Formula;
case 6: return locrianDoubleFlat7Formula;
}
break;
case 5:
switch (modeIndex) {
case 0: return DoubleHarmonicMajorFormula;
case 1: return LydianSharp2Sharp6Formula;
case 2: return UltraPhrygianFormula;
case 3: return GypsyMinorFormula;
case 4: return OrientalFormula;
case 5: return IonianSharp2Sharp6;
case 6: return LocrianDoubleflat3Doubleflat7Formula;
}
break;
}
return ionianFormula; // default if not matching
}
/////////////////MODE NAME FUNCTION//////////////////////////
// Function to get the mode name based on the mode formula
String getModeName(const int* modeFormula) {
//Bank 1 Modenames
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";
//Bank 2 Modenames
if (modeFormula == melodicMinorFormula) return "MelodicMin";
if (modeFormula == dorianb2Formula) return "Dorian b2";
if (modeFormula == lydianAugmentedFormula) return "Lydian #5";
if (modeFormula == lydianDominantFormula) return "Lydian Dom";
if (modeFormula == mixolydianb6Formula) return "Mixolyd b6";
if (modeFormula == Aeolianb5) return "Aeolian b5";
if (modeFormula == superlocrianFormula) return "Altered";
//Bank 3 Modenames
if (modeFormula == harmonicMinorFormula) return "HarmonicMin";
if (modeFormula == locrianSharp6Formula) return "Locrian #6";
if (modeFormula == ionianAugmentedFormula) return "Ionian Aug";
if (modeFormula == romanianMinorFormula) return "RomanianMin";
if (modeFormula == phrygianDominantFormula) return "PhrygianDom";
if (modeFormula == lydianSharp2Formula) return "Lydian #2";
if (modeFormula == ultralocrianFormula) return "Ultra Loc.";
//Bank 4 Modenames
if (modeFormula == harmonicMajorFormula) return "HarmonicMaj";
if (modeFormula == dorianFlat5Formula) return "Dorian b5";
if (modeFormula == phrygianFlat4Formula) return "Phrygian b4";
if (modeFormula == lydianMinorFormula) return "LydianMinor";
if (modeFormula == mixolydianFlat2Formula) return "Mixolyd b2";
if (modeFormula == lydianAugmentedSharp2Formula) return "LydianAug#2";
if (modeFormula == locrianDoubleFlat7Formula) return "Locrian bb7";
//Bank 5 Modenames
if (modeFormula == DoubleHarmonicMajorFormula) return "DblHarmMaj";
if (modeFormula == LydianSharp2Sharp6Formula) return "Lydian #2#6";
if (modeFormula == UltraPhrygianFormula) return "UltraPhryg";
if (modeFormula == GypsyMinorFormula) return "GypsyMinor";
if (modeFormula == OrientalFormula) return "Oriental";
if (modeFormula == IonianSharp2Sharp6) return "Ionian #2#6";
if (modeFormula == LocrianDoubleflat3Doubleflat7Formula) return "Loc. bb3bb7";
return "Unknown";
}
/////////////////STATE VARIABLES//////////////////////////
int activeRootNote = 24;
int activeModeIndex = 0;
int standbyRootNote = 24;
int standbyModeIndex = 3;
int notefilterRootNote = 24;
int notefilterModeIndex = 0;
int currentlyPlayedMidiNote[36] = {-1}; // Initialized to -1 to indicate no note is being played.
const int* activeModeFormula = ionianFormula;
const int* standbyModeFormula = lydianFormula;
const int* notefilterModeFormula = ionianFormula; // Default to Ionian
bool prevNoteButtonState[36] = {0};
unsigned long noteButtonPressTimestamp[36] = {0};
////////////////////////////////////TEMPORARILY COMMENTED OUT TO TEST STUFF 1-11-24///////////////////////////////////////////////
//const int noteButtonPins[] = {53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 22, 24, 26, 28, 12, 13, 19, 18, 17, 16, 15, 14, 2, 3, 4, 5, 6, 7, 8, 9};
/////////////////UTILITY FUNCTIONS//////////////////////////
// Utility functions for note button handling, MIDI notes generation, etc.
////////////////////////////////////TEMPORARILY COMMENTED OUT TO TEST STUFF 1-11-24///////////////////////////////////////////////
/*
// This function reads the state of a note button and returns true if pressed, false otherwise
bool isNoteButtonPressed(int buttonIndex) {
return digitalRead(noteButtonPins[buttonIndex]) == LOW; // With INPUT_PULLUP, a LOW reading means the button is pressed
}
*/
//DEBOUNCE LOGIC FOR NOTEBUTTONS, TOGGLEBUTTON, SUSTAINBUTTON, AND NOTEdiffLEDS
const unsigned long DEBOUNCE_TIME = 10; // 50 ms for debounce
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
const int LCD1_NoteEncoderPinA = 57;
const int LCD1_NoteEncoderPinB = 58;
const int Profile1_ModeEncoderPinA = 54;
const int Profile1_ModeEncoderPinB = 55;
const int LCD2_NoteEncoderPinA = 36;
const int LCD2_NoteEncoderPinB = 38;
const int LCD2_modeEncoderPinA = 30;
const int LCD2_modeEncoderPinB = 32;
const int notefilterlcd_rootEncoder_SW = 2;
const int notefilterlcd_rootEncoder_CLK = 3;
const int notefilterlcd_rootEncoder_DT = 4;
const int notefilterlcd_modeEncoder_SW = 5;
const int notefilterlcd_modeEncoder_CLK = 6;
const int notefilterlcd_modeEncoder_DT = 7;
const int Profile1_RootEncoderButtonPin = A7;
const int Profile2_RootEncoderButtonPin = A6;
bool prevProfile1RootEncoderButtonState = HIGH;
bool prevProfile2RootEncoderButtonState = HIGH;
bool prevNotefilterModeSwitchState = HIGH;
int notefilterBank = 1; // Assuming 5 banks like the other LCDs
bool prevNotefilterBankSwitchState = HIGH;
int lcd1Bank = 1;
int lcd2Bank = 1;
LiquidCrystal_I2C lcd1(0x27, 16, 2);
LiquidCrystal_I2C lcd2(0x26, 16, 2);
LiquidCrystal_I2C notefilterlcd(0x28, 16, 2);
bool lcd1Active = true;
bool lcd1NeedsUpdate = true;
bool lcd2NeedsUpdate = true;
bool notefilterNeedsUpdate = true;
//footswitch
const int togglePin = A15;
bool profileToggle = false; // Variable to keep track of the button state
//Sustainbutton
const int sustainButtonPin = 10; // Sustain pedal pin
bool prevSustainButtonState = false; // Previous state of the sustain pedal
const int SUSTAIN_CONTROLLER_NUMBER = 64;
const int SUSTAIN_ON_VALUE = 127;
const int SUSTAIN_OFF_VALUE = 0;
// Define the LED pins
const int notediff_led[] = {A14, A13, A12, A11, A10, A9, A8};
bool prevLcd1BankSwitchState = HIGH;
bool prevLcd2BankSwitchState = HIGH;
void compareScalesAndUpdateLEDs() {
for (int i = 0; i < 7; i++) { // Check only the first 7 notes of the scales
if ((activeRootNote + activeModeFormula[i]) % 12 != (standbyRootNote + standbyModeFormula[i]) % 12) {
digitalWrite(notediff_led[i], HIGH);
} else {
digitalWrite(notediff_led[i], LOW);
}
}
}
String notefilterDisplayedNotes[7]; // Array to store notes displayed on notefilterlcd
bool isNoteAllowed(String note);
String extractNoteName(String noteWithOctave);
void extractNotefilterNotes(String scaleNotes);
void setup() {
Serial.begin(9600);
Serial.println("Setup complete");
MIDI.begin(MIDI_CHANNEL_OFF);
////////////////////////////////////PIANO ROLL RELATED CODE TO BE REPLACED BY DIRECT MIDI IN CODE////////////////////////////////////
// Initialize Piano Roll buttons
for (int i = 0; i < numPianoRollKeys; ++i) {
pinMode(pianoRollPins[i], INPUT_PULLUP);
prevPianoRollButtonState[i] = HIGH;
pianoRollButtonPressTimestamp[i] = 0;
}
Wire.begin();
// Initialize LCD screens
lcd1.init();
lcd1.backlight();
lcd1.clear();
lcd2.init();
lcd2.backlight();
lcd2.clear();
notefilterlcd.init();
notefilterlcd.backlight();
notefilterlcd.clear();
// Display a test message on notefilterlcd
//notefilterlcd.print("notefilterlcd OK");
// 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);
////////////////////////////////////////////TEMPORARILY DISABLED NOTEBUTTON INITIALIZATION TO DO SOME TESTING 1-11-24////////////////////////////////////////////
/*
//Initalize NOTEButtons
for (int i = 0; i < 36; ++i) {
pinMode(noteButtonPins[i], INPUT_PULLUP);
}
*/
//Initialize togglebutton and sustain button
pinMode(sustainButtonPin, INPUT_PULLUP);
pinMode(togglePin, INPUT_PULLUP);
//Initialize Encoders
pinMode(LCD1_NoteEncoderPinA, INPUT_PULLUP);
pinMode(LCD1_NoteEncoderPinB, INPUT_PULLUP);
pinMode(Profile1_ModeEncoderPinA, INPUT_PULLUP);
pinMode(Profile1_ModeEncoderPinB, INPUT_PULLUP);
pinMode(LCD2_NoteEncoderPinA, INPUT_PULLUP);
pinMode(LCD2_NoteEncoderPinB, INPUT_PULLUP);
pinMode(LCD2_modeEncoderPinA, INPUT_PULLUP);
pinMode(LCD2_modeEncoderPinB, INPUT_PULLUP);
pinMode(A5, INPUT_PULLUP); //mode encoder button 1
pinMode(40, INPUT_PULLUP); //mode encoder button 2
pinMode(5, INPUT_PULLUP); // mode encoder button 3
pinMode(Profile1_RootEncoderButtonPin, INPUT_PULLUP); //Root encoder button 1
pinMode(Profile2_RootEncoderButtonPin, INPUT_PULLUP); //Root encoder button 2
pinMode(notefilterlcd_rootEncoder_SW, INPUT_PULLUP);
pinMode(notefilterlcd_rootEncoder_DT, INPUT_PULLUP);
pinMode(notefilterlcd_rootEncoder_CLK, INPUT_PULLUP);
pinMode(notefilterlcd_modeEncoder_SW, INPUT_PULLUP);
pinMode(notefilterlcd_modeEncoder_DT, INPUT_PULLUP);
pinMode(notefilterlcd_modeEncoder_CLK, INPUT_PULLUP);
//INITIALIZE Profile Indicator Leds AS OUTPUT
pinMode(A2, OUTPUT);
pinMode(34, OUTPUT);
// Initialize LEDs as OUTPUT
for (int i = 0; i < 7; i++) {
pinMode(notediff_led[i], OUTPUT);
digitalWrite(notediff_led[i], LOW); // Initialize them as OFF
}
// Initial LED check based on the initial profile scales
compareScalesAndUpdateLEDs();
lcd1NeedsUpdate = true;
lcd2NeedsUpdate = true;
notefilterNeedsUpdate = true;
}
void loop() {
////////////////////////////////////PIANO ROLL RELATED CODE TO BE REPLACED BY DIRECT MIDI IN CODE////////////////////////////////////
// Piano Roll Button Logic
for (int i = 0; i < numPianoRollKeys; ++i) {
bool currentButtonState = digitalRead(pianoRollPins[i]) == LOW;
unsigned long currentTime = millis();
if (currentButtonState && !prevPianoRollButtonState[i]) {
if (currentTime - pianoRollButtonPressTimestamp[i] > DEBOUNCE_TIME) {
String playedNote = extractNoteName(pianoRollNotes[i]);
if (isNoteAllowed(playedNote)) {
Serial.print("Piano Roll Key Press: ");
Serial.println(pianoRollNotes[i]);
// Add MIDI Note On logic here if needed
}
pianoRollButtonPressTimestamp[i] = currentTime;
}
} else if (!currentButtonState && prevPianoRollButtonState[i]) {
if (currentTime - pianoRollButtonPressTimestamp[i] > DEBOUNCE_TIME) {
String playedNote = extractNoteName(pianoRollNotes[i]);
if (isNoteAllowed(playedNote)) {
Serial.print("Piano Roll Key Release: ");
Serial.println(pianoRollNotes[i]);
// Add MIDI Note Off logic here if needed
}
pianoRollButtonPressTimestamp[i] = currentTime;
}
}
prevPianoRollButtonState[i] = currentButtonState;
}
// For lcd1BankSwitch
bool currentLcd1BankSwitchState = digitalRead(A5);
if (currentLcd1BankSwitchState == LOW && prevLcd1BankSwitchState == HIGH) {
lcd1Bank = lcd1Bank % 5 + 1;
activeModeIndex = 0;
activeModeFormula = getModeFormula(lcd1Bank, activeModeIndex);
compareScalesAndUpdateLEDs();
lcd1NeedsUpdate = true;
delay(100);
}
prevLcd1BankSwitchState = currentLcd1BankSwitchState;
// For lcd2BankSwitch
bool currentLcd2BankSwitchState = digitalRead(40);
if (currentLcd2BankSwitchState == LOW && prevLcd2BankSwitchState == HIGH) {
lcd2Bank = lcd2Bank % 5 + 1;
standbyModeIndex = 0;
standbyModeFormula = getModeFormula(lcd2Bank, standbyModeIndex);
compareScalesAndUpdateLEDs();
lcd2NeedsUpdate = true;
delay(100);
}
prevLcd2BankSwitchState = currentLcd2BankSwitchState;
bool currentNotefilterBankSwitchState = digitalRead(5);
if (currentNotefilterBankSwitchState == LOW && prevNotefilterBankSwitchState == HIGH) {
notefilterBank = notefilterBank % 5 + 1;
notefilterModeIndex = 0;
notefilterModeFormula = getModeFormula(notefilterBank, notefilterModeIndex);
notefilterNeedsUpdate = true;
delay(100); // Debounce delay
}
prevNotefilterBankSwitchState = currentNotefilterBankSwitchState;
if ((millis() - lastDebounceTime) > DEBOUNCE_TIME) {
//TOGGLEBUTTON LOGIC
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/Profile
//Serial.println(lcd1Active ? "Profile1" : "Profile2");
delay(10); // Debouncing
// Managing Pin Voltage to profile indicator led 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 activex
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
}
//lcd encoder logic call to funtions at bottom of code
handleLCD1Encoders();
handleLCD2Encoders();
handleNotefilterLCDEncoders();
/*
/////////////////////////////NOTEBUTTON LOGIC TEMPORARILY DISABLED FOR TEST PURPOSES 1-11-24//////////////////////////////////////////////
///////////////////// NOTEBUTTON LOGIC/////////////////////////////////////
int noteButtonValues[36];
for (int i = 0; i < 36; ++i) {
noteButtonValues[i] = lcd1Active ?
getMidiNoteFromFormula(activeModeFormula, i, activeRootNote / 12) :
getMidiNoteFromFormula(standbyModeFormula, i, standbyRootNote / 12);
bool currentNoteButtonState = isNoteButtonPressed(i);
// If the button is currently pressed and wasn't previously, log the press and send MIDI Note ON
if (currentNoteButtonState && !prevNoteButtonState[i]) {
MIDI.sendNoteOn(noteButtonValues[i], 127, 1); // Send MIDI Note ON for the note value with velocity 127 on channel 1
currentlyPlayedMidiNote[i] = noteButtonValues[i];
noteButtonPressTimestamp[i] = millis(); // Store the current time
}
// If the button was previously pressed and is now released, log the release and send MIDI Note OFF
else if (!currentNoteButtonState && prevNoteButtonState[i]) {
if (currentlyPlayedMidiNote[i] != -1) { // Check if a note was played from this button
MIDI.sendNoteOff(currentlyPlayedMidiNote[i], 0, 1); // Send MIDI Note OFF for the stored note value with velocity 0 on channel 1
currentlyPlayedMidiNote[i] = -1; // Reset the stored note
}
}
prevNoteButtonState[i] = currentNoteButtonState;
}
*/
///////////////////////////////////SENDCONTROLCHANGE COMMANDS FOR SUSTAIN PEDAL TEMPORARILY DISABLED FOR TEST PURPOSES////////////////////////////////////////////////////
/////////////////SUSTAIN PEDAL LOGIC//////////////////////////
bool currentSustainButtonState = digitalRead(sustainButtonPin) == LOW; // Check the sustain pedal state (LOW means pressed with INPUT_PULLUP)
// If the sustain pedal is currently pressed and wasn't previously, send the sustain on message
if (currentSustainButtonState && !prevSustainButtonState) {
////////////////////////////MIDI.sendControlChange(SUSTAIN_CONTROLLER_NUMBER, SUSTAIN_ON_VALUE, 1); // Send sustain on message
}
// If the sustain pedal was previously pressed and is now released, send the sustain off message
else if (!currentSustainButtonState && prevSustainButtonState) {
////////////////////////////MIDI.sendControlChange(SUSTAIN_CONTROLLER_NUMBER, SUSTAIN_OFF_VALUE, 1); // Send sustain off message
}
prevSustainButtonState = currentSustainButtonState;
lastDebounceTime = millis();
}
//Rootnote encoders pushbutton
bool currentProfile1RootEncoderButtonState = digitalRead(Profile1_RootEncoderButtonPin);
bool currentProfile2RootEncoderButtonState = digitalRead(Profile2_RootEncoderButtonPin);
if (currentProfile1RootEncoderButtonState == LOW && prevProfile1RootEncoderButtonState == HIGH) {
// Additional functionality to set Profile 1 root note to match Profile 2 root note
activeRootNote = standbyRootNote;
lcd1NeedsUpdate = true;
compareScalesAndUpdateLEDs();
//Serial.print("Profile 1 Root set to: ");
//Serial.println(activeRootNote);
delay(100); // debounce delay
}
if (currentProfile2RootEncoderButtonState == LOW && prevProfile2RootEncoderButtonState == HIGH) {
// Additional functionality to set Profile 2 root note to match Profile 1 root note
standbyRootNote = activeRootNote;
lcd2NeedsUpdate = true;
compareScalesAndUpdateLEDs();
//Serial.print("Profile 2 Root set to: ");
//Serial.println(standbyRootNote);
delay(100); // debounce delay
}
prevProfile1RootEncoderButtonState = currentProfile1RootEncoderButtonState;
prevProfile2RootEncoderButtonState = currentProfile2RootEncoderButtonState;
// Update LCDs
if (lcd1NeedsUpdate) {
updateLCD(&lcd1, activeRootNote, activeModeFormula, lcd1Bank);
lcd1NeedsUpdate = false;
}
if (lcd2NeedsUpdate) {
updateLCD(&lcd2, standbyRootNote, standbyModeFormula, lcd2Bank);
lcd2NeedsUpdate = false;
}
if (notefilterNeedsUpdate) {
updateLCD(¬efilterlcd, notefilterRootNote, notefilterModeFormula, notefilterBank);
notefilterNeedsUpdate = false;
}
}
///////////////////#########################END OF LOOP FUNCTION########################//////////////////////////////////////
// isNoteAllowed Function Definition
bool isNoteAllowed(String note) {
for (int i = 0; i < 7; ++i) {
if (note == notefilterDisplayedNotes[i]) {
return true;
}
}
return false;
}
// extractNoteName Function Definition
String extractNoteName(String noteWithOctave) {
int noteEnd = noteWithOctave.indexOf('#') + 1;
noteEnd = noteEnd == 0 ? noteWithOctave.length() - 2 : noteEnd;
return noteWithOctave.substring(0, noteEnd);
}
int getMidiNoteFromFormula(const int* formula, int buttonIndex, int baseOctave) {
int formulaIndex = buttonIndex % 7;
int octaveShift = buttonIndex / 7;
int rootNote = lcd1Active ? activeRootNote : standbyRootNote; // Use the root note based on the active profile
return rootNote + formula[formulaIndex] + (octaveShift * 12);
}
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) + " " + getModeName(modeFormula);
lcd->print(output);
lcd->setCursor(0, 1);
String scaleNotes = getScaleNotes(rootNote, modeFormula);
lcd->print(scaleNotes);
if (lcd == ¬efilterlcd) {
extractNotefilterNotes(scaleNotes);
}
lcd->setCursor(15, 0);
lcd->print(String(bank));
}
void extractNotefilterNotes(String scaleNotes) {
int noteIndex = 0;
int lastSpace = -1;
int spaceIndex = scaleNotes.indexOf(' ', lastSpace + 1);
while(spaceIndex != -1 && noteIndex < 7) {
notefilterDisplayedNotes[noteIndex] = scaleNotes.substring(lastSpace + 1, spaceIndex);
noteIndex++;
lastSpace = spaceIndex;
spaceIndex = scaleNotes.indexOf(' ', lastSpace + 1);
}
// Add the last note (or the only note if no spaces were found)
if (noteIndex < 7) {
notefilterDisplayedNotes[noteIndex] = scaleNotes.substring(lastSpace + 1);
}
}
//This string handles calculating the note names and properly formatting them on the LCDs
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;
}
/////////////////////////LCD1 AND ITS ENCODERS//////////////////////////////
void handleLCD1Encoders() {
byte currentState_LCD1_Note = (digitalRead(LCD1_NoteEncoderPinA) << 1) | digitalRead(LCD1_NoteEncoderPinB);
byte currentState_Profile1_Mode = (digitalRead(Profile1_ModeEncoderPinA) << 1) | digitalRead(Profile1_ModeEncoderPinB);
// Check for LCD1 Note Encoder
for (byte i = 0; i < 4; i++) {
if (encoderStates[i] == currentState_LCD1_Note) {
if (i == (prevState_LCD1_Note + 1) % 4) {
encoderPosition_LCD1_Note--;
} else if (i == (prevState_LCD1_Note + 3) % 4) {
encoderPosition_LCD1_Note++;
}
prevState_LCD1_Note = i;
break;
}
}
// Check for Profile1 Mode Encoder
for (byte i = 0; i < 4; i++) {
if (encoderStates[i] == currentState_Profile1_Mode) {
if (i == (prevState_Profile1_Mode + 1) % 4) {
encoderPosition_Profile1_Mode--;
} else if (i == (prevState_Profile1_Mode + 3) % 4) {
encoderPosition_Profile1_Mode++;
}
prevState_Profile1_Mode = i;
break;
}
}
// No matter what I tried the encoders I purchased would read 2 inputs per click. This fixes that so the physical proof of concept is functional
if (encoderPosition_LCD1_Note % 2 == 0 && encoderPosition_LCD1_Note != 0) {
activeRootNote = constrain(activeRootNote + encoderPosition_LCD1_Note / 2, 0, 67);
lcd1NeedsUpdate = true;
compareScalesAndUpdateLEDs();
encoderPosition_LCD1_Note = 0; // Reset position change
}
// No matter what I tried the encoders I purchased would read 2 inputs per click. This fixes that so the physical proof of concept is functional
if (encoderPosition_Profile1_Mode % 2 == 0 && encoderPosition_Profile1_Mode != 0) {
activeModeIndex = (activeModeIndex + encoderPosition_Profile1_Mode / 2 + 7) % 7;
activeModeFormula = getModeFormula(lcd1Bank, activeModeIndex);
lcd1NeedsUpdate = true;
compareScalesAndUpdateLEDs();
encoderPosition_Profile1_Mode = 0; // Reset position change
}
}
////////////////LCD2 AND ITS ENCODERS/////////////////////////
void handleLCD2Encoders() {
byte currentState_LCD2_Note = (digitalRead(LCD2_NoteEncoderPinA) << 1) | digitalRead(LCD2_NoteEncoderPinB);
byte currentState_LCD2_Mode = (digitalRead(LCD2_modeEncoderPinA) << 1) | digitalRead(LCD2_modeEncoderPinB);
// Check for LCD2 Note Encoder
for (byte i = 0; i < 4; i++) {
if (encoderStates[i] == currentState_LCD2_Note) {
if (i == (prevState_LCD2_Note + 1) % 4) {
encoderPosition_LCD2_Note--;
} else if (i == (prevState_LCD2_Note + 3) % 4) {
encoderPosition_LCD2_Note++;
}
prevState_LCD2_Note = i;
break;
}
}
// Check for LCD2 Mode Encoder
for (byte i = 0; i < 4; i++) {
if (encoderStates[i] == currentState_LCD2_Mode) {
if (i == (prevState_LCD2_Mode + 1) % 4) {
encoderPosition_LCD2_Mode--;
} else if (i == (prevState_LCD2_Mode + 3) % 4) {
encoderPosition_LCD2_Mode++;
}
prevState_LCD2_Mode = i;
break;
}
}
// No matter what I tried the encoders I purchased would read 2 inputs per click. This fixes that so the physical proof of concept is functional// No matter what I tried the encoders would read 2 inputs per click. This fixes that so the physical proof of concept if functional
if (encoderPosition_LCD2_Note % 2 == 0 && encoderPosition_LCD2_Note != 0) {
standbyRootNote = constrain(standbyRootNote + encoderPosition_LCD2_Note / 2, 0, 67);
lcd2NeedsUpdate = true;
compareScalesAndUpdateLEDs();
encoderPosition_LCD2_Note = 0; // Reset position change
}
// No matter what I tried the encoders I purchased would read 2 inputs per click. This fixes that so the physical proof of concept is functional
if (encoderPosition_LCD2_Mode % 2 == 0 && encoderPosition_LCD2_Mode != 0) {
standbyModeIndex = (standbyModeIndex + encoderPosition_LCD2_Mode / 2 + 7) % 7;
standbyModeFormula = getModeFormula(lcd2Bank, standbyModeIndex);
lcd2NeedsUpdate = true;
compareScalesAndUpdateLEDs();
encoderPosition_LCD2_Mode = 0; // Reset position change
}
}
///////////////////////////NOTEFILTERLCD ENCODERS/////////////////////////////////
void handleNotefilterLCDEncoders() {
byte currentState_notefilterlcd_Root = (digitalRead(notefilterlcd_rootEncoder_DT) << 1) | digitalRead(notefilterlcd_rootEncoder_CLK);
byte currentState_notefilterlcd_Mode = (digitalRead(notefilterlcd_modeEncoder_DT) << 1) | digitalRead(notefilterlcd_modeEncoder_CLK);
// Check for notefilterlcd Root Encoder
for (byte i = 0; i < 4; i++) {
if (encoderStates[i] == currentState_notefilterlcd_Root) {
if (i == (prevState_notefilterlcd_Root + 1) % 4) {
encoderPosition_notefilterlcd_Root--;
} else if (i == (prevState_notefilterlcd_Root + 3) % 4) {
encoderPosition_notefilterlcd_Root++;
}
prevState_notefilterlcd_Root = i;
break;
}
}
// Check for notefilterlcd Mode Encoder
for (byte i = 0; i < 4; i++) {
if (encoderStates[i] == currentState_notefilterlcd_Mode) {
if (i == (prevState_notefilterlcd_Mode + 1) % 4) {
encoderPosition_notefilterlcd_Mode--;
} else if (i == (prevState_notefilterlcd_Mode + 3) % 4) {
encoderPosition_notefilterlcd_Mode++;
}
prevState_notefilterlcd_Mode = i;
break;
}
}
// Logic for notefilterlcd Root Encoder
if (encoderPosition_notefilterlcd_Root % 2 == 0 && encoderPosition_notefilterlcd_Root != 0) {
notefilterRootNote = constrain(notefilterRootNote + encoderPosition_notefilterlcd_Root / 2, 0, 67);
notefilterNeedsUpdate = true; // Set flag to indicate notefilterlcd needs an update
encoderPosition_notefilterlcd_Root = 0; // Reset position change
}
// Logic for notefilterlcd Mode Encoder
if (encoderPosition_notefilterlcd_Mode % 2 == 0 && encoderPosition_notefilterlcd_Mode != 0) {
notefilterModeIndex = (notefilterModeIndex + encoderPosition_notefilterlcd_Mode / 2 + 7) % 7;
notefilterModeFormula = getModeFormula(notefilterBank, notefilterModeIndex);
notefilterNeedsUpdate = true; // Set flag to indicate notefilterlcd needs an update
encoderPosition_notefilterlcd_Mode = 0; // Reset position change
}
}