/*!
* @file Arpmini_plus.ino
* Project Estorm - Arpmini+
* @brief MIDI Sequencer & Arpeggiator
* @version 2.06
* @author Paolo Estorm
* @date 09/12/24
* @license GPL v3.0
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Inspired by the "arduino based midi sequencer" project by Brendan Clarke
// https://brendanclarke.com/wp/2014/04/23/arduino-based-midi-sequencer/
//==============================================================//
// //
// The provided code and circuit diagram are intended solely //
// as a demonstration of the "Arpmini+" project, //
// designed to run on Arduino Pro Micro. //
// For official code and schematics, //
// please refer to the repository: //
// https://github.com/PaoloEstorm/Arpmini_plus //
// //
//==============================================================//
// KEEP PRESSED THE GREEN BUTTON TO ENTER THE MENU //
// buttons are mapped to numberpad (1, 3, 4, 6)
#include "Vocabulary.h"
// system
char version[] = "2.08";
// leds
#define redled 13 // red led pin
#define yellowled 12 // yellow led pin
#define greenled 11 // green led pin
#define blueled 10 // blue led pin
// sound
#define speaker 5 // buzzer pin
bool uisound = true; // is ui sound on?
uint8_t soundmode = 1; // 1 audio on, 2 ui sounds off, 3 off
bool sound = true; // speaker sound toggle
bool metro = false; // metronome toggle
bool confirmsound = false; // at button press, play the confirmation sound instead of the click
// screen
#include "GyverOLED.h"
GyverOLED<SSD1306_128x64, OLED_NO_BUFFER> oled; // screen initialization
bool StartScreenTimer = true; // activate the timer
// memory
#include <EEPROM.h>
// midi
#include <MIDI.h>
#include <SoftwareSerial.h>
SoftwareSerial Serial2(2, -1); // midi2 pins (input, output)
MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); // initialize midi1
MIDI_CREATE_INSTANCE(SoftwareSerial, Serial2, MIDI2); // initialize midi2
uint8_t midiChannel = 1; // MIDI channel to use for sequencer 1 to 16
int8_t syncport = 1; // enable midi in sync 0=none, 1=port1, 2=port2
// buttons
#define redbutton 9 // red button pin
#define yellowbutton 8 // yellow button pin
#define greenbutton 7 // green button pin
#define bluebutton 6 // blue button pin
bool EnableButtons = true; // if controlled externally (by CC) disable all buttons
uint8_t greentristate = 1; // 0=off, 1=null, 2=on
bool redstate = false; // is redbutton pressed?
bool yellowstate = false; // is yellowbutton pressed?
bool greenstate = false; // is greenbutton pressed?
bool bluestate = false; // is bluebutton pressed?
uint8_t redbuttonCC; // cc for external control
uint8_t yellowbuttonCC; // cc for external control
uint8_t bluebuttonCC; // cc for external control
uint8_t greenbuttonCC; // cc for external control
int8_t numbuttonspressedCC = 0; // number of externally pressed buttons
uint8_t mapButtonSelect = 1; // 1=red, 2=yellow, 3=blue, 4=green
// menu
uint8_t menuitem = 2; // is the number assiciated with every voice in the main menu. 0 to 16
uint8_t menunumber = 0; // 0=mainscreen, 1=menu, 2=submenu, 3=load\save menu
uint8_t savemode = 0; // 0=bake, 1=clone, 2=new, 3=save, 4=load, 5=delete
uint8_t savecurX = 0; // cursor position in load/save menu 0-5
bool confirmation = false; // confirmation popup in load/save menu
bool full = false; // is the selected save slot full?
int8_t curpos = 0; // cursor position in songmode, 0-7
bool PrintSongLiveCursor = false; // send the cursor to screen, song & live mode
bool PrintTimeBar = false; // send the BPM bar to screen
bool StartMenuTimer = true; // activate the go-to-menu timer
uint8_t Jittercur = 1; // cursor position in the jitter submenu
// time keeping
volatile uint8_t clockTimeout; // variable for keeping track of external/internal clock
bool internalClock = true; // is the sequencer using the internal clock?
uint8_t StepSpeed = 2; // 1=2x, 2=1x, 3=3/4, 4=1/2
bool FixSync = false; // tries to re-allign the sequence when changing step-speeds
bool flipflopEnable; // part of the frameperstep flipflop
uint8_t sendrealtime = 1; // send midi realtime messages. 0=off, 1=on (@internalclock, only if playing), 2=on (@internalclock, always)
int8_t GlobalStep; // keep track of note steps for metronome and tempo indicator
int8_t tSignature = 4; // time signature for led indicator/metronome and beats, 1 - 8 (1*/4, 2*/4..to 8/4)
int8_t GlobalDivison = 4; // how many steps per beat
int8_t countBeat; // keep track of the time beats
int8_t countStep; // keep track of note steps in seq/song mode
int8_t IntercountStep; // keep track of note steps while inter-recording
int8_t countTicks; // the frame number (frame=external clock or 24 frames per quarter note)
uint8_t ticksPerStep = 6; // how many clock ticks to count before the sequencer moves to another step.
uint8_t flip = 6; // part of the frameperstep's flipflop
uint8_t flop = 6; // part of the frameperstep's flipflop
bool swing = false; // is swing enabled?
uint8_t BPM = 120; // beats per minute for internalclock - min 20, max 250 bpm
bool playing = false; // is the sequencer playing?
uint8_t snapmode = 1; // when play/stop the next sequence in live mode. 0=pattern, 1=up-beat, 2=beat
bool start = false; // dirty fix for a ableton live 10 bug. becomes true once at sequence start and send a sync command
const uint8_t iterations = 8; // how many BPM "samples" to averege out for the tap tempo. more = more accurate
uint8_t BPMbuffer[iterations]; // BPM "samples" buffer for tap tempo
// arpeggiator
const uint8_t holdNotes = 8; // maximum number of notes that can be arpeggiated
int8_t activeNotes[holdNotes]; // contains the MIDI notes the arpeggiator plays
bool ArpDirection; // alternate up-down for arpstyle 3 & 4
uint8_t arpstyle = 1; // 1=up, 2=down, 3=up-down, 4=down-up, 5=up+down, 6=down+up, 7=random
uint8_t arpcount; // number of times the arpeggio repeats
uint8_t arprepeat = 1; // number of times the arpeggios gets trasposed by "stepdistance"
int8_t stepdistance = 12; // distance in semitones between each arp repeat, -12 to +12
int8_t numNotesHeld = 0; // how many notes are currently pressed on the keyboard
int8_t numActiveNotes = 0; // number of notes currently stored in the holdNotes array
uint8_t trigMode = 0; // 0=hold, 1=trigger, 2=retrigged
// sequencer
bool recording = false; // is the sequencer in the recording state?
uint8_t rolandLowNote; // for 'roland' mode, the sequence is transposed based on the lowest note recorded
uint8_t rolandTransposeNote; // the last note number received for transposing the sequence
uint8_t seqLength = 16; // the number of steps the sequence is looped for - can be less than maxSeqLength
uint8_t NewSeqLength = 16; // 1-32 seq.length of the newly created song
uint8_t currentSeq = 0; // the currently selected sequence row from the big matrix below. 0=seq1, 1=seq2, 2=seq3, 3=seq4
uint8_t newcurrentSeq = 0; // the next sequence is going to play in live mode. 0-3
uint8_t currentSeqCopy = 0;
const uint8_t maxSeqLength = 32; // the total possible number of steps (columns in the big matrix below)
const uint8_t numberSequences = 4; // the number of total sequences
int8_t noteSeq[numberSequences][maxSeqLength]; // sequences arrays
const uint8_t patternLength = 8; // number of patterns
uint8_t songPattern[patternLength]; // pattern array
uint8_t pattern = 0; // currently playing pattern
bool chainrec = false; // sequential recording in song mode?
bool lockpattern = false; // if true block the current pattern from sequencing
// notes
int8_t pitch = 0; // pitch transposition: -12 to +12
bool pitchmode = false; // 0=before scale, 1=scale root
uint8_t scale = 0; // 0=linear, 1=penta. major, 2=penta. minor, 3=major, 4=minor, 5=arabic, 6=locrian, 7=lydian, 8=dorian, 9=inverted, 10=hexa.
int8_t posttranspose = 0; // post-scale pitch transposition: -12 to +12
uint8_t noteLengthSelect = 3; // set the notelength, 0=random, 1=10%, 2=30%, 3=50%, 4=80%, 5=100%, 6=120%
bool sortnotes = true; // sort the ActiveNotes array?
bool muted = false; // temporarily suspend playing any notes from the sequencer
bool TrigMute = false; // toggle mute in sync with snapmode, in live mode
bool sustain = false; // hold notes also if trigmode > 0
const uint8_t queueLength = 2; // the number of notes that can overlap if notelenght is 120%
int8_t cronNote[queueLength]; // stores the notes playing
int8_t cronLength[queueLength]; // stores the amount of time a note has left to play
uint8_t jitrange = 0; // jitter range 0-24
uint8_t jitprob = 0; // jitter probability 0-10 (0-100%)
uint8_t jitmiss = 0; // probability of a note to be not played (0-90%)
// general
uint8_t modeselect = 1; // 1=arp.mode, 2=rec.mode, 3=song mode, 4=live mode
uint8_t premodeselect = 1; // pre-selection of the mode in submenu
void setup() {
// Serial.begin(9600);
// oled screen initialization
oled.init();
Wire.setClock(1000000L);
oled.clear();
oled.setScale(3);
oled.setContrast(255);
// initialize pins
pinMode(redled, OUTPUT);
pinMode(yellowled, OUTPUT);
pinMode(greenled, OUTPUT);
pinMode(blueled, OUTPUT);
pinMode(speaker, OUTPUT);
AllLedsOff();
pinMode(redbutton, INPUT_PULLUP);
pinMode(yellowbutton, INPUT_PULLUP);
pinMode(bluebutton, INPUT_PULLUP);
pinMode(greenbutton, INPUT_PULLUP);
// initialize timer1 used for internal clock
noInterrupts();
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCNT1 = 0; // initialize counter value to 0
OCR1A = 5208; // initial tempo 120bpm
TCCR1B |= (1 << WGM12); // turn on CTC mode
TCCR1B |= (1 << CS11); // 64 prescaler
TCCR1B |= (1 << CS10); // 64 prescaler
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
interrupts();
// midi initialization settings
MIDI.begin(MIDI_CHANNEL_OMNI);
MIDI2.begin(MIDI_CHANNEL_OMNI);
MIDI.turnThruOff();
MIDI2.turnThruOff();
MIDI.setHandleControlChange(HandleCC);
MIDI2.setHandleControlChange(HandleCC);
MIDI.setHandleNoteOn(HandleNoteOn);
MIDI2.setHandleNoteOn(HandleNoteOn);
MIDI.setHandleNoteOff(HandleNoteOff);
MIDI2.setHandleNoteOff(HandleNoteOff);
MIDI.setHandleSongPosition(HandleSongPosition);
MIDI2.setHandleSongPosition(HandleSongPosition);
if (EEPROM.read(15) != 64) ResetEEPROM(); // if check is not valid, initialize the EEPROM with default values
midiChannel = EEPROM.read(0);
sendrealtime = EEPROM.read(1);
soundmode = EEPROM.read(2);
syncport = EEPROM.read(3);
redbuttonCC = EEPROM.read(4);
yellowbuttonCC = EEPROM.read(5);
bluebuttonCC = EEPROM.read(6);
greenbuttonCC = EEPROM.read(7);
SetBPM(BPM);
SetSound(soundmode);
SetSyncPort(syncport);
ClearSeqPatternArray();
Startposition();
SynthReset();
for (uint8_t i = 0; i < iterations; i++) { // initialize the tap tempo array
BPMbuffer[i] = BPM;
}
Bip(2); // Startup Sound
oled.setCursorXY(2, 0);
oled.println(F("ARPMINI"));
oled.print(F(" +"));
oled.setScale(1);
oled.setCursorXY(25, 56);
oled.print(F("FIRMWARE "));
oled.print(version);
delay(2000);
PrintMainScreen();
}
void loop() { // run continuously
MIDI.read();
MIDI2.read();
DebounceButtons();
ScreenOnTimer();
GoToMenuTimer();
if (PrintSongLiveCursor) {
PrintSongLiveCursor = false;
PrintPatternSequenceCursor();
}
if (PrintTimeBar) {
PrintTimeBar = false;
PrintBPMBar();
}
}
void ResetEEPROM() { // initialize the EEPROM with default values
for (uint8_t i = 0; i < 6; i++) {
if (i < 4) {
EEPROM.write(i, 1); // midiChannel, sendrealtime, soundmode, syncport
EEPROM.write(4 + i, i + 21); // buttons cc
}
EEPROM.write(16 + (144 * i), 0); // songs addresses
}
EEPROM.write(15, 64); // write check
}
void AllLedsOn() { // turn on all leds
for (uint8_t i = 2; i <= 5; i++) {
digitalWrite(i, HIGH);
}
}
void AllLedsOff() { // turn of all leds
for (uint8_t i = 2; i <= 5; i++) {
digitalWrite(i, LOW);
}
}
void GoToMenuTimer() { // greenbutton longpress enter the menu
static unsigned long SampleMenuTime; // time in ms at which the timer was set
static bool MenuTimerState; // is the timer still running?
if (StartMenuTimer) {
StartMenuTimer = false;
MenuTimerState = true;
SampleMenuTime = millis();
}
if (MenuTimerState) {
if (greentristate == 2) {
if (millis() - SampleMenuTime > 1000) {
MenuTimerState = false;
greentristate = 1;
Bip(2);
if (modeselect == 4) menuitem = 2;
PrintMenu(menuitem);
if (recording) {
recording = false;
ManageRecording();
}
}
}
}
}
void ScreenOnTimer() { // in the main screen if greenbutton is held for 4 seconds, go to menu
static unsigned long SampleTime = 0; // Time at which the timer was set
static bool TimerState = false; // Is the timer still running?
// Start the timer
if (StartScreenTimer) {
StartScreenTimer = false;
TimerState = true;
SampleTime = millis();
oled.sendCommand(OLED_DISPLAY_ON);
oled.setContrast(255);
}
// If the timer is running, check if it has expired
if (TimerState && millis() - SampleTime > 4000) {
TimerState = false;
// Check if the screen should be turned off or dimmed
if (menunumber == 0 && ((modeselect != 3) || (modeselect == 3 && !playing && internalClock))) {
oled.sendCommand(OLED_DISPLAY_OFF);
} else {
oled.setContrast(1);
}
}
}
void SynthReset() { // clear cache
for (uint8_t i = 0; i < holdNotes; i++) {
activeNotes[i] = -1;
if (i < queueLength) {
cronNote[i] = -1;
cronLength[i] = -1;
}
}
numNotesHeld = 0;
AllNotesOff();
}
void AllNotesOff() { // send allnoteoff control change
MIDI.sendControlChange(123, 0, midiChannel);
}
void ConnectPort(uint8_t portselect) { // connects sync ports
if (portselect == 1) {
MIDI.setHandleClock(HandleClock);
MIDI.setHandleStart(HandleStart);
MIDI.setHandleStop(HandleStop);
MIDI.setHandleContinue(HandleContinue);
} else if (portselect == 2) {
MIDI2.setHandleClock(HandleClock);
MIDI2.setHandleStart(HandleStart);
MIDI2.setHandleStop(HandleStop);
MIDI2.setHandleContinue(HandleContinue);
}
}
void DisconnectPort(uint8_t portselect) { // disconnects sync ports
if (portselect == 1) {
MIDI.disconnectCallbackFromType(midi::Clock);
MIDI.disconnectCallbackFromType(midi::Start);
MIDI.disconnectCallbackFromType(midi::Stop);
MIDI.disconnectCallbackFromType(midi::Continue);
} else if (portselect == 2) {
MIDI2.disconnectCallbackFromType(midi::Clock);
MIDI2.disconnectCallbackFromType(midi::Start);
MIDI2.disconnectCallbackFromType(midi::Stop);
MIDI2.disconnectCallbackFromType(midi::Continue);
}
}
void SetSyncPort(uint8_t port) { // 0-none, 1-port1, 2-port2 define port for external sync
DisconnectPort(1);
DisconnectPort(2);
if (port == 1) {
ConnectPort(1);
} else if (port == 2) {
ConnectPort(2);
}
}
unsigned int eepromaddress(unsigned int address, uint8_t slot) { // calculate eeprom addresses (address, slot number 0-5)
return (144 * slot) + address;
}
void ScreenBlink(bool mode = false) { // blinks screen - 0 bip on button-press, 1 bip regardless
oled.sendCommand(OLED_DISPLAY_OFF);
oled.sendCommand(OLED_DISPLAY_ON);
if (!mode) confirmsound = true;
else Bip(2);
}
void SetSound(uint8_t mode) { // sound settings
if (mode == 1) {
sound = true;
uisound = true;
} else if (mode == 2) {
uisound = false;
sound = true;
} else if (mode == 3) sound = false;
}
void Bip(uint8_t type) { // sounds
if (sound) {
if (uisound) {
if (type == 1) tone(speaker, 3136, 1); // click
else if (type == 2) tone(speaker, 2637, 10); // startup/confirmation
}
if (type == 3) tone(speaker, 3136, 5); // metronome
else if (type == 4) tone(speaker, 2349, 5); // metronome
}
}
void SetBPM(uint8_t tempo) { // change Timer1 speed to match BPM (20-250)
OCR1A = ((250000UL * 5) - tempo) / (2 * tempo);
}
void TapTempo() { // calculate tempo based on the tapping frequency
static unsigned long lastTapTime = 0;
static uint8_t bufferIndex = 0;
unsigned long currentTapTime = millis();
unsigned long tapInterval = currentTapTime - lastTapTime;
if (tapInterval < 1500 && tapInterval > 240) { // if the time between taps is not too long or too short
BPMbuffer[bufferIndex] = 60250 / tapInterval; // store iteration
bufferIndex = (bufferIndex + 1) % iterations; // go to the next slot
unsigned int BPMsum = 0; // sum all iterations
for (uint8_t i = 0; i < iterations; i++) {
BPMsum += BPMbuffer[i];
}
BPM = BPMsum / iterations; // calculate averege
SetBPM(BPM);
PrintSubmenu(menuitem);
}
lastTapTime = currentTapTime; // store last interval
}
ISR(TIMER1_COMPA_vect) { // internal clock
if (internalClock) {
if ((sendrealtime == 1 && playing) || (sendrealtime == 2)) MIDI.sendRealTime(midi::Clock);
RunClock();
} else {
clockTimeout++;
if (clockTimeout > 100) {
HandleStop();
}
}
}
void HandleClock() { // external clock
if (!internalClock) {
clockTimeout = 0;
RunClock();
if (sendrealtime) MIDI.sendRealTime(midi::Clock);
}
}
void RunClock() { // main clock
countTicks = (countTicks + 1) % ticksPerStep;
if (countTicks == 0) {
if (start) {
start = false;
if (sendrealtime) MIDI.sendSongPosition(0); // make ableton live happy, without would be slightly out of sync
}
if ((modeselect != 4 && (!internalClock || (internalClock && playing))) || modeselect == 4) HandleStep();
}
if (countTicks == 2) {
if (menunumber == 0) {
if (modeselect != 4) {
if (!recording || (recording && playing)) digitalWrite(yellowled, LOW); // turn yellowled off before 2 ticks - Tempo indicator
} else {
if (playing) AllLedsOff();
}
}
}
if (modeselect < 3) { // not song & live mode
if (trigMode > 0 && numNotesHeld == 0 && !sustain) muted = true;
}
for (uint8_t i = 0; i < queueLength; i++) { // decrement crons
if (cronLength[i] > -1) {
cronLength[i] = cronLength[i] - SetNoteLength(); // this number is subtracted from the cronLength buffer for each frame a note is active
}
if (cronLength[i] < -1) {
MIDI.sendNoteOff(cronNote[i], 0, midiChannel);
cronLength[i] = -1;
cronNote[i] = -1;
}
}
}
void SetTicksPerStep() { // set ticksPerStep values
switch (StepSpeed) {
case 1: // 2x
flip = swing ? 4 : 3;
flop = swing ? 2 : 3;
GlobalDivison = 8;
break;
case 2: // 1x
flip = swing ? 8 : 6;
flop = swing ? 4 : 6;
GlobalDivison = 4;
break;
case 3: // 3/4
flip = 8;
flop = 8;
GlobalDivison = 3;
break;
case 4: // 1/2
flip = swing ? 16 : 12;
flop = swing ? 8 : 12;
GlobalDivison = 2;
break;
}
}
void FlipFlopFPS() { // alternates ticksPerStep values (for swing)
flipflopEnable = !flipflopEnable;
if (flipflopEnable) ticksPerStep = flip;
else ticksPerStep = flop;
}
uint8_t SetNoteLength() { // set the note duration
uint8_t noteLength;
static const uint8_t noteLengths[] = { 55, 35, 30, 25, 20, 15 }; // noteLengths
if (noteLengthSelect == 0) { // random
noteLength = random(15, 55);
} else noteLength = noteLengths[noteLengthSelect - 1];
if (swing && StepSpeed != 3) {
if (flipflopEnable) noteLength = noteLength - 5;
else noteLength = noteLength + 10;
}
if (StepSpeed == 1) noteLength = noteLength * 2;
else if (StepSpeed == 3) noteLength = noteLength - 5;
else if (StepSpeed == 4) noteLength = noteLength / 2;
return (noteLength);
}
void HandleStep() { // step sequencer
GlobalStep++;
if (GlobalStep > (GlobalDivison - 1)) {
GlobalStep = 0;
}
if (FixSync) {
if (GlobalStep == 0) {
Startposition();
FixSync = false;
}
} else SetTicksPerStep();
FlipFlopFPS();
if (modeselect == 1) { // arp.mode
if (numActiveNotes > 0) {
SetArpStyle(arpstyle); // arp sequencer
if (!muted && playing) {
QueueNote(activeNotes[countStep % numActiveNotes]); // enqueue and play
}
} else countStep = -1;
}
else { // rec. & song mode
countStep = (countStep + 1) % seqLength; // seq. sequencer
if (modeselect == 2 || modeselect == 3) {
if (playing && recording && bluestate && menunumber == 0) { // in rec.mode bluebutton delete note
noteSeq[currentSeq][countStep] = -1;
}
if (!playing && recording && internalClock) { // in recording and not playing yellowled indicate steps
if (countStep % 4 == 0) {
digitalWrite(yellowled, HIGH);
} else digitalWrite(yellowled, LOW);
}
if (modeselect == 3) { // call pattern sequencer
if (countStep == 0) {
if ((!recording) || (recording && chainrec)) {
HandlePattern();
}
}
}
}
else if (modeselect == 4) {
if ((snapmode == 0 && countStep == 0) || (snapmode == 1 && GlobalStep == 0 && countBeat == (tSignature - 1)) || (snapmode == 2 && GlobalStep == 0)) {
if (TrigMute) {
TrigMute = false;
muted = !muted;
}
if (currentSeq != newcurrentSeq) {
currentSeq = newcurrentSeq;
muted = false;
if (menunumber == 0) PrintSongLiveCursor = true;
}
}
}
if (playing && (noteSeq[currentSeq][countStep] >= 0) && !muted) { // step has a note - enqueue and play
int8_t note;
if (recording) {
note = noteSeq[currentSeq][countStep];
} else note = (noteSeq[currentSeq][countStep] + rolandTransposeNote - rolandLowNote);
QueueNote(note);
}
}
if (GlobalStep == 0) {
HandleBeat();
}
if (menunumber == 0 && modeselect == 4) { // live mode blinking
if (GlobalStep == 0 || (TrigMute && !(GlobalStep % 2))) {
if (playing) {
if (!muted || TrigMute) {
if (currentSeq == 0) digitalWrite(redled, HIGH);
else if (currentSeq == 1) digitalWrite(yellowled, HIGH);
else if (currentSeq == 2) digitalWrite(blueled, HIGH);
else if (currentSeq == 3) digitalWrite(greenled, HIGH);
} else AllLedsOn();
}
}
}
}
void HandleBeat() { // tempo indicator and metronome
countBeat = (countBeat + 1) % tSignature;
if (menunumber == 0) {
if (modeselect != 4) { // turn yellowled on every beat
if (playing) digitalWrite(yellowled, HIGH);
}
}
if (playing && (recording || metro)) Metronome();
}
void HandlePattern() { // pattern sequencer in songmode
if (!lockpattern) {
pattern = (pattern + 1) % patternLength;
SkipPattern();
}
currentSeq = songPattern[pattern] - 1;
if (menunumber == 0) PrintSongLiveCursor = true;
}
void SkipPattern() { // skip pattern if empty
if ((songPattern[pattern]) == 0) pattern = (pattern + 1) % patternLength;
if ((songPattern[pattern]) == 0) SkipPattern();
}
void Metronome() { // manage the metronome
if (countBeat == 0) Bip(3);
else Bip(4);
}
void HandleStart() { // start message - re-start the sequence
if (sendrealtime) {
MIDI.sendRealTime(midi::Start);
}
internalClock = false;
playing = true;
Startposition();
if (menunumber == 0) { // switch on display and restart screen-on timer
StartScreenTimer = true;
PrintTimeBar = true;
}
}
void HandleContinue() { // continue message - re-start the sequence
HandleStop();
HandleStart();
}
void HandleStop() { // stop the sequence and switch over to internal clock
if (sendrealtime) MIDI.sendRealTime(midi::Stop);
if (menunumber == 0) {
if (modeselect != 4) digitalWrite(yellowled, LOW); // turn yellowled off before 2 ticks - Tempo indicator
else AllLedsOff();
}
playing = false;
internalClock = true;
Startposition();
numNotesHeld = 0;
if (menunumber == 0) { // switch on display and restart screen-on timer
StartScreenTimer = true;
PrintTimeBar = true;
}
}
void StartAndStop() { // manage starts and stops
playing = !playing;
if (internalClock) {
Startposition();
if (sendrealtime) {
if (playing) {
MIDI.sendRealTime(midi::Start);
start = true; // to make ableton live 10 happy
} else MIDI.sendRealTime(midi::Stop);
}
}
}
void HandleSongPosition(unsigned int position) { // send song position midi messages
if (sendrealtime) MIDI.sendSongPosition(position);
}
void Startposition() { // called every time the sequencing starts or stops
SetTicksPerStep();
if (!FixSync) {
GlobalStep = -1;
countBeat = -1;
countTicks = -1;
}
if (numNotesHeld > 0) AllNotesOff();
arpcount = 0;
countStep = -1;
flipflopEnable = false;
ArpDirection = true;
if (playing && modeselect == 3) {
if ((!recording) || (recording && chainrec)) {
if (!lockpattern) {
pattern = -1;
}
}
}
if (menunumber == 2 && menuitem == 3) { // refresh BPM page
PrintSubmenu(3);
}
}
void HandleCC(uint8_t channel, uint8_t cc, uint8_t value) { // handle midi CC messages
if (channel == midiChannel) {
if ((cc != 64) && (cc != 123)) { // not sustain pedal or all notes off cc
if (menunumber == 2 && menuitem == 16) { // if cc mapping mode
EnableButtons = true;
if (value > 0) {
if (mapButtonSelect == 1) redbuttonCC = cc;
else if (mapButtonSelect == 2) yellowbuttonCC = cc;
else if (mapButtonSelect == 3) bluebuttonCC = cc;
else if (mapButtonSelect == 4) greenbuttonCC = cc;
PrintSubmenu(menuitem);
StartScreenTimer = true;
Bip(2);
} else {
mapButtonSelect++;
PrintSubmenu(menuitem);
if (mapButtonSelect > 4) {
mapButtonSelect = 1;
ScreenBlink(1);
EEPROM.update(4, redbuttonCC);
EEPROM.update(5, yellowbuttonCC);
EEPROM.update(6, bluebuttonCC);
EEPROM.update(7, greenbuttonCC);
PrintMenu(menuitem);
}
}
}
else { // external control
bool* state = nullptr;
if (cc == redbuttonCC) state = &redstate;
else if (cc == yellowbuttonCC) state = &yellowstate;
else if (cc == bluebuttonCC) state = &bluestate;
else if (cc == greenbuttonCC) state = &greenstate;
if (state) {
*state = value;
numbuttonspressedCC += (*state ? 1 : -1);
ButtonsCommands(*state);
}
EnableButtons = (numbuttonspressedCC == 0);
if (numbuttonspressedCC < 0) numbuttonspressedCC = 0;
}
}
else if (cc == 64) { // sustain pedal cc
sustain = value;
if (!playing || (playing && trigMode == 0)) MIDI.sendControlChange(cc, value, channel); // pass through sustain pedal cc
}
} else MIDI.sendControlChange(cc, value, channel);
}
void HandleNoteOn(uint8_t channel, uint8_t note, uint8_t velocity) { // handle a noteOn event
if (channel == midiChannel) {
if (!playing /*&& !recording*/) { // bypass if not playing
MIDI.sendNoteOn(TransposeAndScale(note), velocity, channel);
}
if (modeselect == 1) { // arp mode
if (playing) {
if (numNotesHeld <= 0) { // clear the activeNotes array
numActiveNotes = 0;
for (uint8_t i = 0; i < holdNotes; i++) {
activeNotes[i] = -1;
}
}
if (numActiveNotes < holdNotes) { // store the note only if the activeNotes array is not full
activeNotes[numActiveNotes] = note;
numActiveNotes++;
SortArray(); // sort the activeNotes array
}
}
}
// else if (modeselect != 1) { // not arp mode
// if (recording) {
// if (playing) {
// if (countTicks + 2 > (ticksPerStep / 2)) {
// noteSeq[currentSeq][(countStep + 1) % seqLength] = note; // recording & playing
// } else {
// noteSeq[currentSeq][countStep] = note;
// QueueNote(note);
// }
// } else { // recording & !playing
// HandleStep();
// noteSeq[currentSeq][countStep] = note;
// QueueNote(note);
// }
// } else if (playing) rolandTransposeNote = note; // in recmode transpose to note only if playing
// }
else if (modeselect != 1) { // not arp mode
if (recording) {
if (!playing) HandleStep();
noteSeq[currentSeq][countStep] = note;
QueueNote(note);
} else if (playing) rolandTransposeNote = note; // in recmode transpose to note only if playing
}
if (modeselect < 3) { // not song & live mode
if (trigMode > 0) {
muted = false;
if (trigMode == 2 && numNotesHeld == 1) { // trigmode 2 retrig start sequence
Startposition();
if (internalClock && sendrealtime) {
MIDI.sendSongPosition(0);
}
}
}
}
numNotesHeld++;
} else MIDI.sendNoteOn(note, velocity, channel);
}
void HandleNoteOff(uint8_t channel, uint8_t note, uint8_t velocity) { // handle a noteOff event
if (channel == midiChannel) {
numNotesHeld--;
if (numNotesHeld < 0) numNotesHeld = 0;
if (!playing /* && !recording*/) { // pass trough note off messages
MIDI.sendNoteOff(TransposeAndScale(note), velocity, channel);
}
else if (modeselect == 1 && trigMode > 0 && !sustain) { // arp mode
if (numNotesHeld == 0) muted = true;
for (uint8_t i = 0; i < holdNotes; i++) {
if (activeNotes[i] == note) {
activeNotes[i] = -1;
numActiveNotes--;
SortArray();
}
}
}
} else MIDI.sendNoteOff(note, velocity, channel);
}
void SortArray() { // sort activeNotes array
if (sortnotes) { // sort from small to large
for (uint8_t i = 0; i < holdNotes - 1; i++) {
for (uint8_t j = 0; j < holdNotes - 1; j++) {
if (activeNotes[j + 1] < activeNotes[j] && activeNotes[j + 1] >= 0) {
int8_t temp = activeNotes[j + 1];
activeNotes[j + 1] = activeNotes[j];
activeNotes[j] = temp;
}
}
}
}
if (trigMode == 0) { // remove duplicates and replace them with -1
for (uint8_t i = 0; i < holdNotes - 1; i++) {
if ((activeNotes[i] > 0) && (activeNotes[i] == activeNotes[i + 1])) {
activeNotes[i + 1] = -1;
numActiveNotes--;
}
}
}
for (uint8_t i = 0; i < holdNotes - 1; i++) { // shift all the -1s to the right
if (activeNotes[i] == -1) {
int8_t temp = activeNotes[i + 1];
activeNotes[i + 1] = activeNotes[i];
activeNotes[i] = temp;
}
}
}
int SetScale(int note, uint8_t scale) { // filter incoming note to fit in to a music scale
static const PROGMEM int8_t scaleOffsets[][12] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Scale 0: Linear
{ 0, -1, -2, -1, -2, -1, -2, -3, -1, -2, -1, -2 }, // Scale 1: Pentatonic Major
{ 0, -1, -2, 0, -1, 0, -1, 0, -1, -2, 0, -1 }, // Scale 2: Pentatonic Minor
{ 0, -1, 0, -1, 0, 0, -1, 0, -1, 0, -1, 0 }, // Scale 3: Major
{ 0, -1, 0, 0, -1, 0, -1, 0, 0, -1, 0, -1 }, // Scale 4: Minor
{ 0, -1, 0, 0, -1, 0, -1, 0, 0, -1, +1, 0 }, // Scale 5: Arabic
{ 0, -1, -2, 0, -1, 0, 0, 0, -1, -2, 0, -1 }, // Scale 6: Blues
{ 0, 0, -1, 0, -1, 0, 0, -1, 0, -1, 0, -1 }, // Scale 7: Locrian
{ 0, -1, 0, -1, 0, -1, 0, 0, -1, 0, -1, 0 }, // Scale 8: Lydian
{ 0, -1, 0, 0, -1, 0, -1, 0, -1, 0, 0, -1 }, // Scale 9: Dorian
{ 11, 9, 7, 5, 3, 1, -1, -3, -5, -7, -9, -11 }, // Scale 10: Inverted
{ 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1 } // Scale 11: Hexatonal
};
uint8_t reminder = note % 12;
int8_t offset = pgm_read_byte(&scaleOffsets[scale][reminder]); // read from PROGMEM
return note + offset;
}
int8_t Jitter(uint8_t range, uint8_t probability) { // add random variations
int8_t jitter = 0;
uint8_t chance = random(1, 10);
if (chance <= probability) jitter = random(-range, range);
return jitter;
}
bool ProbabilityMiss(uint8_t missProb) { // return true or false randomly
uint8_t chance = random(1, 10);
if (missProb <= chance) return true;
else return false;
}
uint8_t TransposeAndScale(int note) { // apply transposition and scale
if ((!pitchmode) || (pitchmode && scale == 0)) note = SetScale(note + pitch, scale);
else note = SetScale(note - pitch, scale) + pitch;
note = note + posttranspose; // apply post-transposition
if (note > 127) { // if note gets transposed out of range, raise or lower an octave
note = note - 12;
} else if (note < 0) {
note = note + 12;
}
return note;
}
void QueueNote(int8_t note) { // send notes to the midi port
static const uint8_t noteMaxLength = 100; // this number gets put in the cronLength buffer when the sequencer generates a note
bool queued = 0; // has current note been queued?
int8_t shortest = noteMaxLength;
int8_t shortIdx = 0;
static const uint8_t defaultVelocity = 64;
if (modeselect == 1) { // only in arp mode
if (arpcount > 0) note = note + ((arpcount - 1) * stepdistance);
}
note = note + Jitter(jitrange, jitprob); // apply jitter
note = TransposeAndScale(note); // apply scale & transpositions
if (modeselect == 1 && recording) { // inter-recording
IntercountStep++;
noteSeq[currentSeq][IntercountStep] = note;
if (IntercountStep == seqLength) {
IntercountStep = 0;
recording = false;
ManageRecording();
}
}
if (ProbabilityMiss(jitmiss)) { // pass note or not?
for (uint8_t i = 0; (i < queueLength) && (!queued); i++) { // check avail queue
if ((cronLength[i] < 0) || (cronNote[i] == note)) { // free queue slot
MIDI.sendNoteOn(note, defaultVelocity, midiChannel);
if (cronNote[i] >= 0) { // contains a note that hasn't been collected yet
MIDI.sendNoteOff(cronNote[i], 0, midiChannel);
}
cronNote[i] = note;
cronLength[i] = noteMaxLength;
queued = true;
} else {
if (cronLength[i] < shortest) {
shortest = cronLength[i];
shortIdx = i;
}
}
}
if (!queued) { // no free queue slots, steal the next expiry
MIDI.sendNoteOff(cronNote[shortIdx], 0, midiChannel);
MIDI.sendNoteOn(note, defaultVelocity, midiChannel);
cronNote[shortIdx] = note;
cronLength[shortIdx] = noteMaxLength;
queued = true;
}
}
}
void ManageRecording() { // recording ending setup
if (!recording) {
rolandLowNote = 128; // get the low note for transpose
for (uint8_t i = 0; i < maxSeqLength; i++) {
if ((noteSeq[currentSeq][i] < rolandLowNote) && (noteSeq[currentSeq][i] >= 0)) {
rolandLowNote = noteSeq[currentSeq][i];
}
}
}
digitalWrite(redled, recording);
rolandTransposeNote = rolandLowNote;
if (internalClock && !playing) {
Startposition();
}
}
void SetArpStyle(uint8_t style) { // arpeggiator algorithms
switch (style) {
case 2:
case 4:
case 6:
if (countStep == -1) countStep = numActiveNotes;
break;
default:
break;
}
uint8_t arpup = (countStep + 1) % numActiveNotes;
uint8_t arpdown = (numActiveNotes + (countStep - 1)) % numActiveNotes;
uint8_t arpcountplus = 1 + (arpcount % arprepeat);
switch (style) {
case 1: // up
countStep = arpup;
if (countStep == 0) arpcount = arpcountplus;
break;
case 2: // down
countStep = arpdown;
if (countStep == (numActiveNotes - 1)) arpcount = arpcountplus;
break;
case 3: // up-down
if (ArpDirection) {
countStep = arpup;
if (countStep == 0) arpcount = arpcountplus;
if ((countStep + 1) == numActiveNotes) ArpDirection = false;
} else {
countStep = arpdown;
if (countStep == 0) arpcount = arpcountplus;
if (countStep == 0) ArpDirection = true;
}
break;
case 4: // down-up
if (ArpDirection) {
countStep = arpdown;
if (countStep == (numActiveNotes - 1)) arpcount = arpcountplus;
if (countStep == 0) ArpDirection = false;
} else {
countStep = arpup;
if (countStep == (numActiveNotes - 1)) arpcount = arpcountplus;
if ((countStep + 1) == numActiveNotes) ArpDirection = true;
}
break;
case 5: // up+down
if (ArpDirection) {
countStep++;
if (countStep > (numActiveNotes - 1)) {
countStep = numActiveNotes - 1;
ArpDirection = false;
}
} else {
countStep--;
if (countStep < 0) {
countStep = 0;
ArpDirection = true;
arpcount = arpcountplus;
}
}
break;
case 6: // down+up
if (ArpDirection) {
countStep--;
if (countStep < 0) {
countStep = 0;
ArpDirection = false;
}
} else {
countStep++;
if (countStep > (numActiveNotes - 1)) {
countStep = numActiveNotes - 1;
ArpDirection = true;
arpcount = arpcountplus;
}
}
break;
case 7: // random
countStep = random(0, numActiveNotes);
arpcount = random(1, arprepeat + 1);
break;
}
if (arprepeat < 2) arpcount = 1;
}
void PrintSmallSpace(uint8_t scale) { // print a smaller blank space
oled.setScale(1);
oled.printF(space);
oled.setScale(scale);
}
void PrintTitle() { // title's printing setup
oled.setScale(2);
oled.invertText(1);
}
void PrintBottomText() { // bottom small text printing setup
oled.setScale(2);
oled.setCursorXY(0, 48);
}
void ClearScreen() { // clear screen and reset cursor position
oled.clear();
oled.home();
}
void ClearSeqPatternArray() { // clear all Pattern and Sequences arrays
for (uint8_t i = 0; i < patternLength; i++) { // clean SongPattern array
if (i < 4) songPattern[i] = (i + 1);
else songPattern[i] = (i - 3);
}
for (uint8_t j = 0; j < numberSequences; j++) { // clean Seq arrays
for (uint8_t i = 0; i < maxSeqLength; i++) {
noteSeq[j][i] = -1;
}
}
}
void PrintMainScreen() { // print menu 0 to the screen
menunumber = 0;
ClearScreen();
PrintTitle();
if (modeselect == 1) { // arp mode screen
oled.setCursorXY(4, 0);
oled.print(F(" ARP MODE "));
oled.invertText(0);
oled.setScale(3);
oled.setCursorXY(0, 19);
switch (arpstyle) {
case 1:
oled.setCursorXY(48, 19);
oled.printF(printup);
break;
case 2:
oled.setCursorXY(28, 19);
oled.printF(printdown);
break;
case 3:
oled.printF(printup);
oled.printF(minus);
oled.printF(printdown);
break;
case 4:
oled.printF(printdown);
oled.printF(minus);
oled.printF(printup);
break;
case 5:
oled.printF(printup);
oled.printF(plus);
oled.printF(printdown);
break;
case 6:
oled.printF(printdown);
oled.printF(plus);
oled.printF(printup);
break;
case 7:
oled.setCursorXY(10, 19);
oled.printF(printrandom);
break;
}
oled.setScale(2);
}
else if (modeselect == 2) { // rec mode screen
oled.setCursorXY(4, 0);
oled.print(F(" REC MODE "));
oled.invertText(0);
oled.setCursorXY(36, 16);
oled.printF(seq);
oled.print(currentSeq + 1);
if (seqLength > 9) oled.setCursorXY(13, 32);
else oled.setCursorXY(18, 32);
oled.printF(length);
if (seqLength > 9) oled.setCursorXY(90, 32);
else oled.setCursorXY(95, 32);
oled.print(seqLength);
}
else if (modeselect == 3) { // song mode screen
oled.print(F(" SONG MODE "));
oled.invertText(0);
PrintPatternSequence();
PrintSongLiveCursor = true;
}
else if (modeselect == 4) { // live mode screen
oled.print(F(" LIVE MODE "));
oled.invertText(0);
for (uint8_t i = 0; i < 4; ++i) {
uint8_t x = (i < 2) ? 13 : 102;
uint8_t y = (i % 2 == 0) ? 16 : 48;
oled.setCursorXY(x, y);
oled.print(i + 1);
}
PrintSongLiveCursor = true;
}
PrintTimeBar = true;
}
void PrintBPMBar() { // print BPM status bar to the screen
oled.setScale(2);
if (modeselect != 4) oled.clear(0, 54, 127, 64);
else oled.clear(40, 16, 86, 31);
if ((internalClock && BPM > 99) || (!internalClock)) {
if (modeselect != 4) oled.setCursorXY(4, 48);
else oled.setCursorXY(46, 16);
}
else if (internalClock && BPM < 100) {
if (modeselect != 4) oled.setCursorXY(11, 48);
else oled.setCursorXY(52, 16);
}
if (internalClock) oled.print(BPM);
else oled.printF(ext);
if (modeselect != 4) {
PrintSmallSpace(2);
} else oled.setCursorXY(46, 32);
oled.printF(printbpm);
if (modeselect != 4) {
PrintSmallSpace(2);
} else oled.setCursorXY(45, 48);
oled.print(tSignature);
oled.printF(fourth);
}
void PrintPatternSequence() { // print the sequence to the screen - song mode
for (uint8_t i = 0; i < patternLength; i++) {
if (menunumber == 0) oled.setCursorXY((i * 16) + 2, 16);
else oled.setCursorXY(i * 16, 30);
if (songPattern[i] > 0) oled.print(songPattern[i]);
else oled.printF(printx);
}
}
void PrintPatternSequenceCursor() { // print the cursor to the screen - song mode
oled.setScale(2);
if (modeselect == 3) { // song mode
oled.clear(0, 34, 127, 40);
oled.setCursorXY((pattern * 16) + 2, 32);
if (!lockpattern) oled.printF(upcursor);
else oled.print(F("="));
}
else if (modeselect == 4) { // live mode
oled.clear(11, 32, 25, 40);
oled.clear(100, 32, 114, 40);
if (currentSeq < 2) oled.setCursorXY(13, 32);
else oled.setCursorXY(102, 32);
if (currentSeq == 0 || currentSeq == 2) oled.printF(upcursor);
else oled.printF(downcursor);
}
}
void PrintMenu(uint8_t item) { // print menu 1 to the screen
menunumber = 1;
ClearScreen();
oled.setScale(3);
oled.printF(printnext);
switch (item) {
case 0: // load-save
oled.printF(file);
break;
case 1: // mode select
oled.printlnF(printmode);
oled.printF(select);
break;
case 2: // seq.select
if (modeselect == 1) {
oled.printlnF(arp);
oled.printF(printstyle);
}
else if (modeselect == 2) {
oled.printlnF(seq);
oled.printF(select);
}
else if (modeselect == 3) {
oled.printlnF(edit);
oled.printF(song);
}
else if (modeselect == 4) {
if (playing) oled.println(F("STOP"));
else oled.println(F("START"));
oled.printF(printlive);
}
break;
case 3: // tempo
oled.print(F("TEMPO"));
break;
case 4: // pitch
oled.printlnF(printpitch);
break;
case 5: // scale
oled.printlnF(printscale);
break;
case 6: // jitter
oled.println(F("JITTER"));
break;
case 7: // note lenght
oled.printlnF(printnote);
oled.printF(length);
break;
case 8: // seq.length / arp steps
if (modeselect != 1) {
oled.printlnF(seq);
oled.printF(length);
} else {
oled.printlnF(arp);
oled.printF(steps);
}
break;
case 9: // arp/seq. speed
if (modeselect != 1) oled.printlnF(seq);
else oled.printlnF(arp);
oled.printF(speed);
break;
case 10: // trig.mode / chain rec / snap mode
if (modeselect < 3) {
oled.println(F("TRIG"));
oled.printF(printmode);
} else if (modeselect == 3) {
oled.printlnF(chain);
oled.printF(rec);
} else if (modeselect == 4) {
oled.println(F("SNAP"));
oled.printF(printmode);
}
break;
case 11: // swing
oled.printF(printswing);
break;
case 12: // metro
oled.printF(printmetro);
break;
case 13: // midi channel
oled.println(F("MIDI"));
oled.print(F("CHANNEL"));
break;
case 14: // midi realtime messages
oled.println(F("SEND"));
oled.printF(sync);
break;
case 15: // sync port
oled.printlnF(ext);
oled.printF(sync);
break;
case 16: // map buttons
oled.printlnF(printmap);
oled.print(F("KEYS"));
break;
case 17: // sound
oled.printF(printsound);
break;
case 18: // restart
oled.print(F("REBOOT"));
break;
}
}
void PrintSubmenu(uint8_t item) { // print menu 2 to the screen
menunumber = 2;
ClearScreen();
oled.setScale(3);
if (item == 0) oled.printF(printnext);
else if (item != 6 && item != 18) oled.printF(printback);
switch (item) {
case 0: // load-save
if (modeselect == 1 && savemode < 2) savemode = 2;
if (savemode == 0) oled.println(F("BAKE"));
else if (savemode == 1) oled.println(F("CLONE"));
else if (savemode == 2) oled.println(F("NEW"));
else if (savemode == 3) oled.println(F("SAVE"));
else if (savemode == 4) oled.println(F("LOAD"));
else if (savemode == 5) oled.println(F("DELETE"));
if ((savemode == 0 && modeselect == 2) || (modeselect == 3 && lockpattern) || (savemode == 1)) oled.printF(seq);
else oled.printF(song);
break;
case 1: // mode select
oled.printlnF(printmode);
if (premodeselect == 1) oled.printF(arp);
else if (premodeselect == 2) oled.printF(rec);
else if (premodeselect == 3) oled.printF(song);
else if (premodeselect == 4) oled.printF(printlive);
break;
case 2: // arp style/seq.select/edit song/start-stop live
if (modeselect == 1) {
oled.printlnF(printstyle);
switch (arpstyle) {
case 1:
oled.printF(printup);
break;
case 2:
oled.printF(printdown);
break;
case 3:
oled.printF(printup);
oled.printF(minus);
oled.printF(printdown);
break;
case 4:
oled.printF(printdown);
oled.printF(minus);
oled.printF(printup);
break;
case 5:
oled.printF(printup);
oled.printF(plus);
oled.printF(printdown);
break;
case 6:
oled.printF(printdown);
oled.printF(plus);
oled.printF(printup);
break;
case 7:
oled.printF(printrandom);
break;
}
PrintBottomText();
if (sortnotes) {
oled.print(F("ORDERED"));
} else oled.print(F("KEY-ORDER"));
}
else if (modeselect == 2) {
oled.printlnF(seq);
oled.print(currentSeq + 1);
}
else if (modeselect == 3) {
oled.printlnF(edit);
oled.setScale(2);
PrintPatternSequence();
oled.setCursorXY((curpos * 16), 48);
oled.printF(upcursor);
}
else if (modeselect == 4) {
if (!playing) muted = true;
StartAndStop();
confirmsound = true;
greentristate = 1;
PrintMainScreen();
}
break;
case 3: // tempo
oled.printlnF(printbpm);
oled.print(BPM);
PrintSmallSpace(3);
if (!internalClock) oled.print(F("INT"));
break;
case 4: // pitch
oled.printlnF(printpitch);
if (pitch > 0) oled.printF(plus);
oled.print(pitch);
if (pitchmode) {
oled.printF(space);
uint8_t normalizedTranspose = (pitch % 12 + 12) % 12; // normalize the transpose value
oled.printF(noteNames[normalizedTranspose]);
}
PrintBottomText();
if (!pitchmode) {
oled.print(F("PRE-"));
oled.printF(printscale);
} else {
oled.printF(printscale);
oled.print(F("-ROOT"));
}
break;
case 5: // scale
oled.printlnF(printscale);
oled.printF(scaleNames[scale]);
PrintBottomText();
oled.print(F("TRANSP."));
if (posttranspose > 0) oled.printF(plus);
oled.print(posttranspose);
break;
case 6: // jitter
PrintTitle();
oled.println(F(" JITTER "));
oled.invertText(0);
oled.print(F(" RANG "));
oled.println(jitrange);
oled.print(F(" PROB "));
oled.print(jitprob * 10);
oled.printlnF(percent);
oled.print(F(" MISS "));
oled.print(jitmiss * 10);
oled.printF(percent);
oled.setCursorXY(0, Jittercur * 16);
oled.printF(printnext);
break;
case 7: // note lenght
oled.printlnF(length);
if (noteLengthSelect == 0)
oled.printF(printrandom);
else {
oled.print(noteLengthSelect * 20);
oled.printF(percent);
}
break;
case 8: // seq.length / arp steps
if (modeselect != 1) {
oled.printlnF(length);
oled.print(seqLength);
} else {
oled.printlnF(steps);
if (arprepeat > 1) oled.printF(plus);
oled.print(arprepeat - 1);
PrintBottomText();
oled.print(F("DIST."));
if (stepdistance > 0) oled.printF(plus);
oled.print(stepdistance);
}
break;
case 9: // arp/seq. speed
oled.printlnF(speed);
if (StepSpeed == 4) {
oled.printF(half);
} else if (StepSpeed == 3) {
oled.printF(third);
} else if (StepSpeed == 2) {
oled.printF(normal);
} else if (StepSpeed == 1) {
oled.printF(twice);
}
break;
case 10: // trig.mode / chain rec / snap mode
if (modeselect < 3) {
oled.printlnF(printmode);
if (trigMode == 0) oled.print(F("HOLD"));
else if (trigMode == 1) {
oled.print(F("GATE"));
//numActiveNotes = 0;
} else if (trigMode == 2) oled.print(F("RETRIG"));
}
else if (modeselect == 3) {
oled.printlnF(chain);
if (chainrec) oled.printF(on);
else oled.printF(off);
}
else if (modeselect == 4) {
oled.printlnF(printmode);
if (snapmode == 0) oled.print(F("PATTERN"));
if (snapmode == 1) oled.print(F("UP-"));
if (snapmode > 0) oled.print(F("BEAT"));
}
break;
case 11: // swing
oled.printlnF(printswing);
if (!swing) oled.printF(off);
else if (swing) oled.printF(on);
break;
case 12: // metro
oled.printlnF(printmetro);
if (!metro) oled.printF(off);
else if (metro) oled.printF(on);
PrintBottomText();
oled.print(F("SIGN."));
oled.print(tSignature);
oled.printF(fourth);
break;
case 13: // midi channel
oled.println(F("CH"));
oled.print(midiChannel);
break;
case 14: // midi realtime messages
oled.printlnF(sync);
if (!sendrealtime) oled.printF(off);
else if (sendrealtime == 1) oled.printF(on);
else if (sendrealtime == 2) oled.print(F("ALWAYS"));
break;
case 15: // sync port
oled.println(F("PORT"));
if (syncport == 0) oled.printF(off);
else if (syncport > 0) oled.print(syncport);
break;
case 16: // map buttons
oled.printlnF(printmap);
oled.printF(printCC);
AllLedsOff();
if (mapButtonSelect == 1) {
oled.print(redbuttonCC);
digitalWrite(redled, HIGH);
} else if (mapButtonSelect == 2) {
oled.print(yellowbuttonCC);
digitalWrite(yellowled, HIGH);
} else if (mapButtonSelect == 3) {
oled.print(bluebuttonCC);
digitalWrite(blueled, HIGH);
} else if (mapButtonSelect == 4) {
oled.print(greenbuttonCC);
digitalWrite(greenled, HIGH);
}
break;
case 17: // sound
oled.printlnF(printsound);
if (soundmode == 1) oled.printF(on);
else if (soundmode == 2) oled.print(F("UI-"));
if (soundmode != 1) oled.printF(off);
break;
case 18: // restart
asm volatile(" jmp 0");
break;
}
}
void SubmenuSettings(uint8_t item, bool dir) { // handles changing settings in menu 2
switch (item) {
case 0: // load-save
if (dir == LOW) {
if (savemode < 5) savemode++;
} else {
if (savemode > 0) savemode--;
}
break;
case 1: // mode select
if (dir == LOW) {
if (premodeselect < 4) premodeselect++;
} else {
if (premodeselect > 1) premodeselect--;
}
break;
case 2: // seq.select
if (modeselect == 1) {
if (!greenstate) {
if (dir == HIGH) {
if (arpstyle > 1) arpstyle--;
} else {
if (arpstyle < 7) arpstyle++;
}
} else {
if (dir == HIGH) {
sortnotes = true;
} else {
sortnotes = false;
}
}
}
else if (modeselect == 2) {
if (dir == HIGH) {
if (currentSeq > 0) currentSeq--;
} else {
if (currentSeq < (numberSequences - 1)) currentSeq++;
}
}
else if (modeselect == 3) {
if (!greenstate) {
if (dir == HIGH) {
if (curpos < 7) curpos++;
} else {
if (curpos > 0) curpos--;
}
} else {
if (dir == HIGH) {
if (songPattern[curpos] < 4) songPattern[curpos]++;
} else {
if (songPattern[curpos] > 0) {
if (curpos > 0) songPattern[curpos]--;
else if (curpos == 0 && (songPattern[curpos] > 1)) songPattern[curpos]--;
}
}
}
}
break;
case 3: // tempo
if (dir == HIGH) {
if (!greenstate) BPM++;
else BPM += 5;
} else {
if (!greenstate) BPM--;
else BPM -= 5;
}
BPM = constrain(BPM, 20, 250);
SetBPM(BPM);
break;
case 4: // pitch
if (!greenstate) {
if (dir == HIGH) {
if (pitch < 12) pitch++;
} else {
if (pitch > -12) pitch--;
}
} else {
if (dir == HIGH) {
pitchmode = true;
} else {
pitchmode = false;
}
}
if (!playing) AllNotesOff();
break;
case 5: // scale
if (!greenstate) {
if (dir == LOW) {
if (scale < 11) scale++;
} else {
if (scale > 0) scale--;
}
} else {
if (dir == HIGH) {
if (posttranspose < 12) posttranspose++;
} else {
if (posttranspose > -12) posttranspose--;
}
}
if (!playing) AllNotesOff();
break;
case 6: // jitter
if (!greenstate) {
if (dir == LOW) {
if (Jittercur < 3) Jittercur++;
} else {
if (Jittercur > 1) Jittercur--;
}
} else {
if (dir == HIGH) {
if (Jittercur == 1) {
if (jitrange < 24) jitrange++;
} else if (Jittercur == 2) {
if (jitprob < 10) jitprob++;
} else if (Jittercur == 3) {
if (jitmiss < 9) jitmiss++;
}
} else {
if (Jittercur == 1) {
if (jitrange > 0) jitrange--;
} else if (Jittercur == 2) {
if (jitprob > 0) jitprob--;
} else if (Jittercur == 3) {
if (jitmiss > 0) jitmiss--;
}
}
}
break;
case 7: // note lenght
if (dir == HIGH) {
if (noteLengthSelect < 6) noteLengthSelect++;
} else {
if (noteLengthSelect > 0) noteLengthSelect--;
}
break;
case 8: // seq.length / arp steps
if (modeselect != 1) {
if (dir == HIGH) {
if (seqLength < maxSeqLength) seqLength++;
} else {
if (seqLength > 1) seqLength--;
}
} else {
if (!greenstate) {
if (dir == HIGH) {
if (arprepeat < 5) arprepeat++;
} else {
if (arprepeat > 1) arprepeat--;
}
} else {
if (dir == HIGH) {
if (stepdistance < 12) stepdistance++;
} else {
if (stepdistance > -12) stepdistance--;
}
}
}
break;
case 9: // arp/seq. speed
if (dir == HIGH) {
if (StepSpeed > 1) StepSpeed--;
} else {
if (StepSpeed < 4) StepSpeed++;
}
FixSync = true;
break;
case 10: // trig.mode / chain rec
if (modeselect < 3) {
if (dir == HIGH) {
if (trigMode > 0) trigMode--;
} else {
if (trigMode < 2) trigMode++;
}
}
else if (modeselect == 3) {
if (dir == HIGH) chainrec = true;
else chainrec = false;
}
else if (modeselect == 4) {
if (dir == HIGH) {
if (snapmode < 2) snapmode++;
} else {
if (snapmode > 0) snapmode--;
}
}
break;
case 11: // swing
if (dir == HIGH) swing = true;
else swing = false;
break;
case 12: // metro
if (!greenstate) {
if (dir == HIGH) metro = true;
else metro = false;
} else {
if (dir == HIGH) {
if (tSignature < 8) tSignature++;
} else {
if (tSignature > 1) tSignature--;
}
}
break;
case 13: // midi channel
if (!greenstate) {
AllNotesOff();
if (dir == HIGH) {
if (midiChannel < 16) midiChannel++;
} else {
if (midiChannel > 1) midiChannel--;
}
} else {
EEPROM.update(0, midiChannel);
ScreenBlink();
}
break;
case 14: // send midi realtime messages
if (!greenstate) {
if (dir == LOW) {
if (sendrealtime < 2) sendrealtime++;
} else {
if (sendrealtime > 0) sendrealtime--;
}
} else {
EEPROM.update(1, sendrealtime);
ScreenBlink();
}
break;
case 15: // sync port
if (internalClock) {
if (!greenstate) {
if (dir == HIGH) {
if (syncport < 2) syncport++;
} else {
if (syncport > 0) syncport--;
}
} else {
EEPROM.update(3, syncport);
ScreenBlink();
}
}
SetSyncPort(syncport);
break;
case 16: // map buttons
if (!greenstate) {
if (dir == HIGH) {
if (mapButtonSelect > 1) mapButtonSelect--;
} else {
if (mapButtonSelect < 4) mapButtonSelect++;
}
} else {
EEPROM.update(4, redbuttonCC);
EEPROM.update(5, yellowbuttonCC);
EEPROM.update(6, bluebuttonCC);
EEPROM.update(7, greenbuttonCC);
ScreenBlink();
}
break;
case 17: // sound
if (!greenstate) {
if (dir == HIGH) {
if (soundmode > 1) soundmode--;
} else {
if (soundmode < 3) soundmode++;
}
} else {
EEPROM.update(2, soundmode);
ScreenBlink();
}
SetSound(soundmode);
break;
case 18: // restart
break;
}
PrintSubmenu(item);
}
void PrintLoadSaveMenu(uint8_t mode) { // print menu 3 to the screen
menunumber = 3;
oled.home();
PrintTitle();
if (mode == 0) { // bake song
confirmation = true;
PrintConfirmationPopup();
}
else if (mode == 1) { // clone seq.
oled.clear(0, 32, 127, 48);
oled.print(F(" CLONE SEQ "));
oled.invertText(0);
oled.setCursorXY(35, 16);
oled.printF(seq);
oled.print(currentSeq + 1);
oled.setCursorXY(58, 32);
oled.print(F("$"));
oled.setCursorXY(23, 48);
oled.printF(printnext);
oled.printF(seq);
oled.print(currentSeqCopy + 1);
}
else if (mode == 2) { // new song
oled.clear(0, 32, 127, 48);
oled.setCursorXY(5, 0);
oled.print(F(" NEW SONG "));
oled.invertText(0);
oled.setCursorXY(5, 16);
oled.printF(seq);
oled.printF(length);
oled.setScale(3);
if (NewSeqLength > 9) oled.setCursorXY(45, 32);
else oled.setCursorXY(56, 32);
oled.print(NewSeqLength);
}
else if (mode > 1) { // save, load, delete song
oled.clear(48, 20, 64, 64);
oled.clear(112, 20, 127, 127);
if (mode == 3) {
oled.print(F(" SAVE SONG "));
}
else if (mode == 4) {
oled.print(F(" LOAD SONG "));
}
else if (mode == 5) {
oled.print(F(" DELETE "));
}
oled.invertText(0);
for (uint8_t i = 0; i < 6; i++) {
if (i < 3) oled.setCursorXY(0, (i + 1) * 16);
else oled.setCursorXY(65, (i - 2) * 16);
if (EEPROM.read(eepromaddress(16, i)) > 0) {
oled.print(F("SNG"));
oled.print(i + 1);
} else oled.print(F("----"));
}
if (savecurX <= 2) {
oled.setCursorXY(50, (savecurX + 1) * 16);
}
if (savecurX >= 3) {
oled.setCursorXY(115, (savecurX - 2) * 16);
}
oled.printF(printback);
}
}
void LoadSave(uint8_t mode, uint8_t number) { // (bake/clone/new/save/load/delete, slot number 0-5)
if (mode == 0) { // bake song
BakeSequence();
ScreenBlink();
PrintMainScreen();
}
else if (mode == 1) { // clone seq.
if (currentSeq != currentSeqCopy) {
CloneSequence(currentSeq, currentSeqCopy);
}
ScreenBlink();
PrintMainScreen();
}
else if (mode == 2) { // new song
playing = false;
StepSpeed = 2;
swing = false;
noteLengthSelect = 4;
seqLength = NewSeqLength;
BPM = 120;
SetBPM(BPM);
lockpattern = false;
pattern = 0;
NewSeqLength = 16;
ClearSeqPatternArray();
modeselect = 3;
currentSeq = 0;
ScreenBlink();
PrintMainScreen();
}
else if (mode > 2) {
if (mode == 3) { // save song
EEPROM.update((eepromaddress(16, number)), 1);
EEPROM.update((eepromaddress(17, number)), pitchmode);
EEPROM.update((eepromaddress(18, number)), pitch);
EEPROM.update((eepromaddress(19, number)), scale);
EEPROM.update((eepromaddress(20, number)), posttranspose);
EEPROM.update((eepromaddress(21, number)), seqLength);
EEPROM.update((eepromaddress(22, number)), BPM);
EEPROM.update((eepromaddress(23, number)), swing);
for (uint8_t i = 24; i <= 159; i++) {
if (i <= 31) EEPROM.update((eepromaddress(i, number)), songPattern[i - 24]); // save SongPattern array
else if (i <= 63) EEPROM.update((eepromaddress(i, number)), noteSeq[0][i - 32]); // save Seq.1 array
else if (i <= 95) EEPROM.update((eepromaddress(i, number)), noteSeq[1][i - 64]); // save Seq.2 array
else if (i <= 127) EEPROM.update((eepromaddress(i, number)), noteSeq[2][i - 96]); // save Seq.3 array
else if (i <= 159) EEPROM.update((eepromaddress(i, number)), noteSeq[3][i - 128]); // save Seq.4 array
}
ScreenBlink();
oled.clear();
}
else if (mode == 4) { // load song
if (full) {
playing = false;
lockpattern = false;
pattern = 0;
pitchmode = EEPROM.read(eepromaddress(17, number));
pitch = EEPROM.read(eepromaddress(18, number));
scale = EEPROM.read(eepromaddress(19, number));
posttranspose = EEPROM.read(eepromaddress(20, number));
seqLength = EEPROM.read(eepromaddress(21, number));
BPM = EEPROM.read(eepromaddress(22, number));
swing = EEPROM.read(eepromaddress(23, number));
for (uint8_t i = 24; i <= 159; i++) {
if (i <= 31) songPattern[i - 24] = EEPROM.read(eepromaddress(i, number)); // load SongPattern array
else if (i <= 63) noteSeq[0][i - 32] = EEPROM.read(eepromaddress(i, number)); // load Seq.1 array
else if (i <= 95) noteSeq[1][i - 64] = EEPROM.read(eepromaddress(i, number)); // load Seq.2 array
else if (i <= 127) noteSeq[2][i - 96] = EEPROM.read(eepromaddress(i, number)); // load Seq.3 array
else if (i <= 159) noteSeq[3][i - 128] = EEPROM.read(eepromaddress(i, number)); // load Seq.4 array
}
SetBPM(BPM);
if (modeselect != 4) modeselect = 3;
currentSeq = 0;
ManageRecording();
ScreenBlink();
} else confirmation = false;
}
else if (mode == 5) { // delete song
if (full) {
EEPROM.update((eepromaddress(16, number)), 0);
ScreenBlink();
oled.clear();
} else confirmation = false;
}
PrintLoadSaveMenu(savemode);
}
}
void BakeSequence() { // bake transpose & scale in to current seq / all seqs
if ((modeselect == 2) || (modeselect == 3 && lockpattern)) { // bake current Seq array
for (uint8_t i = 0; i < maxSeqLength; i++) {
if (noteSeq[currentSeq][i] > 0) noteSeq[currentSeq][i] = TransposeAndScale(noteSeq[currentSeq][i]);
}
}
else {
for (uint8_t j = 0; j < numberSequences; j++) { // bake all Seq arrays
for (uint8_t i = 0; i < maxSeqLength; i++) {
if (noteSeq[j][i] > 0) noteSeq[j][i] = TransposeAndScale(noteSeq[j][i]);
}
}
}
scale = 0;
pitch = 0;
posttranspose = 0;
}
void CloneSequence(uint8_t source, uint8_t destination) {
for (uint8_t i = 0; i < maxSeqLength; i++) {
noteSeq[destination][i] = noteSeq[source][i];
}
}
void PrintConfirmationPopup() { // print confirmation popup in load-save menu
PrintTitle();
oled.setCursorXY(28, 33);
if (savemode < 3) { // bake & new
oled.setCursorXY(10, 33);
oled.print(F(" PROCEED?"));
}
else if (savemode == 3) { // save
if (!full) oled.print(F(" SAVE?"));
else {
oled.setCursorXY(4, 33);
oled.print(F(" OVERRIDE?"));
}
}
else if (savemode > 3) { // load
if (!full) {
oled.setCursorXY(22, 33);
oled.print(F(" EMPTY!"));
} else {
if (savemode == 4) oled.print(F(" LOAD?"));
else if (savemode == 5) { // delete
oled.setCursorXY(16, 33);
oled.print(F(" DELETE?"));
}
}
}
oled.invertText(0);
}
void PrintInterRecordingPopup() { // seq. select popup
PrintTitle();
oled.setCursorXY(28, 33);
oled.printF(space);
oled.printF(seq);
oled.print(newcurrentSeq + 1);
oled.invertText(0);
}
void DebounceButtons() { // debounce buttons
static bool lastGreenDeb = false;
static bool lastYellowDeb = false;
static bool lastRedDeb = false;
static bool lastBlueDeb = false;
static unsigned long lastDebounceTime = 0;
static const uint8_t debounceDelay = 10;
bool greenReading = !digitalRead(greenbutton);
bool yellowReading = !digitalRead(yellowbutton);
bool redReading = !digitalRead(redbutton);
bool blueReading = !digitalRead(bluebutton);
bool stateChanged = (greenReading != lastGreenDeb) || (yellowReading != lastYellowDeb) || (redReading != lastRedDeb) || (blueReading != lastBlueDeb);
if (stateChanged) {
lastDebounceTime = millis();
if (!EnableButtons) {
EnableButtons = true;
numbuttonspressedCC = 0;
}
}
if ((millis() - lastDebounceTime) > debounceDelay && EnableButtons) {
if (greenReading != greenstate) {
greenstate = greenReading;
ButtonsCommands(greenstate);
} else if (yellowReading != yellowstate) {
yellowstate = yellowReading;
ButtonsCommands(yellowstate);
} else if (redReading != redstate) {
redstate = redReading;
ButtonsCommands(redstate);
} else if (blueReading != bluestate) {
bluestate = blueReading;
ButtonsCommands(bluestate);
}
}
lastGreenDeb = greenReading;
lastYellowDeb = yellowReading;
lastRedDeb = redReading;
lastBlueDeb = blueReading;
}
void ButtonsCommands(bool anystate) { // manage the buttons's commands
static bool lockmute = false; // keep the mute muted. only in arp, seq & song mode
static bool redispressed = false; // is redbutton still pressed?
static bool yellowispressed = false; // is yellowbutton still pressed?
static bool greenispressed = false; // is greenbutton still pressed?
static bool blueispressed = false; // is bluebutton still pressed?
bool newredstate = false; // reset redstate snapshot
bool newyellowstate = false; // reset yellowstate snapshot
bool newgreenstate = false; // reset bluestate snapshot
bool newbluestate = false; // reset greenstate snapshot
if (!redispressed) newredstate = redstate; // take a redstate snapshot
if (!yellowispressed) newyellowstate = yellowstate; // take a yellowstate snapshot
if (!blueispressed) newbluestate = bluestate; // take a bluestate snapshot
if (!greenispressed) newgreenstate = greenstate; // take a greenstate snapshot
StartScreenTimer = true; // if any button is pressed or released, start the screen timer
if (!greentristate) greentristate = 1; // set greentristate to null
if (menunumber == 0) { // main screen
if (newgreenstate) { // go to menu
greentristate = 2;
StartMenuTimer = true;
}
if (greentristate == 2 && !newgreenstate) {
greentristate = 0;
}
if (modeselect != 4) {
if (yellowispressed && newgreenstate) { // restart sequence
if (!playing) playing = true;
Startposition();
}
else if (blueispressed) {
if (newgreenstate) SynthReset(); // reset synth
else if (newredstate && !recording) { // lock mute
lockmute = !lockmute;
}
}
else if (greenispressed) {
if (newredstate && modeselect == 3) { // lock pattern
lockpattern = !lockpattern;
PrintSongLiveCursor = true;
}
}
else if (!greenispressed) {
if (newbluestate) { // recording and not playing bluebutton add a space
if (!playing && internalClock && recording) {
HandleStep();
noteSeq[currentSeq][countStep] = -1;
}
}
else if (newyellowstate) { // play/stop
StartAndStop();
}
if (!blueispressed && newredstate) { // start/stop recording
if ((modeselect == 3 && !playing) || modeselect == 1) {
if (!recording) {
menunumber = 4;
PrintInterRecordingPopup();
} else {
recording = false;
ManageRecording();
}
} else {
recording = !recording;
ManageRecording();
}
}
}
if (!lockmute) muted = newbluestate; // toggle mute
else {
muted = !newbluestate;
digitalWrite(blueled, !newbluestate);
}
}
else if (modeselect == 4) {
if (newredstate) newcurrentSeq = 0;
else if (newyellowstate) newcurrentSeq = 1;
else if (newbluestate) newcurrentSeq = 2;
else if (!greentristate) newcurrentSeq = 3;
if (newredstate || newyellowstate || newbluestate || !greentristate) {
if (!playing) {
currentSeq = newcurrentSeq;
PrintSongLiveCursor = true;
StartAndStop();
muted = false;
} else {
TrigMute = true;
if (muted) {
currentSeq = newcurrentSeq;
PrintSongLiveCursor = true;
}
}
}
}
}
else if (menunumber == 1) { // menu
if (newgreenstate) { // go to submenu
PrintSubmenu(menuitem);
}
else if (newbluestate) { // go back to main screen
PrintMainScreen();
if (lockmute) digitalWrite(blueled, HIGH);
}
else if (newyellowstate) { // go up in the menu
if (menuitem < 18) {
menuitem++;
PrintMenu(menuitem);
}
}
else if (newredstate) { // go down in the menu
if (menuitem > 0) {
menuitem--;
PrintMenu(menuitem);
}
}
}
else if (menunumber == 2) { // submenu
if (newbluestate) PrintMenu(menuitem); // go back to the menu
else if (newgreenstate) {
if (menuitem == 0) {
savecurX = 0;
if (savemode > 0) oled.clear();
PrintLoadSaveMenu(savemode);
newgreenstate = false;
}
else if (menuitem == 1) {
newcurrentSeq = currentSeq;
modeselect = premodeselect;
ScreenBlink();
PrintMainScreen();
//newgreenstate = false;
}
else if (menuitem == 3) TapTempo();
}
else if (newyellowstate) SubmenuSettings(menuitem, LOW);
else if (newredstate) SubmenuSettings(menuitem, HIGH);
}
else if (menunumber == 3) { // load/save menu
if (!confirmation) {
if (savemode == 1) { // clone seq.
if (newredstate) {
if (currentSeqCopy < (numberSequences + 1)) currentSeqCopy++;
}
else if (newyellowstate) {
if (currentSeqCopy > 0) currentSeqCopy--;
}
}
else if (savemode == 2) { // new song
if (newyellowstate) {
if (NewSeqLength > 1) {
NewSeqLength--;
}
}
else if (newredstate) {
if (NewSeqLength < 32) {
NewSeqLength++;
}
}
}
else { // save/load/delete
if (newredstate) {
if (savecurX > 0) {
savecurX--;
}
}
else if (newyellowstate) {
if (savecurX < 5) {
savecurX++;
}
}
full = EEPROM.read(eepromaddress(16, savecurX));
}
if (anystate) PrintLoadSaveMenu(savemode);
if (newgreenstate) {
confirmation = true;
PrintConfirmationPopup();
newgreenstate = false;
}
}
if (newgreenstate) {
if (confirmation) {
LoadSave(savemode, savecurX);
confirmation = false;
}
}
else if (newbluestate) {
if (!confirmation) PrintSubmenu(menuitem);
else {
confirmation = false;
if (savemode > 0) PrintLoadSaveMenu(savemode);
else PrintSubmenu(menuitem);
}
}
}
else if (menunumber == 4) { // Inter-Recording Popup
if (newbluestate) {
menunumber = 0;
PrintMainScreen();
}
else if (newgreenstate) {
menunumber = 0;
currentSeq = newcurrentSeq;
pattern = currentSeq;
PrintMainScreen();
IntercountStep = -1;
recording = true;
ManageRecording();
}
else if (newyellowstate) {
if (newcurrentSeq > 0) newcurrentSeq--;
PrintInterRecordingPopup();
}
else if (newredstate) {
if (newcurrentSeq < (numberSequences - 1)) newcurrentSeq++;
PrintInterRecordingPopup();
}
}
if (anystate) {
if (confirmsound) { // bips
confirmsound = false;
Bip(2);
} else Bip(1); // do the buttons click if any button is pressed
}
if (!(menunumber == 2 && menuitem == 16)) { // if not in CC mapping menu, turn the led on if button is pressed
if (!recording) {
digitalWrite(redled, redstate);
digitalWrite(yellowled, yellowstate);
}
digitalWrite(greenled, greenstate);
if (!lockmute || menunumber > 0) digitalWrite(blueled, newbluestate);
}
greenispressed = greenstate;
blueispressed = bluestate;
redispressed = redstate;
yellowispressed = yellowstate;
}