#include <MemoryUsage.h>;
#include <SD.h>
#include <LiquidCrystal.h>
#define CS_PIN 53
#define CHORD_BUTTON_PIN 22
#define RESET_BUTTON_PIN 23
#define NOTE_ON 144
#define NOTE_OFF 128
LiquidCrystal lcd(8,9,5,4,3,2);
int currentSequenceIndex = -1;
boolean entered = false;
long lastDebounceTime = 0; // the last time the output pin was toggled
long debounceDelay = 10; // the debounce time; increase if the output flickers
int chordLastState = HIGH;
int resetLastState = HIGH;
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////////////////////// CLASSES ///////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
class Chord {
public:
String* notes;
String name = "";
int numNotes;
Chord(){}
Chord(String name, String* notes, int numNotes) {
this->numNotes = numNotes;
this->name = name;
this->notes = notes;
}
};
class Data {
public:
int size = 1;
int count = 0;
String* sequence;
int numSequenceChords = 0;
Chord* chords;
Data(int size) {
this->size = size;
/*for (int i = 0; i < size; i++) {
this->chords[i] = Chord();
}*/
}
void addChord(String name, String* notes, int numNotes) {
if ( this->count == 0 ) {
this->chords = (Chord*)malloc(sizeof(Chord) * this->size);
}
this->chords[this->count++] = Chord(name, notes, numNotes);
}
~Data() {
delete[] this->chords;
delete[] this->sequence;
}
};
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////////////////////// FUNCTIONS ///////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void writeFile(){
// Load chords from SD file
File textFile = SD.open("wokwi.txt", FILE_WRITE);
if ( textFile ) {
textFile.write("G#m,Am,Dm,A,Bdim,Dm$44,47|45,48,34|38,45,56|45,49,52,34|47,50,12|38,50$0,1,2,3,4,3,5");
textFile.close();
} else {
Serial.println("write failed");
textFile.close();
return 0;
}
}
String openFile(){
// Load chords from SD file
File textFile = SD.open("wokwi.txt");
if ( textFile.available() ) {
String str = textFile.readString();
textFile.close();
return str;
} else {
textFile.close();
Serial.println("open failed");
return "";
}
}
String* split(String inputStr, char delimiter, int &size, boolean prnt = false) {
String* strs = new String[20];
int count = 0;
// Split the string into substrings
while ( inputStr.length() > 0 ) {
int index = inputStr.indexOf(delimiter);
if ( index > -1 ) {
strs[count] = inputStr.substring(0, index);
if (prnt) {
Serial.print("splitaa");
Serial.print(count);
Serial.print(": ");
Serial.println(strs[count]);
delay(100);
}
inputStr = inputStr.substring(index+1);
count++;
} else {
strs[count] = inputStr;
if (prnt) {
Serial.print("splitb");
Serial.print(count);
Serial.print(": ");
Serial.println(strs[count]);
delay(100);
}
break;
}
}
size = count+1;
count++;
strs[count] = '\0';
return strs;
}
void printLCD(String current, String next) {
lcd.begin(16, 2);
lcd.print(current);
lcd.print(" - CURRENT");
lcd.setCursor(0, 1);
if ( next != "" ) {
lcd.print(next);
lcd.print(" - NEXT");
} else {
lcd.print("END***************");
}
}
void resetDisplay() {
lcd.begin(16, 2);
lcd.print(" Chords loaded.");
lcd.setCursor(0, 1);
lcd.print(" LET'S GO!");
}
void sendNote(int onOff, String note) {
Serial.write(onOff);
Serial.write(note.toInt());
Serial.write(127); // Velocity
}
void sendNotes(int onOff, String* notes, int size) {
for ( int i = 0; i < size; i++ ) {
sendNote(onOff, notes[i]);
}
}
void reset() {
currentSequenceIndex = -1;
Serial.println("RESET");
lastDebounceTime = millis(); //set the current time
resetDisplay();
entered = false;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////////////////////// SETUP ///////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
Data* data;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
if (!SD.begin(CS_PIN)) {
Serial.println("Card initialization failed!");
while (true);
}
Serial.println("initialization done.");
pinMode(CHORD_BUTTON_PIN, INPUT_PULLUP);
pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);
resetDisplay();
// Write file for testing purposes
writeFile();
// Open file to get all data
String inputStr = openFile();
if ( inputStr == "" ) {
Serial.println("Error, file is empty.");
return 0;
}
int size = 0;
// Split by $ to get every piece of data separated
String* values = split(inputStr, '$', size, false);
//Serial.println(values[0]); // 5
//Serial.println(values[1]); // G#m,Am,Dm,A,Bdim
//Serial.println(values[2]); // 44,47,51|45,48,52|38,41,45|45,49,52|47,50,53
//Serial.println(values[3]); // 0,1,2,3,4,3
//FREERAM_PRINT
{
String* names;
String* notes;
String* notesNumbers;
int numChords = 0;
names = split(values[0], ',', numChords, false);
data = new Data(numChords);
notes = split(values[1], '|', size, false);
for(int i = 0; i < numChords; i++) {
notesNumbers = split(notes[i], ',', size, false);
data->addChord(names[i], notesNumbers, size);
}
data->sequence = split(values[2], ',', data->numSequenceChords, false);
}
/*
for(int i = 0; i < data->numSequenceChords; i++) {
int currentIndex = data->sequence[i].toInt();
Serial.print(data->chords[currentIndex].name);
Serial.print("(");
Serial.print(data->chords[currentIndex].numNotes);
Serial.print("): ");
for(int j = 0;
j < data->chords[currentIndex].numNotes;
j++ ) {
Serial.print(data->chords[currentIndex].notes[j]);
if ( j < data->chords[currentIndex].numNotes - 1 ) {
Serial.print(", ");
}
}
Serial.println("");
}
*/
FREERAM_PRINT
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////////////////////// LOOP ///////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void loop() {
// Read the action button. This one moves the chord progression.
int chordButtonValue = digitalRead(CHORD_BUTTON_PIN);
int resetButtonValue = digitalRead(RESET_BUTTON_PIN);
//filter out any noise by setting a time buffer
if ( (millis() - lastDebounceTime) > debounceDelay) {
// resetButtonValue Button down
if (resetLastState != resetButtonValue) {
if (resetButtonValue == LOW) {
resetLastState = resetButtonValue;
reset();
}
if (resetButtonValue == HIGH) {
resetLastState = resetButtonValue;
lastDebounceTime = millis(); //set the current time
}
}
}
//filter out any noise by setting a time buffer
if ( (millis() - lastDebounceTime) > debounceDelay) {
// chordButtonValue Button down
if (chordButtonValue == LOW) {
// Only display it once.
if ( entered == false ){
entered = true;
if ( currentSequenceIndex < data->numSequenceChords ) {
currentSequenceIndex++;
delay(debounceDelay);
}
if ( currentSequenceIndex < data->numSequenceChords - 1 ) {
printLCD(data->chords[data->sequence[currentSequenceIndex].toInt()].name, data->chords[data->sequence[currentSequenceIndex+1].toInt()].name);
} else if( currentSequenceIndex == data->numSequenceChords - 1 ) {
printLCD(data->chords[data->sequence[currentSequenceIndex].toInt()].name, "");
} else {
resetLastState = resetButtonValue;
reset();
}
}
if ( currentSequenceIndex > -1 ) {
sendNotes(NOTE_ON, data->chords[data->sequence[currentSequenceIndex].toInt()].notes, data->chords[data->sequence[currentSequenceIndex].toInt()].numNotes);
}
lastDebounceTime = millis(); //set the current time
}
}
// Do not repeat
if (chordLastState != chordButtonValue) {
chordLastState = chordButtonValue;
if (chordButtonValue == HIGH) {
sendNotes(NOTE_OFF, data->chords[data->sequence[currentSequenceIndex].toInt()].notes, data->chords[data->sequence[currentSequenceIndex].toInt()].numNotes);
lastDebounceTime = millis(); //set the current time
entered = false;
}
}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////