#include <LiquidCrystal_I2C.h>
#include "Button.h"
#include "Menu.h"
#define I2C_ADDR 0x27
#define LCD_COLUMNS 16
#define LCD_LINES 2
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
const String notes[] = {"C ","C#","D ","D#","E ","F ","F#","G ","G#","A ","Bb","B "};
byte pattern[64] = {0,2,4,5,7,9,11,12,128};
int currentStep = 0;
int tempo = 120;
bool editTempo = false;
bool editSync = false;
bool editPattern = false;
bool scroll = false;
byte currentMenu = 0;
Menu menus[4]({0,1,2,3});
int clockDelay = 60000/tempo;
bool extSync = false;
bool run = false;
void displayMenu();
void displayMenu(uint8_t note);
void initMenus();
String numToNote(byte n);
bool extClock();
bool intClock();
void playNextNote();
double stepToFreq(int);
// ======================================================================
// Setup
// ======================================================================
void setup() {
//Serial.begin(9600);
lcd.init();
lcd.backlight();
pinMode(12, INPUT); // Clk in
// Parallel + Gate output pins:
for(int i=6;i<12;i++) pinMode(i, OUTPUT);
initMenus();
displayMenu();
}
// ======================================================================
// Loop
// ======================================================================
void loop() {
static Button btnEnter(2);
static Button btnUp(3);
static Button btnDown(4);
static Button btnExit(5);
if(btnUp.repeated()) {
if(scroll) {
currentStep++;
currentStep = currentStep%64;
displayMenu();
}
else if(editPattern) {
pattern[currentStep] += 1;
pattern[currentStep] = pattern[currentStep]%32;
displayMenu();
}
else if(editTempo) {
tempo++;
if (tempo > 400) tempo = 10;
clockDelay = 60000/tempo;
displayMenu();
}
else if(editSync) {
extSync = !extSync;
displayMenu();
}
else {
menus[currentMenu].incPointer();
displayMenu();
}
}
if(btnDown.repeated()) {
if(scroll) {
currentStep += 63;
currentStep = currentStep%64;
displayMenu();
}
else if(editPattern) {
pattern[currentStep] += 31;
pattern[currentStep] = pattern[currentStep]%32;
displayMenu();
}
else if(editTempo) {
tempo--;
if (tempo < 10) tempo = 400;
clockDelay = 60000/tempo;
displayMenu();
}
else if(editSync) {
extSync = !extSync;
displayMenu();
}
else {
menus[currentMenu].decPointer();
displayMenu();
}
}
if(btnEnter.released()) {
if(!editPattern) {
switch(menus[currentMenu].getSelected() + menus[currentMenu].getId()*10) {
case 0: // EDIT
currentMenu = 1;
break;
case 1: // PLAY
currentMenu = 2;
break;
case 2: // SETUP
currentMenu = 3;
break;
case 10: // NOTE
editPattern = true;
break;
case 11: // HOLD
if(currentStep > 0) {
pattern[currentStep] = pattern[currentStep - 1] + 64;
currentStep++;
currentStep = currentStep%64;
}
break;
case 12: // PAUSE
pattern[currentStep] = 32;
currentStep++;
currentStep = currentStep%64;
break;
/* case 13: // NEXT
currentStep++;
currentStep = currentStep%64;
break;
case 14: // PREVIOUS
currentStep += 63;
currentStep = currentStep%64;
break;*/
case 13: // END
pattern[currentStep] = 128;
break;
case 14: // SCROLL
editPattern = true;
scroll = true;
break;
case 20: // PLAY
run = true;
break;
case 21: // PAUSE
run = 0;
tone(6,0);
break;
case 22: // RESTART
currentStep = 0;
break;
case 30: // TEMPO
editTempo = true;
break;
case 31: // SYNC
editSync = true;
break;
default:
break;
}
}
if(currentMenu < 3) menus[currentMenu].home();
//if(!editPattern || scroll) displayMenu();
//else displayMenu(pattern[currentStep]);
displayMenu();
}
if(btnExit.released()) {
if(editTempo || editSync) {
currentMenu = 3;
editTempo = false;
editSync = false;
menus[currentMenu].home();
displayMenu();
return;
}
if(!editPattern) {
switch(currentMenu) {
case 1: // EDIT
currentMenu = 0;
break;
case 2: // PLAY
currentMenu = 0;
run = false;
digitalWrite(11, LOW); // CLOSE GATE
break;
case 3: // SETUP
currentMenu = 0;
break;
default:
break;
}
}
else {
if(!scroll) {
currentStep++;
currentStep = currentStep%64;
}
editPattern = false;
scroll = false;
}
if(currentMenu <3) menus[currentMenu].home();
displayMenu();
}
if(extSync && extClock() && run) {
playNextNote();
}
if(!extSync && intClock() && run) {
playNextNote();
}
}
//===END LOOP======================================================
void displayMenu() {
lcd.setCursor(0,0);
if(currentMenu == 0) {
lcd.print("Select Mode! ");
}
else if(currentMenu == 1) {
lcd.print("Edit Step ");
if(scroll) lcd.print(">");
lcd.print(currentStep + 1);
lcd.print(" ");
}
else if(currentMenu == 2) {
lcd.print("Play Menu ");
}
else if(currentMenu == 3) {
lcd.print("Setup Menu ");
}
lcd.setCursor(0,1);
if(!(scroll || editPattern || editTempo || editSync)) lcd.print(">");
lcd.print(menus[currentMenu].getCurrentEntry());
lcd.print(" ");
lcd.setCursor(10,1);
if(currentMenu == 1) {
if(editPattern && !scroll) lcd.print(">");
lcd.print(numToNote(pattern[currentStep]));
lcd.print(" ");
}
else if(menus[currentMenu].getCurrentEntry() == "TEMPO") {
if(editTempo) lcd.print(">");
lcd.print(tempo);
lcd.print(" ");
}
else if(menus[currentMenu].getCurrentEntry() == "SYNC") {
if(editSync) lcd.print(">");
extSync ? lcd.print("EXT") : lcd.print("INT");
lcd.print(" ");
}
else if(editPattern) {
lcd.print(">");
lcd.print(numToNote(pattern[currentStep]));
lcd.print(" ");
}
else {
lcd.print(" ");
}
}
void initMenus() {
menus[0].push("EDIT");
menus[0].push("PLAY");
menus[0].push("SETUP");
menus[1].push("NOTE");
menus[1].push("HOLD");
menus[1].push("PAUSE");
//menus[1].push("NEXT");
//menus[1].push("PREVIOUS");
menus[1].push("LOOP END");
menus[1].push("SCROLL");
menus[2].push("START");
menus[2].push("PAUSE");
menus[2].push("RESTART");
menus[3].push("TEMPO");
menus[3].push("SYNC");
}
String numToNote(uint8_t num) {
String hld = "";
if(num & 128) return "END";
if(num & 32) return "PAU";
if(num & 64) {
num = num & 31;
hld = "(H)";
}
if(num < 32) {
num += 7;
uint8_t okt = num/12;
String note = notes[num%12];
note.concat(okt);
note.concat(hld);
return note;
}
return "";
}
bool intClock() {
static uint32_t timer = millis() + clockDelay;
if(timer > millis()) return false;
timer = millis() + clockDelay;
return true;
}
bool extClock() {
static bool oldstate = false;
bool state = digitalRead(12);
if(oldstate == state) return false;
oldstate = state;
if(state)return true; // pos edge
return false;
}
void playNextNote() {
byte n = pattern[currentStep];
if(currentStep > 63 || n > 127) { // LOOP END
currentStep = 0;
n = pattern[currentStep];
}
if(n & 32) { // PAUSE
digitalWrite(11, LOW); // GATE
}
else if(n & 64) { // HOLD
// do nothing
}
else {
digitalWrite(11, LOW); // GATE
delay(50);
digitalWrite(6,bitRead(n,0));
digitalWrite(7,bitRead(n,1));
digitalWrite(8,bitRead(n,2));
digitalWrite(9,bitRead(n,3));
digitalWrite(10,bitRead(n,4));
digitalWrite(11, HIGH);
}
currentStep++;
}
double stepToFreq(int step) {
return 199 * pow(1.059463,step);
}