#include <FastLED.h>
#include <OneButton.h>
#include <MIDI.h>
#define LED_PIN 13
#define NUM_LEDS 11
CRGB leds[NUM_LEDS];
#define BRIGHTNESS 255
const int DEBUG = 1;
enum LoopBtnPressedStates {
STOP,
RECORD,
PLAY
};
const char* loopStateStr[] = {"STOP", "RECORD", "PLAY"};
/////////////////////////////////////////////
// MIDI
byte midiCh = 1; //* MIDI channel to be used
byte note = 1; //* Lowest note to be used
byte cc = 1; //* Lowest MIDI CC to be used
/////////////////////////////////////////////
// Taskmanager to blink leds while not using a delay command
#define LIMIT 20
class BlinkManager {
public:
// C++ cannot handle dynamically assigned array, so we set up a bank of 20 reusable tasks
// A single task can blink a single led a number of times across a set delay.
// I have never managed to get more than 5 tasks at any one time in real life.
struct timerData {
int led;
int pulsesLeft;
int delay;
unsigned long runTime;
CRGB colour;
CRGB currentColour;
int beingUsed;
};
struct timerData timers[LIMIT];
int numTasks = 0;
void pulse(){
if (numTasks >= 0) {
for (int i = 0; i <= numTasks-1; i++) {
if (timers[i].beingUsed == 1) {
if (millis() >= timers[i].runTime) {
// This task is due to be run now
if (timers[i].pulsesLeft > 0){
// More pulses to come so just swap the colour to black or vise versa
if (timers[i].currentColour == CRGB::Black){
leds[timers[i].led] = timers[i].colour;
timers[i].currentColour = timers[i].colour;
} else {
leds[timers[i].led] = CRGB::Black;
timers[i].currentColour = CRGB::Black;
}
timers[i].runTime = timers[i].runTime + timers[i].delay;
timers[i].pulsesLeft--;
} else {
// Last pulse so close it out to be reused by another task
leds[timers[i].led] = CRGB::Black;
timers[i].currentColour = CRGB::Black;
timers[i].beingUsed = 0; // Allow the task to be reused
}
}
}
}
FastLED.show();
}
}
void addTask(int led, unsigned long startTime, int pulses, int delay, CRGB colour){
int task = -1;
boolean needNewTask = true;
// find the first unused task
for (int i = 0; i < numTasks; i++) {
if (timers[i].beingUsed == 0) {
if (task == -1) {
task = i;
needNewTask = false;
}
}
}
if (task == -1) {
task = numTasks;
needNewTask = true;
}
timers[task].led = led;
timers[task].runTime = startTime;
timers[task].pulsesLeft = pulses*2;
timers[task].delay = delay;
timers[task].beingUsed = 1;
timers[task].colour = colour;
if (needNewTask){
numTasks++;
}
}
void cancelTask(int led){
for (int i = 0; i < numTasks; i++) {
if (timers[i].led == led){
timers[i].beingUsed = 0;
}
}
}
void debug(String msg) {
if (DEBUG == 1) {
Serial.println(msg);
}
}
};
BlinkManager blinkManager;
/////////////////////////////////////////////
class MidiButton {
public:
const int _pin;
const int _led;
OneButton _btn;
LoopBtnPressedStates _pressedState = STOP;
MidiButton(int pin, int led) :
_pin(pin),
_led(led),
_btn(OneButton(pin))
{
};
void tick(){ _btn.tick(); }
void debug(String msg) {
if (DEBUG == 1) {
Serial.println(msg);
}
}
private:
};
// /////////////////////////////////////////////////////////////
class LoopButton: public MidiButton {
public:
bool _loopRecorded = false;
LoopButton(int pin, int led) :
MidiButton(pin,led)
{
_btn.attachClick(clickCallBack, this);
_btn.attachLongPressStart(longPressCallBack, this);
}
static void clickCallBack(void *ptr) {
LoopButton *btnPtr = (LoopButton *)ptr;
btnPtr->clickHandler();
}
static void longPressCallBack(void *ptr) {
LoopButton *btnPtr = (LoopButton *)ptr;
btnPtr->longPressHandler();
}
private:
void clickHandler(){
CRGB colour;
switch (_pressedState) {
case STOP:
if (_loopRecorded){
colour = CRGB::Green;
_pressedState = PLAY;
} else {
colour = CRGB::Orange;
_pressedState = RECORD;
}
break;
case RECORD:
// Playing
colour = CRGB::Green;
_pressedState = PLAY;
_loopRecorded = true;
break;
default: //
if (_loopRecorded){
colour = CRGB::Blue;
} else {
colour = CRGB::Black;
}
_pressedState = STOP;
break;
}
leds[_led] = colour;
FastLED.show();
if (_loopRecorded){
}
};
void longPressHandler(){
_loopRecorded = false;
_pressedState = STOP;
leds[_led] = CRGB::Black;
FastLED.show();
};
};
// ////////////////////////////////////////////////////
class PulseButton: public MidiButton {
public:
PulseButton(int pin, int led) :
MidiButton(pin,led)
{
_btn.attachClick(clickCallBack, this);
_btn.attachLongPressStart(longPressCallBack, this);
}
static void clickCallBack(void *ptr) {
PulseButton *btnPtr = (PulseButton *)ptr;
btnPtr->clickHandler();
}
static void longPressCallBack(void *ptr) {
PulseButton *btnPtr = (PulseButton *)ptr;
btnPtr->longPressHandler();
}
private:
void clickHandler(){
blinkManager.addTask(_led, millis(), 1, 100, CRGB::Blue);
};
void longPressHandler(){
};
};
// ////////////////////////////////////////////////////
class OnOffButton: public MidiButton {
public:
int onOff = 0;
OnOffButton(int pin, int led) :
MidiButton(pin,led)
{
_btn.attachClick(clickCallBack, this);
_btn.attachLongPressStart(longPressCallBack, this);
}
static void clickCallBack(void *ptr) {
OnOffButton *btnPtr = (OnOffButton *)ptr;
btnPtr->clickHandler();
}
static void longPressCallBack(void *ptr) {
OnOffButton *btnPtr = (OnOffButton *)ptr;
btnPtr->longPressHandler();
}
private:
void clickHandler(){
if (onOff == 0){
onOff = 1;
leds[_led] = CRGB::Green;
} else {
onOff = 0;
leds[_led] = CRGB::Black;
}
FastLED.show();
};
void longPressHandler(){
//button6.cancelBlink();
};
};
// ////////////////////////////////////////////////////
class BankButton: public MidiButton {
public:
int onOff = 0;
int blinking = 0;
BankButton(int pin, int led) :
MidiButton(pin,led)
{
_btn.attachClick(clickCallBack, this);
_btn.attachLongPressStart(longPressCallBack, this);
}
static void clickCallBack(void *ptr) {
BankButton *btnPtr = (BankButton *)ptr;
btnPtr->clickHandler();
}
static void longPressCallBack(void *ptr) {
BankButton *btnPtr = (BankButton *)ptr;
btnPtr->longPressHandler();
}
void currentBank(){
return onOff;
}
private:
void clickHandler(){
if (onOff == 0){
onOff = 1;
leds[_led] = CRGB::Green;
} else {
onOff = 0;
leds[_led] = CRGB::Black;
}
FastLED.show();
};
void longPressHandler(){
// connecting bluetooth now...
if (blinking == 0){
blinkManager.addTask(_led, millis(), 100, 100, CRGB::Aqua);
blinking = 1;
} else {
blinkManager.cancelTask(_led);
blinking = 0;
onOff = 0;
leds[_led] = CRGB::Black;
FastLED.show();
}
};
};
LoopButton button1 = LoopButton(2,0);
LoopButton button2 = LoopButton(3,1);
LoopButton button3 = LoopButton(4,2);
LoopButton button4 = LoopButton(5,3);
PulseButton button5 = PulseButton(6,4);
BankButton button6 = BankButton(7,5);
PulseButton button7 = PulseButton(8,6);
OnOffButton button8 = OnOffButton(9,7);
LoopButton button9 = LoopButton(10,8);
LoopButton button10 = LoopButton(11,9);
LoopButton button11 = LoopButton(12,10);
void setup() {
// Baud Rate: 115200 for Hairless MIDI
Serial.begin(115200);
FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, NUM_LEDS);
FastLED.setMaxPowerInVoltsAndMilliamps(5,500);
FastLED.setBrightness(BRIGHTNESS);
FastLED.clear();
FastLED.show();
}
void loop() {
button1.tick();
button2.tick();
button3.tick();
button4.tick();
button5.tick();
button6.tick();
button7.tick();
button8.tick();
button9.tick();
button10.tick();
button11.tick();
blinkManager.pulse();
}
Up
Down
Start/Stop/Reset