diff --git a/src/Banks/Bank.cpp b/src/Banks/Bank.cpp index 45ea8ca..d857c38 100644 --- a/src/Banks/Bank.cpp +++ b/src/Banks/Bank.cpp @@ -47,7 +47,7 @@ void Bank::setBankSetting(uint8_t bankSetting) } } -void Bank::map(int (*fn)(int)) +void Bank::map(int (*fn)(int, int)) { MIDI_Element_list_node *element = firstMIDI_Element; while (element != nullptr) diff --git a/src/Banks/Bank.h b/src/Banks/Bank.h index 0eee059..4c0e465 100644 --- a/src/Banks/Bank.h +++ b/src/Banks/Bank.h @@ -26,7 +26,7 @@ class Bank } void setBankSetting(uint8_t bankSetting); - void map(int (*fn)(int)); + void map(int (*fn)(int, int)); private: const uint8_t channelsPerBank; diff --git a/src/MIDI_Outputs/Analog.cpp b/src/MIDI_Outputs/Analog.cpp index 0abf755..d92d5dc 100755 --- a/src/MIDI_Outputs/Analog.cpp +++ b/src/MIDI_Outputs/Analog.cpp @@ -11,10 +11,26 @@ Analog::Analog(pin_t analogPin, uint8_t controllerNumber, uint8_t channel) // Co this->channel = channel; } +void Analog::push() // +{ + MIDI_Controller.MIDI()->send(CC, channel + channelOffset * channelsPerBank, controllerNumber + addressOffset * channelsPerBank, 127); // send a Control Change MIDI event +} + +void Analog::release() // +{ + MIDI_Controller.MIDI()->send(CC, channel + channelOffset * channelsPerBank, controllerNumber + addressOffset * channelsPerBank, 0); // send a Control Change MIDI event +} + +void Analog::invert() // Invert the button state (send Note On event when released, Note Off when pressed) +{ + invertState = true; +} + void Analog::refresh() // read the analog value, update the average, map it to a MIDI value, check if it changed since last time, if so, send Control Change message over MIDI { unsigned int input = ExtIO::analogRead(analogPin); // read the raw analog input value - input = analogMap(input); // apply the analogMap function to the value (identity function f(x) = x by default) + if (invertState) input = 1023-input; // invert the scale + input = analogMap(analogPin, input); // apply the analogMap function to the value (identity function f(x) = x by default) #ifdef SINGLE_BYTE_AVERAGE // use 8-bit value for averaging uint8_t value = input >> 2; // map from the 10-bit analog input value [0, 1023] to the 8-bit value [0, 255] @@ -31,7 +47,7 @@ void Analog::refresh() // read the analog value, update the average, map it to a } } -void Analog::map(int (*fn)(int)) // change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in Analog::refresh() +void Analog::map(int (*fn)(int, int)) // change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in Analog::refresh() { analogMap = fn; } diff --git a/src/MIDI_Outputs/Analog.h b/src/MIDI_Outputs/Analog.h index 80522b5..ceccb27 100755 --- a/src/MIDI_Outputs/Analog.h +++ b/src/MIDI_Outputs/Analog.h @@ -10,19 +10,24 @@ class Analog : public MIDI_Control_Element { public: Analog(pin_t analogPin, uint8_t controllerNumber, uint8_t channel); // Constructor - void map(int (*fn)(int)); // Change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in Analog::refresh() + void push(); + void release(); + void invert(); // Invert the analog scale + void map(int (*fn)(int, int)); // Change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in Analog::refresh() private: void refresh(); // Read the analog input value, update the average, map it to a MIDI value, check if it changed since last time, if so, send Control Change message over MIDI pin_t analogPin; uint8_t controllerNumber, channel, oldVal = -1; - int (*analogMap)(int) = identity; // function pointer to identity function f(x) → x + int (*analogMap)(int, int) = identity; // function pointer to identity function f(x) → x - static int identity(int x) + static int identity(int p, int x) { // identity function f(x) → x return x; } + + bool invertState = false; #ifdef SINGLE_BYTE_AVERAGE uint8_t runningAverage(uint8_t value); // http://playground.arduino.cc/Main/RunningAverage diff --git a/src/MIDI_Outputs/AnalogResponsive.cpp b/src/MIDI_Outputs/AnalogResponsive.cpp new file mode 100644 index 0000000..1fa7a13 --- /dev/null +++ b/src/MIDI_Outputs/AnalogResponsive.cpp @@ -0,0 +1,69 @@ +#include "Arduino.h" +#include "AnalogResponsive.h" +#include "MIDI_Controller.h" + +using namespace ExtIO; + +AnalogResponsive::AnalogResponsive(pin_t analogPin, uint8_t msg, uint8_t controllerNumber, uint8_t channel) // Constructor +{ + this->analogPin = analogPin; + this->msg = msg; + this->controllerNumber = controllerNumber; + this->channel = channel; + this->respAnalog = new ResponsiveAnalogRead(analogPin, true); +} + +AnalogResponsive::~AnalogResponsive() // Destructor +{ + delete respAnalog; +} + +void AnalogResponsive::push(uint16_t value) // +{ + switch (msg) { + case NOTE_ON: + MIDI_Controller.MIDI()->send(NOTE_ON, channel + channelOffset * channelsPerBank, value, 127); + break; + case CONTROL_CHANGE: + MIDI_Controller.MIDI()->send(CONTROL_CHANGE, channel + channelOffset * channelsPerBank, controllerNumber + addressOffset * channelsPerBank, value); + break; + case PROGRAM_CHANGE: + MIDI_Controller.MIDI()->send(PROGRAM_CHANGE, channel + channelOffset * channelsPerBank, value); + break; + case PITCH_BEND: + MIDI_Controller.MIDI()->send(PITCH_BEND, channel + channelOffset * channelsPerBank, value, value >> 7); + break; + } +} + +void AnalogResponsive::release(uint16_t value) // +{ + this->push(value); +} + +void AnalogResponsive::invert() // Invert the button state (send Note On event when released, Note Off when pressed) +{ + invertState = true; +} + +void AnalogResponsive::refresh() // read the analog value, update the average, map it to a MIDI value, check if it changed since last time, if so, send Control Change message over MIDI +{ + unsigned int input = ExtIO::analogRead(analogPin); // read the raw analog input value + if (invertState) input = 1023-input; // invert the scale + uint16_t value = analogMap(analogPin, input); // apply the analogMap function to the value (identity function f(x) = x by default) + respAnalog->update(value); // update the responsive analog average + if (respAnalog->hasChanged()) // if the value changed since last time + { + value = respAnalog->getValue(); // get the responsive analog average value + if ( msg == PITCH_BEND) + { + value = value << 4; // make it a 14-bit number (pad with 4 zeros) + } + this->push(value); // send a MIDI event + } +} + +void AnalogResponsive::map(int (*fn)(int, int)) // change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in Analog::refresh() +{ + analogMap = fn; +} diff --git a/src/MIDI_Outputs/AnalogResponsive.h b/src/MIDI_Outputs/AnalogResponsive.h new file mode 100644 index 0000000..59313ac --- /dev/null +++ b/src/MIDI_Outputs/AnalogResponsive.h @@ -0,0 +1,36 @@ +#ifndef AnalogResponsive_h_ +#define AnalogResponsive_h_ + +#include "Arduino.h" +#include "../Settings/Settings.h" +#include "MIDI_Control_Element.h" +#include "../ExtendedInputOutput/ExtendedInputOutput.h" +#include + +class AnalogResponsive : public MIDI_Control_Element +{ +public: + AnalogResponsive(pin_t analogPin, uint8_t msg, uint8_t controllerNumber, uint8_t channel); // Constructor + ~AnalogResponsive(); // Destructor + void push(uint16_t value = 0x4000); + void release(uint16_t value = 0); + void invert(); // Invert the analog scale + void map(int (*fn)(int, int)); // Change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in Analog::refresh() + +private: + void refresh(); // Read the analog input value, update the average, map it to a MIDI value, check if it changed since last time, if so, send Control Change message over MIDI + + ResponsiveAnalogRead *respAnalog; + pin_t analogPin; + uint8_t msg, controllerNumber, channel, oldVal = -1; + int (*analogMap)(int, int) = identity; // function pointer to identity function f(x) → x + + static int identity(int p, int x) + { // identity function f(x) → x + return x; + } + + bool invertState = false; +}; + +#endif // AnalogResponsive_h_ diff --git a/src/MIDI_Outputs/Digital.cpp b/src/MIDI_Outputs/Digital.cpp index a20d231..0e93130 100755 --- a/src/MIDI_Outputs/Digital.cpp +++ b/src/MIDI_Outputs/Digital.cpp @@ -3,10 +3,11 @@ using namespace ExtIO; -Digital::Digital(pin_t pin, uint8_t note, uint8_t channel, uint8_t velocity) // Constructor +Digital::Digital(pin_t pin, uint8_t msg, uint8_t note, uint8_t channel, uint8_t velocity) // Constructor { ExtIO::pinMode(pin, INPUT_PULLUP); // Enable the internal pull-up resistor on the pin with the button/switch this->pin = pin; + this->msg = msg; this->note = note; this->channel = channel; this->velocity = velocity; @@ -17,6 +18,44 @@ Digital::~Digital() // Destructor ExtIO::pinMode(pin, INPUT); // make it a normal input again, without the internal pullup resistor. } +void Digital::push() // +{ + switch (msg) { + case NOTE_ON: + MIDI_Controller.MIDI()->send(NOTE_ON, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); + break; + case CONTROL_CHANGE: + MIDI_Controller.MIDI()->send(CONTROL_CHANGE, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, 127); + break; + case PROGRAM_CHANGE: + if (!invertState) + MIDI_Controller.MIDI()->send(PROGRAM_CHANGE, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank); + break; + case PITCH_BEND: + MIDI_Controller.MIDI()->send(PITCH_BEND, channel + channelOffset * channelsPerBank, 127, note + addressOffset * channelsPerBank); + break; + } +} + +void Digital::release() // +{ + switch (msg) { + case NOTE_ON: + MIDI_Controller.MIDI()->send(NOTE_OFF, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); + break; + case CONTROL_CHANGE: + MIDI_Controller.MIDI()->send(CONTROL_CHANGE, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, 0); + break; + case PROGRAM_CHANGE: + if (invertState) + MIDI_Controller.MIDI()->send(PROGRAM_CHANGE, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank); + break; + case PITCH_BEND: + MIDI_Controller.MIDI()->send(PITCH_BEND, channel + channelOffset * channelsPerBank, 0, note + addressOffset * channelsPerBank); + break; + } +} + void Digital::invert() // Invert the button state (send Note On event when released, Note Off when pressed) { invertState = true; @@ -28,18 +67,18 @@ void Digital::refresh() // Check if the button state changed, and send a MIDI No if (millis() - prevBounceTime > debounceTime) { - int8_t stateChange = state - buttonState; + int8_t stateChange = digitalMap(pin, state) - buttonState; if (stateChange == falling) { // Button is pushed buttonState = state; - MIDI_Controller.MIDI()->send(NOTE_ON, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); + this->push(); } if (stateChange == rising) { // Button is released buttonState = state; - MIDI_Controller.MIDI()->send(NOTE_OFF, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); + this->release(); } } if (state != prevState) @@ -47,4 +86,9 @@ void Digital::refresh() // Check if the button state changed, and send a MIDI No prevBounceTime = millis(); prevState = state; } +} + +void Digital::map(int (*fn)(int, int)) // change the function pointer for digitalMap to a new function. It will be applied to the raw digital input value in Digital::refresh() +{ + digitalMap = fn; } \ No newline at end of file diff --git a/src/MIDI_Outputs/Digital.h b/src/MIDI_Outputs/Digital.h index b5f5ad5..45b5da0 100755 --- a/src/MIDI_Outputs/Digital.h +++ b/src/MIDI_Outputs/Digital.h @@ -9,15 +9,24 @@ class Digital : public MIDI_Control_Element { public: - Digital(pin_t pin, uint8_t note, uint8_t channel, uint8_t velocity = 127); // Constructor - ~Digital(); // Destructor - void invert(); // Invert the button state (send Note On event when released, Note Off when pressed) + Digital(pin_t pin, uint8_t msg, uint8_t note, uint8_t channel, uint8_t velocity = 127); // Constructor + ~Digital(); // Destructor + void push(); + void release(); + void invert(); // Invert the button state (send Note On event when released, Note Off when pressed) + void map(int (*fn)(int, int)); // Change the function pointer for digitalMap to a new function. It will be applied to the raw digital input value in Digital::refresh() private: void refresh(); // Check if the button state changed, and send a MIDI Note On or Off accordingly + int (*digitalMap)(int, int) = identity; // function pointer to identity function f(x) → x + static int identity(int p, int x) + { // identity function f(x) → x + return x; + } + pin_t pin; - uint8_t note, channel, velocity; + uint8_t msg, note, channel, velocity; bool prevState = HIGH, buttonState = HIGH; unsigned long prevBounceTime = 0; diff --git a/src/MIDI_Outputs/DigitalLatch.cpp b/src/MIDI_Outputs/DigitalLatch.cpp index 22222f8..6d84b95 100755 --- a/src/MIDI_Outputs/DigitalLatch.cpp +++ b/src/MIDI_Outputs/DigitalLatch.cpp @@ -3,10 +3,11 @@ using namespace ExtIO; -DigitalLatch::DigitalLatch(pin_t pin, uint8_t note, uint8_t channel, uint8_t velocity, unsigned long latchTime) // Constructor +DigitalLatch::DigitalLatch(pin_t pin, uint8_t msg, uint8_t note, uint8_t channel, uint8_t velocity, unsigned long latchTime) // Constructor { ExtIO::pinMode(pin, INPUT_PULLUP); // Enable the internal pull-up resistor on the pin with the button/switch this->pin = pin; + this->msg = msg; this->note = note; this->channel = channel; this->velocity = velocity; @@ -18,28 +19,73 @@ DigitalLatch::~DigitalLatch() // Destructor ExtIO::pinMode(pin, INPUT); // make it a normal input again, without the internal pullup resistor. } +void DigitalLatch::push() // +{ + switch (msg) { + case NOTE_ON: + MIDI_Controller.MIDI()->send(NOTE_ON, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); + break; + case CONTROL_CHANGE: + MIDI_Controller.MIDI()->send(CONTROL_CHANGE, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, 127); + break; + case PROGRAM_CHANGE: + if (!invertState) + MIDI_Controller.MIDI()->send(PROGRAM_CHANGE, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank); + break; + } +} + +void DigitalLatch::release() // +{ + switch (msg) { + case NOTE_ON: + MIDI_Controller.MIDI()->send(NOTE_OFF, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); + break; + case CONTROL_CHANGE: + MIDI_Controller.MIDI()->send(CONTROL_CHANGE, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, 0); + break; + case PROGRAM_CHANGE: + if (invertState) + MIDI_Controller.MIDI()->send(PROGRAM_CHANGE, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank); + break; + } +} + +void DigitalLatch::invert() // Invert the button state (send Note On event when released, Note Off when pressed) +{ + invertState = true; +} + void DigitalLatch::refresh() // Check if the button state changed, if so, send a MIDI Note On, after a non-blocking delay of "latchTime", send a Note Off { - bool state = ExtIO::digitalRead(pin); // read the button state + bool state = ExtIO::digitalRead(pin) ^ invertState; // read the button state and invert it if "invert" is true + + state = digitalMap(pin, state); + if (state != oldState) // If the switch changed position { if (noteOffSent) // If the note is turned off { - MIDI_Controller.MIDI()->send(NOTE_ON, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); // Turn on the note + this->push(); noteOnTime = millis(); // store the time of the note on message noteOffSent = false; // The note is turned on } else // If the button is switched again, before latch time is reached { - MIDI_Controller.MIDI()->send(NOTE_OFF, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); // Turn off the note - MIDI_Controller.MIDI()->send(NOTE_ON, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); // Immediately turn the note on again - noteOnTime = millis(); // store the time of the note on message + this->release(); // Turn off the note + this->push(); // Immediately turn the note on again + noteOnTime = millis(); // store the time of the note on message } oldState = state; } if (millis() - noteOnTime > latchTime && !noteOffSent) // if the time elapsed since the Note On event is greater than the latch time, and if the note is still on { - MIDI_Controller.MIDI()->send(NOTE_OFF, channel + channelOffset * channelsPerBank, note + addressOffset * channelsPerBank, velocity); // Turn off the note + this->release(); noteOffSent = true; // The note is turned off } +} + +void DigitalLatch::map(int (*fn)(int, int)) // change the function pointer for digitalMap to a new function. It will be applied to the raw digital input value in Digital::refresh() +{ + digitalMap = fn; } \ No newline at end of file diff --git a/src/MIDI_Outputs/DigitalLatch.h b/src/MIDI_Outputs/DigitalLatch.h index 7e02448..55654dc 100755 --- a/src/MIDI_Outputs/DigitalLatch.h +++ b/src/MIDI_Outputs/DigitalLatch.h @@ -8,18 +8,30 @@ class DigitalLatch : public MIDI_Control_Element { public: - DigitalLatch(pin_t pin, uint8_t note, uint8_t channel, uint8_t velocity, unsigned long latchTime); // Constructor + DigitalLatch(pin_t pin, uint8_t msg, uint8_t note, uint8_t channel, uint8_t velocity = 127, unsigned long latchTime = 100); // Constructor ~DigitalLatch(); // Destructor + void push(); + void release(); + void invert(); // Invert the button state (send Note On event when released, Note Off when pressed) + void map(int (*fn)(int, int)); // Change the function pointer for digitalMap to a new function. It will be applied to the raw digital input value in DigitalLatch::refresh() -private: + private: void refresh(); // Check if the button state changed, if so, send a MIDI Note On, after a non-blocking delay of "latchTime", send a Note Off + int (*digitalMap)(int, int) = identity; // function pointer to identity function f(x) → x + static int identity(int p, int x) + { // identity function f(x) → x + return x; + } + pin_t pin; - uint8_t note, channel, velocity; + uint8_t msg, note, channel, velocity; bool oldState = HIGH; bool noteOffSent = true; unsigned long latchTime; unsigned long noteOnTime; + + bool invertState = false; }; #endif diff --git a/src/MIDI_Outputs/MIDI_Control_Element.h b/src/MIDI_Outputs/MIDI_Control_Element.h index 3614b3b..90d4b93 100644 --- a/src/MIDI_Outputs/MIDI_Control_Element.h +++ b/src/MIDI_Outputs/MIDI_Control_Element.h @@ -14,12 +14,14 @@ class MIDI_Control_Element { INSERT_INTO_LINKED_LIST(this, first, last); } - ~MIDI_Control_Element() // Destructor + virtual ~MIDI_Control_Element() // Destructor { DELETE_FROM_LINKED_LIST(this, first, last); } - - virtual void map(int (*fn)(int)) {} // Change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in Analog::refresh() and AnalogHiRes::refresh() + + virtual void push() {} + virtual void release() {} + virtual void map(int (*fn)(int, int)) {} // Change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in Analog::refresh() and AnalogHiRes::refresh() virtual void invert() {} // Invert the button state (send Note On event when released, Note Off when pressed). It will be applied in Digital::refresh() void setChannelOffset(uint8_t offset); // Set the channel offset @@ -49,4 +51,4 @@ class MIDI_Control_Element static MIDI_Control_Element *first; }; -#endif // MIDI_CONTROL_ELEMENT_h \ No newline at end of file +#endif // MIDI_CONTROL_ELEMENT_h