diff --git a/.editorconfig b/.editorconfig index 3c44241c..46834463 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,7 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true + +[*.yml] +indent_size = 2 +trim_trailing_whitespace = false diff --git a/.github/workflows/platformio.yml b/.github/workflows/platformio.yml index b4f4891e..d1716ac6 100644 --- a/.github/workflows/platformio.yml +++ b/.github/workflows/platformio.yml @@ -4,7 +4,7 @@ on: push: branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: platformio: @@ -25,38 +25,45 @@ jobs: - SimpleSynth - CustomBaudRate board: + # Arduino - uno - due - zero - leonardo - micro - nanoatmega328 + - nano_every + - nano33ble - megaatmega2560 + # Teensy - teensy2 - teensy30 - teensy31 - teensylc + # ESP-32 + - featheresp32 + - pico32 steps: - - uses: actions/checkout@v2 - - name: Cache pip - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: ${{ runner.os }}-pip- - - name: Cache PlatformIO - uses: actions/cache@v2 - with: - path: ~/.platformio - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - - name: Set up Python - uses: actions/setup-python@v2 - - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - pip install "click!=8.0.2" # See platformio/platformio-core#4078 - - name: Run PlatformIO - run: pio ci --lib="." --board="${{matrix.board}}" - env: - PLATFORMIO_CI_SRC: examples/${{ matrix.example }} + - uses: actions/checkout@v2 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: ${{ runner.os }}-pip- + - name: Cache PlatformIO + uses: actions/cache@v2 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + pip install "click!=8.0.2" # See platformio/platformio-core#4078 + - name: Run PlatformIO + run: pio ci --lib="." --board="${{matrix.board}}" --verbose + env: + PLATFORMIO_CI_SRC: examples/${{ matrix.example }} diff --git a/.vscode/settings.json b/.vscode/settings.json index fc1d2102..45e90381 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,68 @@ "string_view": "cpp", "vector": "cpp", "istream": "cpp", - "system_error": "cpp" + "system_error": "cpp", + "ios": "cpp", + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__hash_table": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__std_stream": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "exception": "cpp", + "coroutine": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "limits": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "optional": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "variant": "cpp", + "algorithm": "cpp" } -} \ No newline at end of file +} diff --git a/CMakeLists.txt b/CMakeLists.txt index a06c2e49..53f1d1a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ -cmake_minimum_required(VERSION 2.8.7) +cmake_minimum_required(VERSION 3.26.4) project(arduino_midi_library CXX) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + add_subdirectory(builder) setup_builder() diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index ce57cb0f..1e623243 100644 --- a/examples/AltPinSerial/AltPinSerial.ino +++ b/examples/AltPinSerial/AltPinSerial.ino @@ -5,7 +5,7 @@ // Here, when receiving any message on channel 4, the Arduino // will blink a led and play back a note for 1 second. -#if defined(ARDUINO_SAM_DUE) || defined(SAMD_SERIES) || defined(_VARIANT_ARDUINO_ZERO_) +#if defined(ARDUINO_SAM_DUE) || defined(SAMD_SERIES) || defined(_VARIANT_ARDUINO_ZERO_) || defined(ARDUINO_ARDUINO_NANO33BLE) || defined(ESP32) /* example not relevant for this hardware (SoftwareSerial not supported) */ MIDI_CREATE_DEFAULT_INSTANCE(); #else @@ -18,6 +18,11 @@ MIDI_NAMESPACE::MidiInterface MIDI((Transport&)serialMIDI); #endif +// Some boards don't have this set (ESP32) +#ifndef LED_BUILTIN +#define LED_BUILTIN 0 +#endif + void setup() { pinMode(LED_BUILTIN, OUTPUT); diff --git a/examples/Basic_IO/Basic_IO.ino b/examples/Basic_IO/Basic_IO.ino index 82097192..ce03e477 100644 --- a/examples/Basic_IO/Basic_IO.ino +++ b/examples/Basic_IO/Basic_IO.ino @@ -4,6 +4,11 @@ // Here, when receiving any message on channel 4, the Arduino // will blink a led and play back a note for 1 second. +// Some boards don't have this set (ESP32) +#ifndef LED_BUILTIN +#define LED_BUILTIN 0 +#endif + MIDI_CREATE_DEFAULT_INSTANCE(); void setup() diff --git a/examples/Bench/Bench.ino b/examples/Bench/Bench.ino index 75855c36..f1a04951 100644 --- a/examples/Bench/Bench.ino +++ b/examples/Bench/Bench.ino @@ -7,7 +7,7 @@ // All other Arduinos: Connect pins 2 and 3. // The program will then wait for 100 loops and print the results. -#if defined(ARDUINO_SAM_DUE) || defined(USBCON) +#if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(ARDUINO_ARDUINO_NANO33BLE) || defined(ESP32) // Print through USB and bench with Hardware serial MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiBench); #else diff --git a/examples/CustomBaudRate/CustomBaudRate.ino b/examples/CustomBaudRate/CustomBaudRate.ino index d554871d..cb076539 100644 --- a/examples/CustomBaudRate/CustomBaudRate.ino +++ b/examples/CustomBaudRate/CustomBaudRate.ino @@ -15,6 +15,11 @@ struct CustomBaudRateSettings : public MIDI_NAMESPACE::DefaultSerialSettings { MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); #endif +// Some boards don't have this set (ESP32) +#ifndef LED_BUILTIN +#define LED_BUILTIN 0 +#endif + void setup() { pinMode(LED_BUILTIN, OUTPUT); MIDI.begin(MIDI_CHANNEL_OMNI); diff --git a/examples/DualMerger/DualMerger.ino b/examples/DualMerger/DualMerger.ino index 4e99ea1a..3e54d758 100644 --- a/examples/DualMerger/DualMerger.ino +++ b/examples/DualMerger/DualMerger.ino @@ -6,7 +6,7 @@ // A out = A in + B in // B out = B in + A in -#if defined(ARDUINO_SAM_DUE) +#if defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARDUINO_NANO33BLE) || defined(ESP32) MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); #elif defined(ARDUINO_SAMD_ZERO) @@ -15,7 +15,7 @@ #elif defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) #include SoftwareSerial softSerial(2,3); - MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiA); + MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiA); MIDI_CREATE_INSTANCE(SoftwareSerial, softSerial, midiB); #else #include diff --git a/examples/ErrorCallback/ErrorCallback.ino b/examples/ErrorCallback/ErrorCallback.ino index e35cf81f..b6824d73 100644 --- a/examples/ErrorCallback/ErrorCallback.ino +++ b/examples/ErrorCallback/ErrorCallback.ino @@ -5,6 +5,11 @@ MIDI_CREATE_DEFAULT_INSTANCE(); +// Some boards don't have this set (ESP32) +#ifndef LED_BUILTIN +#define LED_BUILTIN 0 +#endif + void handleError(int8_t err) { digitalWrite(LED_BUILTIN, (err == 0)? LOW : HIGH); diff --git a/examples/Hairless/Hairless.ino b/examples/Hairless/Hairless.ino new file mode 100644 index 00000000..f03937de --- /dev/null +++ b/examples/Hairless/Hairless.ino @@ -0,0 +1,30 @@ +#include +USING_NAMESPACE_MIDI + +struct MySerialSettings : public MIDI_NAMESPACE::DefaultSerialSettings +{ + static const long BaudRate = 115200; +}; + +unsigned long t1 = millis(); + +MIDI_NAMESPACE::SerialMIDI serialMIDI(Serial1); +MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); + +void setup() +{ + MIDI.begin(1); +} + +void loop() +{ + MIDI.read(); + + // send a note every second + if ((millis() - t1) > 1000) + { + t1 = millis(); + + MIDI.sendNoteOn(random(1, 127), 55, 1); + } +} diff --git a/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino new file mode 100644 index 00000000..d93d2820 --- /dev/null +++ b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino @@ -0,0 +1,56 @@ +#include +USING_NAMESPACE_MIDI + +// Some boards don't have this set (ESP32) +#ifndef LED_BUILTIN +#define LED_BUILTIN 0 +#endif + +struct MyMIDISettings : public MIDI_NAMESPACE::DefaultSettings +{ + // When setting UseReceiverActiveSensing to true, MIDI.read() *must* be called + // as often as possible (1000 / SenderActiveSensingPeriodicity per second). + // + // setting UseReceiverActiveSensing to true, adds 174 bytes of code. + // + // (Taken from a Roland MIDI Implementation Owner's manual) + // Once an Active Sensing message is received, the unit will begin monitoring + // the interval between all subsequent messages. If there is an interval of 420 ms + // or longer between messages while monitoring is active, the same processing + // as when All Sound Off, All Notes Off,and Reset All Controllers messages are + // received will be carried out. The unit will then stopmonitoring the message interval. + + static const bool UseReceiverActiveSensing = true; + + static const uint16_t ReceiverActiveSensingTimeout = 420; +}; + +MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial1, MIDI, MyMIDISettings); + +void activeSensingTimeoutExceptionHandler(bool active) +{ + if (!active) + { + MIDI.sendControlChange(AllSoundOff, 0, 1); + MIDI.sendControlChange(AllNotesOff, 0, 1); + MIDI.sendControlChange(ResetAllControllers, 0, 1); + + digitalWrite(LED_BUILTIN, HIGH); + } + else + digitalWrite(LED_BUILTIN, LOW); +} + +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + + MIDI.setHandleActiveSensingTimeout(activeSensingTimeoutExceptionHandler); + MIDI.begin(1); +} + +void loop() +{ + MIDI.read(); +} diff --git a/examples/SenderActiveSensing/SenderActiveSensing.ino b/examples/SenderActiveSensing/SenderActiveSensing.ino new file mode 100644 index 00000000..c9c77182 --- /dev/null +++ b/examples/SenderActiveSensing/SenderActiveSensing.ino @@ -0,0 +1,58 @@ +#include +USING_NAMESPACE_MIDI + +struct MyMIDISettings : public MIDI_NAMESPACE::DefaultSettings +{ + // When setting UseSenderActiveSensing to true, MIDI.read() *must* be called + // as often as possible (1000 / SenderActiveSensingPeriodicity per second). + // + // setting UseSenderActiveSensing to true, adds 34 bytes of code. + // + // When using Active Sensing, call MIDI.read(); in the Arduino loop() + // + // from 'a' MIDI implementation manual: "Sent periodically" + // In the example here, a NoteOn is send every 1000ms (1s), ActiveSensing is + // send every 250ms after the last command. + // Logging the command will look like this: + // + // ... + // A.Sense FE + // A.Sense FE + // A.Sense FE + // NoteOn 90 04 37 [E-2] + // A.Sense FE + // A.Sense FE + // A.Sense FE + // NoteOn 90 04 37 [E-2] + // A.Sense FE + // A.Sense FE + // A.Sense FE + // NoteOn 90 04 37 [E-2] + // ... + + static const bool UseSenderActiveSensing = true; + + static const uint16_t SenderActiveSensingPeriodicity = 250; +}; + +unsigned long t1 = millis(); + +MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial1, MIDI, MyMIDISettings); + +void setup() +{ + MIDI.begin(1); +} + +void loop() +{ + MIDI.read(); + + // send a note every second + if ((millis() - t1) > 1000) + { + t1 = millis(); + + MIDI.sendNoteOn(random(1, 127), 55, 1); + } +} diff --git a/examples/ThruFilterMap/ThruFilterMap.ino b/examples/ThruFilterMap/ThruFilterMap.ino new file mode 100644 index 00000000..40c2f56a --- /dev/null +++ b/examples/ThruFilterMap/ThruFilterMap.ino @@ -0,0 +1,50 @@ +#include + +MIDI_CREATE_DEFAULT_INSTANCE(); + +/** + * This example shows how to make MIDI processors. + * + * The `filter` function defines whether to forward an incoming + * MIDI message to the output. + * + * The `map` function transforms the forwarded message before + * it is sent, allowing to change things. + * + * Here we will transform NoteOn messages into Program Change, + * allowing to use a keyboard to change patches on a MIDI device. + */ + +bool filter(const MIDIMessage& message) +{ + if (message.type == midi::NoteOn) + { + // Only forward NoteOn messages + return true; + } + return false; +} + +MIDIMessage map(const MIDIMessage& message) +{ + // Make a copy of the message + MIDIMessage output(message); + if (message.type == midi::NoteOn) + { + output.type = midi::ProgramChange; + output.data2 = 0; // Not needed in ProgramChange + } + return output; +} + +void setup() +{ + MIDI.begin(); + MIDI.setThruFilter(filter); + MIDI.setThruMap(map); +} + +void loop() +{ + MIDI.read(); +} diff --git a/external/google-test b/external/google-test index 703bd9ca..b796f7d4 160000 --- a/external/google-test +++ b/external/google-test @@ -1 +1 @@ -Subproject commit 703bd9caab50b139428cea1aaff9974ebee5742e +Subproject commit b796f7d44681514f58a683a3a71ff17c94edb0c1 diff --git a/keywords.txt b/keywords.txt index 845d7987..0a355fda 100644 --- a/keywords.txt +++ b/keywords.txt @@ -55,14 +55,12 @@ getData1 KEYWORD2 getData2 KEYWORD2 getSysExArray KEYWORD2 getSysExArrayLength KEYWORD2 -getFilterMode KEYWORD2 getThruState KEYWORD2 getInputChannel KEYWORD2 check KEYWORD2 setInputChannel KEYWORD2 turnThruOn KEYWORD2 turnThruOff KEYWORD2 -setThruFilterMode KEYWORD2 disconnectCallbackFromType KEYWORD2 setHandleNoteOff KEYWORD2 setHandleNoteOn KEYWORD2 diff --git a/src/MIDI.h b/src/MIDI.h index 767feb9c..5bc2bc5d 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -212,6 +212,7 @@ class MidiInterface void (*mMessageCallback)(const MidiMessage& message) = nullptr; ErrorCallback mErrorCallback = nullptr; + ActiveSensingTimeoutCallback mActiveSensingTimeoutCallback = nullptr; NoteOffCallback mNoteOffCallback = nullptr; NoteOnCallback mNoteOnCallback = nullptr; AfterTouchPolyCallback mAfterTouchPolyCallback = nullptr; @@ -236,15 +237,28 @@ class MidiInterface // MIDI Soft Thru public: - inline Thru::Mode getFilterMode() const; - inline bool getThruState() const; - - inline MidiInterface& turnThruOn(Thru::Mode inThruFilterMode = Thru::Full); + using ThruFilterCallback = bool (*)(const MidiMessage& inMessage); + using ThruMapCallback = MidiMessage (*)(const MidiMessage& inMessage); + inline MidiInterface& turnThruOn(ThruFilterCallback fptr = thruOn); inline MidiInterface& turnThruOff(); - inline MidiInterface& setThruFilterMode(Thru::Mode inThruFilterMode); + inline MidiInterface& setThruFilter(ThruFilterCallback fptr) + { + mThruFilterCallback = fptr; + return *this; + } + inline MidiInterface& setThruMap(ThruMapCallback fptr) + { + mThruMapCallback = fptr; + return *this; + } private: - void thruFilter(byte inChannel); + void processThru(); + static inline bool thruOn(const MidiMessage& inMessage) { (void)inMessage; return true; } + static inline bool thruOff(const MidiMessage& inMessage) { (void)inMessage; return false; } + static inline MidiMessage thruEcho(const MidiMessage& inMessage) { return inMessage; } + ThruFilterCallback mThruFilterCallback; + ThruMapCallback mThruMapCallback; // ------------------------------------------------------------------------- // MIDI Parsing @@ -277,13 +291,11 @@ class MidiInterface unsigned mPendingMessageIndex; unsigned mCurrentRpnNumber; unsigned mCurrentNrpnNumber; - bool mThruActivated : 1; - Thru::Mode mThruFilterMode : 7; MidiMessage mMessage; unsigned long mLastMessageSentTime; unsigned long mLastMessageReceivedTime; unsigned long mSenderActiveSensingPeriodicity; - bool mReceiverActiveSensingActivated; + bool mReceiverActiveSensingActive; int8_t mLastError; private: diff --git a/src/MIDI.hpp b/src/MIDI.hpp index be7c28a1..610e1441 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -40,15 +40,13 @@ inline MidiInterface::MidiInterface(Transport& in , mPendingMessageIndex(0) , mCurrentRpnNumber(0xffff) , mCurrentNrpnNumber(0xffff) - , mThruActivated(true) - , mThruFilterMode(Thru::Full) , mLastMessageSentTime(0) , mLastMessageReceivedTime(0) - , mSenderActiveSensingPeriodicity(0) - , mReceiverActiveSensingActivated(false) + , mSenderActiveSensingPeriodicity(Settings::SenderActiveSensingPeriodicity) + , mReceiverActiveSensingActive(false) , mLastError(0) { - mSenderActiveSensingPeriodicity = Settings::SenderActiveSensingPeriodicity; + static_assert(!(Settings::UseSenderActiveSensing && Settings::UseReceiverActiveSensing), "UseSenderActiveSensing and UseReceiverActiveSensing can't be both set to true."); } /*! \brief Destructor for MidiInterface. @@ -84,7 +82,8 @@ MidiInterface& MidiInterface& MidiInterface& MidiInterface& MidiInterface& MidiInterface inline bool MidiInterface::read(Channel inChannel) { #ifndef RegionActiveSending + // Active Sensing. This message is intended to be sent // repeatedly to tell the receiver that a connection is alive. Use - // of this message is optional. When initially received, the - // receiver will expect to receive another Active Sensing - // message each 300ms (max), and if it does not then it will - // assume that the connection has been terminated. At - // termination, the receiver will turn off all voices and return to - // normal (non- active sensing) operation. - if (Settings::UseSenderActiveSensing && (mSenderActiveSensingPeriodicity > 0) && (Platform::now() - mLastMessageSentTime) > mSenderActiveSensingPeriodicity) + // of this message is optional. + if (Settings::UseSenderActiveSensing) { - sendActiveSensing(); - mLastMessageSentTime = Platform::now(); + // Send ActiveSensing ms after the last command + if ((Platform::now() - mLastMessageSentTime) > Settings::SenderActiveSensingPeriodicity) + sendActiveSensing(); } - if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated && (mLastMessageReceivedTime + ActiveSensingTimeout < Platform::now())) + // Once an Active Sensing message is received, the unit will begin monitoring + // the intervalbetween all subsequent messages. If there is an interval of 420 ms + // or longer betweenmessages while monitoring is active, the same processing + // as when All Sound Off, All Notes Off,and Reset All Controllers messages are + // received will be carried out. The unit will then stopmonitoring the message interval. + if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActive) { - mReceiverActiveSensingActivated = false; + if ((Platform::now() - mLastMessageReceivedTime > Settings::ReceiverActiveSensingTimeout)) + { + mReceiverActiveSensingActive = false; - mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit - if (mErrorCallback) - mErrorCallback(mLastError); + // its up to the handler to send the stop processing messages + // (also, no clue what the channel is on which to send them) + mActiveSensingTimeoutCallback(mReceiverActiveSensingActive); + } } #endif @@ -792,25 +799,18 @@ inline bool MidiInterface::read(Channel inChannel #ifndef RegionActiveSending - if (Settings::UseReceiverActiveSensing && mMessage.type == ActiveSensing) + if (Settings::UseReceiverActiveSensing) { - // When an ActiveSensing message is received, the time keeping is activated. - // When a timeout occurs, an error message is send and time keeping ends. - mReceiverActiveSensingActivated = true; + mLastMessageReceivedTime = Platform::now(); - // is ErrorActiveSensingTimeout bit in mLastError on - if (mLastError & (1 << (ErrorActiveSensingTimeout - 1))) + if (mMessage.type == ActiveSensing && !mReceiverActiveSensingActive) { - mLastError &= ~(1UL << ErrorActiveSensingTimeout); // clear the ErrorActiveSensingTimeout bit - if (mErrorCallback) - mErrorCallback(mLastError); + mReceiverActiveSensingActive = true; + + mActiveSensingTimeoutCallback(mReceiverActiveSensingActive); } } - // Keep the time of the last received message, so we can check for the timeout - if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated) - mLastMessageReceivedTime = Platform::now(); - #endif handleNullVelocityNoteOnAsNoteOff(); @@ -819,7 +819,7 @@ inline bool MidiInterface::read(Channel inChannel if (channelMatch) launchCallback(); - thruFilter(inChannel); + processThru(); return channelMatch; } @@ -1399,51 +1399,24 @@ void MidiInterface::launchCallback() @{ */ -/*! \brief Set the filter for thru mirroring - \param inThruFilterMode a filter mode - - @see Thru::Mode - */ -template -inline MidiInterface& MidiInterface::setThruFilterMode(Thru::Mode inThruFilterMode) -{ - mThruFilterMode = inThruFilterMode; - mThruActivated = mThruFilterMode != Thru::Off; - - return *this; -} - -template -inline Thru::Mode MidiInterface::getFilterMode() const -{ - return mThruFilterMode; -} - template -inline bool MidiInterface::getThruState() const +inline MidiInterface& MidiInterface::turnThruOn(ThruFilterCallback fptr) { - return mThruActivated; -} - -template -inline MidiInterface& MidiInterface::turnThruOn(Thru::Mode inThruFilterMode) -{ - mThruActivated = true; - mThruFilterMode = inThruFilterMode; - + mThruFilterCallback = fptr; return *this; } template inline MidiInterface& MidiInterface::turnThruOff() { - mThruActivated = false; - mThruFilterMode = Thru::Off; - + mThruFilterCallback = thruOff; + if (Settings::UseSenderActiveSensing) + { + mLastMessageSentTime = Platform::now(); + } return *this; } - /*! @} */ // End of doc group MIDI Thru // This method is called upon reception of a message @@ -1453,56 +1426,25 @@ inline MidiInterface& MidiInterface -void MidiInterface::thruFilter(Channel inChannel) +void MidiInterface::processThru() { - // If the feature is disabled, don't do anything. - if (!mThruActivated || (mThruFilterMode == Thru::Off)) - return; + if (!Transport::thruActivated || !mThruFilterCallback(mMessage)) + return; + + MidiMessage thruMessage = mThruMapCallback(mMessage); // First, check if the received message is Channel - if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + if (thruMessage.type >= NoteOff && thruMessage.type <= PitchBend) { - const bool filter_condition = ((mMessage.channel == inChannel) || - (inChannel == MIDI_CHANNEL_OMNI)); - - // Now let's pass it to the output - switch (mThruFilterMode) - { - case Thru::Full: - send(mMessage.type, - mMessage.data1, - mMessage.data2, - mMessage.channel); - break; - - case Thru::SameChannel: - if (filter_condition) - { - send(mMessage.type, - mMessage.data1, - mMessage.data2, - mMessage.channel); - } - break; - - case Thru::DifferentChannel: - if (!filter_condition) - { - send(mMessage.type, - mMessage.data1, - mMessage.data2, - mMessage.channel); - } - break; - - default: - break; - } + send(thruMessage.type, + thruMessage.data1, + thruMessage.data2, + thruMessage.channel); } else { // Send the message to the output - switch (mMessage.type) + switch (thruMessage.type) { // Real Time and 1 byte case Clock: @@ -1512,24 +1454,24 @@ void MidiInterface::thruFilter(Channel inChannel) case ActiveSensing: case SystemReset: case TuneRequest: - sendRealTime(mMessage.type); + sendRealTime(thruMessage.type); break; case SystemExclusive: // Send SysEx (0xf0 and 0xf7 are included in the buffer) - sendSysEx(getSysExArrayLength(), getSysExArray(), true); + sendSysEx(thruMessage.getSysExSize(), thruMessage.sysexArray, true); break; case SongSelect: - sendSongSelect(mMessage.data1); + sendSongSelect(thruMessage.data1); break; case SongPosition: - sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2 << 7)); + sendSongPosition(thruMessage.data1 | ((unsigned)thruMessage.data2 << 7)); break; case TimeCodeQuarterFrame: - sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); + sendTimeCodeQuarterFrame(thruMessage.data1,thruMessage.data2); break; default: diff --git a/src/midi_Defs.h b/src/midi_Defs.h index 1da019d8..409407bd 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -46,28 +46,23 @@ BEGIN_MIDI_NAMESPACE #define MIDI_PITCHBEND_MIN -8192 #define MIDI_PITCHBEND_MAX 8191 -/*! Receiving Active Sensing -*/ -static const uint16_t ActiveSensingTimeout = 300; - // ----------------------------------------------------------------------------- // Type definitions typedef byte StatusByte; typedef byte DataByte; typedef byte Channel; -typedef byte FilterMode; // ----------------------------------------------------------------------------- // Errors static const uint8_t ErrorParse = 0; -static const uint8_t ErrorActiveSensingTimeout = 1; static const uint8_t WarningSplitSysEx = 2; // ----------------------------------------------------------------------------- // Aliasing using ErrorCallback = void (*)(int8_t); +using ActiveSensingTimeoutCallback = void (*)(bool); using NoteOffCallback = void (*)(Channel channel, byte note, byte velocity); using NoteOnCallback = void (*)(Channel channel, byte note, byte velocity); using AfterTouchPolyCallback = void (*)(Channel channel, byte note, byte velocity); @@ -123,20 +118,6 @@ enum MidiType: uint8_t // ----------------------------------------------------------------------------- -/*! Enumeration of Thru filter modes */ -struct Thru -{ - enum Mode - { - Off = 0, ///< Thru disabled (nothing passes through). - Full = 1, ///< Fully enabled Thru (every incoming message is sent back). - SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. - DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back. - }; -}; - -// ----------------------------------------------------------------------------- - /*! \brief Enumeration of Control Change command numbers. See the detailed controllers numbers & description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums diff --git a/src/midi_Settings.h b/src/midi_Settings.h index 179b773e..6c40922c 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -72,17 +72,14 @@ struct DefaultSettings */ static const unsigned SysExMaxSize = 128; - /*! Global switch to turn on/off sender ActiveSensing - Set to true to send ActiveSensing - Set to false will not send ActiveSensing message (will also save memory) - */ - static const bool UseSenderActiveSensing = false; + /*! Global switch to turn on/off sending and receiving ActiveSensing + Set to true to activate ActiveSensing + Set to false will not send/receive ActiveSensing message (will also save 236 bytes of memory) - /*! Global switch to turn on/off receiver ActiveSensing - Set to true to check for message timeouts (via ErrorCallback) - Set to false will not check if chained device are still alive (if they use ActiveSensing) (will also save memory) + When setting UseActiveSensing to true, MIDI.read() *must* be called + as often as possible (1000 / ActiveSensingPeriodicity per second). */ - static const bool UseReceiverActiveSensing = false; + static const bool UseSenderActiveSensing = false; /*! Active Sensing is intended to be sent repeatedly by the sender to tell the receiver that a connection is alive. Use @@ -94,11 +91,20 @@ struct DefaultSettings normal (non- active sensing) operation. Typical value is 250 (ms) - an Active Sensing command is send every 250ms. - (All Roland devices send Active Sensing every 250ms) + (Most Roland devices send Active Sensing every 250ms) + */ + static const uint16_t SenderActiveSensingPeriodicity = 300; - Setting this field to 0 will disable sending MIDI active sensing. + /*! Once an Active Sensing message is received, the unit will begin monitoring + the intervalbetween all subsequent messages. If there is an interval of ActiveSensingPeriodicity ms + or longer betweenmessages while monitoring is active, the same processing + as when All Sound Off, All Notes Off,and Reset All Controllers messages are + received will be carried out. The unit will then stopmonitoring the message interval. */ - static const uint16_t SenderActiveSensingPeriodicity = 0; + static const bool UseReceiverActiveSensing = false; + + static const uint16_t ReceiverActiveSensingTimeout = 300; + }; END_MIDI_NAMESPACE diff --git a/src/serialMIDI.h b/src/serialMIDI.h index e69e9b2d..2bb673ed 100644 --- a/src/serialMIDI.h +++ b/src/serialMIDI.h @@ -52,7 +52,7 @@ class SerialMIDI public: static const bool thruActivated = true; - + void begin() { // Initialise the Serial port @@ -103,9 +103,12 @@ END_MIDI_NAMESPACE Example: MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, midi2); Then call midi2.begin(), midi2.read() etc.. */ -#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \ - MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ - MIDI_NAMESPACE::MidiInterface> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); +#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \ + using Name##SerialTransport = MIDI_NAMESPACE::SerialMIDI; \ + using Name##Interface = MIDI_NAMESPACE::MidiInterface; \ + using Name##Message = Name##Interface::MidiMessage; \ + Name##SerialTransport serial##Name(SerialPort); \ + Name##Interface Name((Name##SerialTransport&)serial##Name); #if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) // Leonardo, Due and other USB boards use Serial1 by default. @@ -121,10 +124,11 @@ END_MIDI_NAMESPACE #endif /*! \brief Create an instance of the library attached to a serial port with - custom settings. + custom MIDI settings (not to be confused with modified Serial Settings, like BaudRate) @see DefaultSettings @see MIDI_CREATE_INSTANCE */ #define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \ MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ MIDI_NAMESPACE::MidiInterface, Settings> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c3d5866f..d598098a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(mocks) add_subdirectory(unit-tests) +add_subdirectory(analyzer) diff --git a/test/analyzer/CMakeLists.txt b/test/analyzer/CMakeLists.txt new file mode 100644 index 00000000..cac11b15 --- /dev/null +++ b/test/analyzer/CMakeLists.txt @@ -0,0 +1,22 @@ +include(CMakeToolsHelpers OPTIONAL) + +project(analyzer) + +include_directories( + "${analyzer_SOURCE_DIR}" +) + +add_executable(analyzer + analyzer.cpp + analyzer.h + analyzer_Namespace.h + + analyzer_MIDI.cpp + analyzer_MIDI.h +) + +target_link_libraries(analyzer + midi + test-mocks +) + diff --git a/test/analyzer/analyzer.cpp b/test/analyzer/analyzer.cpp new file mode 100644 index 00000000..fb248a6c --- /dev/null +++ b/test/analyzer/analyzer.cpp @@ -0,0 +1,25 @@ +#include "analyzer.h" +#include "analyzer_MIDI.h" +#include +#include +#include +#include +#include +#include +#include + +USING_NAMESPACE_ANALYZER + +int main(int, char**) { + MIDIAnalyzer analyzer; + analyzer.setup(); + + std::ifstream csvFile("../bytes.csv"); + std::cout << "File open" << std::endl; + std::string line = ""; + while (std::getline(csvFile, line)) + { + uint8_t n = std::stoi(line, nullptr, 16); + analyzer.process(n); + } +} diff --git a/test/analyzer/analyzer.h b/test/analyzer/analyzer.h new file mode 100644 index 00000000..c1b667ea --- /dev/null +++ b/test/analyzer/analyzer.h @@ -0,0 +1,7 @@ +#pragma once + +#include "analyzer_Namespace.h" + +BEGIN_ANALYZER_NAMESPACE + +END_ANALYZER_NAMESPACE diff --git a/test/analyzer/analyzer_MIDI.cpp b/test/analyzer/analyzer_MIDI.cpp new file mode 100644 index 00000000..f5881d79 --- /dev/null +++ b/test/analyzer/analyzer_MIDI.cpp @@ -0,0 +1,81 @@ +#include "analyzer_MIDI.h" +#include + +BEGIN_ANALYZER_NAMESPACE + +void handleNoteOn(byte inChannel, byte inPitch, byte inVelocity) +{ + std::cout << "NoteOn Ch " << int(inChannel) << " Pitch " << int(inPitch) << " Vel " << int(inVelocity); + if (inPitch > 127) { + std::cout << "--------------- Pitch greater than 127 detected "; + } + if (inVelocity > 127) { + std::cout << "--------------- Velocity greater than 127 detected "; + } + std::cout << std::endl; +} +void handleNoteOff(byte inChannel, byte inPitch, byte inVelocity) +{ + std::cout << "NoteOff Ch " << int(inChannel) << " Pitch " << int(inPitch) << " Vel " << int(inVelocity); + if (inPitch > 127) { + std::cout << "--------------- Pitch greater than 127 detected "; + } + if (inVelocity > 127) { + std::cout << "--------------- Velocity greater than 127 detected "; + } + std::cout << std::endl; +} +void handleControlChange(byte inChannel, byte inControl, byte inValue) +{ + std::cout << "ControlChange Ch " << int(inChannel) << " Cntrl " << int(inControl) << " Val " << int(inValue); + if (inControl > 127) { + std::cout << "--------------- Control greater than 127 detected "; + } + if (inValue > 127) { + std::cout << "--------------- Value greater than 127 detected "; + } + std::cout << std::endl; +} +void handleProgramChange(byte inChannel, byte inProgram) +{ + std::cout << "ProgramChange Ch " << int(inChannel) << " Progm " << int(inProgram); + if (inProgram > 127) { + std::cout << "--------------- Program greater than 127 detected "; + } + std::cout << std::endl; +} +void handleChannelPressure(byte inChannel, byte inPressure) +{ + std::cout << "AftertouchChannel Ch " << int(inChannel) << " Press " << int(inPressure); + if (inPressure > 127) { + std::cout << "--------------- Pressure greater than 127 detected "; + } + std::cout << std::endl; +} + +MIDIAnalyzer::MIDIAnalyzer() + : mSerialBuffer() + , mTransport(mSerialBuffer) + , mMIDI((Transport&)mTransport) +{ +} + +void MIDIAnalyzer::setup() +{ + mMIDI.begin(MIDI_CHANNEL_OMNI); + mMIDI.turnThruOff(); + mMIDI.setHandleNoteOn(handleNoteOn); + mMIDI.setHandleNoteOff(handleNoteOff); + mMIDI.setHandleControlChange(handleControlChange); + mMIDI.setHandleProgramChange(handleProgramChange); + mMIDI.setHandleAfterTouchChannel(handleChannelPressure); +} + +void MIDIAnalyzer::process(uint8_t inByte) +{ + std::cout << "Processing byte " << std::hex < +#include + +BEGIN_ANALYZER_NAMESPACE + +using SerialMock = test_mocks::SerialMock<32>; +using Transport = midi::SerialMIDI; +using MidiInterface = midi::MidiInterface; + +struct MIDIAnalyzer { +public: + MIDIAnalyzer(); + +public: + void setup(); + void process(uint8_t inByte); + +public: + SerialMock mSerialBuffer; + Transport mTransport; + MidiInterface mMIDI; +}; + +END_ANALYZER_NAMESPACE diff --git a/test/analyzer/analyzer_Namespace.h b/test/analyzer/analyzer_Namespace.h new file mode 100644 index 00000000..fd6769d5 --- /dev/null +++ b/test/analyzer/analyzer_Namespace.h @@ -0,0 +1,13 @@ +#pragma once + +#define ANALYZER_NAMESPACE analyzer +#define BEGIN_ANALYZER_NAMESPACE namespace ANALYZER_NAMESPACE { +#define END_ANALYZER_NAMESPACE } +#define BEGIN_UNNAMED_NAMESPACE namespace { +#define END_UNNAMED_NAMESPACE } + +#define USING_NAMESPACE_ANALYZER using namespace ANALYZER_NAMESPACE; + +BEGIN_ANALYZER_NAMESPACE + +END_ANALYZER_NAMESPACE diff --git a/test/mocks/test-mocks_SerialMock.h b/test/mocks/test-mocks_SerialMock.h index b18ebb99..4a125070 100644 --- a/test/mocks/test-mocks_SerialMock.h +++ b/test/mocks/test-mocks_SerialMock.h @@ -2,6 +2,7 @@ #include "test-mocks.h" #include +#include BEGIN_TEST_MOCKS_NAMESPACE diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index eaf1f3fd..d98ac8e4 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -23,6 +23,8 @@ add_executable(unit-tests tests/unit-tests_MidiThru.cpp ) +set_source_files_properties(tests/unit-tests_MidiThru.cpp PROPERTIES COMPILE_FLAGS -Wno-shadow) + target_link_libraries(unit-tests gtest gmock diff --git a/test/unit-tests/tests/unit-tests_MidiOutput.cpp b/test/unit-tests/tests/unit-tests_MidiOutput.cpp index c243966b..ffe55919 100644 --- a/test/unit-tests/tests/unit-tests_MidiOutput.cpp +++ b/test/unit-tests/tests/unit-tests_MidiOutput.cpp @@ -63,7 +63,7 @@ TEST(MidiOutput, sendGenericWithRunningStatus) SerialMock serial; Transport transport(serial); RsMidiInterface midi((Transport&)transport); - + Buffer buffer; buffer.resize(5); @@ -85,7 +85,7 @@ TEST(MidiOutput, sendGenericWithoutRunningStatus) SerialMock serial; Transport transport(serial); NoRsMidiInterface midi((Transport&)transport); - + Buffer buffer; buffer.resize(6); @@ -336,7 +336,7 @@ TEST(MidiOutput, sendSysEx) LargeSerialMock serial; LargeTransport transport(serial); LargeMidiInterface midi((LargeTransport&)transport); - + Buffer buffer; // Short frame @@ -547,6 +547,41 @@ TEST(MidiOutput, sendRealTime) } } +TEST(MidiOutput, sendCommon) +{ + SerialMock serial; + Transport transport(serial); + MidiInterface midi((Transport&)transport); + + Buffer buffer; + + // Test valid Common messages + { + buffer.clear(); + buffer.resize(8); + + midi.begin(); + midi.sendCommon(midi::TimeCodeQuarterFrame, 1); + midi.sendCommon(midi::SongPosition, 0x5555); + midi.sendCommon(midi::SongSelect, 3); + midi.sendCommon(midi::TuneRequest, 4); + + EXPECT_EQ(serial.mTxBuffer.getLength(), 8); + serial.mTxBuffer.read(&buffer[0], 8); + EXPECT_THAT(buffer, ElementsAreArray({ + 0xf1, 0x01, 0xf2, 0x55, 0x2a, 0xf3, 0x03, 0xf6 + })); + } + // Test invalid messages + { + midi.begin(); + midi.sendCommon(midi::Undefined_F4, 0); + midi.sendCommon(midi::Undefined_F5, 0); + midi.sendCommon(midi::InvalidType, 0); + EXPECT_EQ(serial.mTxBuffer.getLength(), 0); + } +} + TEST(MidiOutput, RPN) { typedef VariableSettings Settings; @@ -555,7 +590,7 @@ TEST(MidiOutput, RPN) SerialMock serial; Transport transport(serial); RsMidiInterface midi((Transport&)transport); - + Buffer buffer; // 14-bit Value Single Frame @@ -673,7 +708,7 @@ TEST(MidiOutput, NRPN) SerialMock serial; Transport transport(serial); RsMidiInterface midi((Transport&)transport); - + Buffer buffer; // 14-bit Value Single Frame @@ -791,7 +826,7 @@ TEST(MidiOutput, runningStatusCancellation) SerialMock serial; Transport transport(serial); RsMidiInterface midi((Transport&)transport); - + Buffer buffer; static const unsigned sysExLength = 13; diff --git a/test/unit-tests/tests/unit-tests_MidiThru.cpp b/test/unit-tests/tests/unit-tests_MidiThru.cpp index dc0c0c1b..b89c262b 100644 --- a/test/unit-tests/tests/unit-tests_MidiThru.cpp +++ b/test/unit-tests/tests/unit-tests_MidiThru.cpp @@ -17,6 +17,7 @@ typedef test_mocks::SerialMock<32> SerialMock; typedef midi::SerialMIDI Transport; typedef midi::MidiInterface MidiInterface; typedef std::vector Buffer; +typedef midi::Message MidiMessage; template struct VariableSysExSettings : midi::DefaultSettings @@ -24,75 +25,42 @@ struct VariableSysExSettings : midi::DefaultSettings static const unsigned SysExMaxSize = Size; }; -// ----------------------------------------------------------------------------- +SerialMock serial; +Transport transport(serial); +MidiInterface midi((Transport&)transport); -TEST(MidiThru, defaultValues) +bool thruFilterSameChannel(const MidiMessage& inMessage) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - - EXPECT_EQ(midi.getThruState(), true); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); - midi.begin(); // Should not change the state - EXPECT_EQ(midi.getThruState(), true); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); + if (!midi.isChannelMessage(inMessage.type)) + return true; + + return MIDI_CHANNEL_OMNI == midi.getInputChannel() || + inMessage.channel == midi.getInputChannel(); } -TEST(MidiThru, beginEnablesThru) +bool thruFilterDifferentChannel(const MidiMessage& inMessage) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); + if (!midi.isChannelMessage(inMessage.type)) + return true; - midi.turnThruOff(); - EXPECT_EQ(midi.getThruState(), false); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::Off); - midi.begin(); - EXPECT_EQ(midi.getThruState(), true); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); + return MIDI_CHANNEL_OMNI != midi.getInputChannel() && + inMessage.channel != midi.getInputChannel(); } -TEST(MidiThru, setGet) +MidiMessage thruMapNoteOnFullVelocity(const MidiMessage& inMessage) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); + if (inMessage.type != midi::MidiType::NoteOn) + return inMessage; - midi.turnThruOff(); - EXPECT_EQ(midi.getThruState(), false); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::Off); - - midi.turnThruOn(); - EXPECT_EQ(midi.getThruState(), true); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); - midi.turnThruOn(midi::Thru::SameChannel); - EXPECT_EQ(midi.getThruState(), true); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::SameChannel); - midi.turnThruOn(midi::Thru::DifferentChannel); - EXPECT_EQ(midi.getThruState(), true); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::DifferentChannel); - - midi.setThruFilterMode(midi::Thru::Full); - EXPECT_EQ(midi.getThruState(), true); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); - midi.setThruFilterMode(midi::Thru::SameChannel); - EXPECT_EQ(midi.getThruState(), true); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::SameChannel); - midi.setThruFilterMode(midi::Thru::DifferentChannel); - EXPECT_EQ(midi.getThruState(), true); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::DifferentChannel); - midi.setThruFilterMode(midi::Thru::Off); - EXPECT_EQ(midi.getThruState(), false); - EXPECT_EQ(midi.getFilterMode(), midi::Thru::Off); + MidiMessage modified = inMessage; + modified.data2 = 127; + return modified; } +// ----------------------------------------------------------------------------- + TEST(MidiThru, off) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - midi.begin(MIDI_CHANNEL_OMNI); midi.turnThruOff(); @@ -110,14 +78,9 @@ TEST(MidiThru, off) TEST(MidiThru, full) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); - midi.setThruFilterMode(midi::Thru::Full); static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; @@ -154,14 +117,10 @@ TEST(MidiThru, full) TEST(MidiThru, sameChannel) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - Buffer buffer; midi.begin(12); - midi.setThruFilterMode(midi::Thru::SameChannel); + midi.setThruFilter(thruFilterSameChannel); static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; @@ -185,14 +144,10 @@ TEST(MidiThru, sameChannel) TEST(MidiThru, sameChannelOmni) // Acts like full { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); - midi.setThruFilterMode(midi::Thru::SameChannel); + midi.setThruFilter(thruFilterSameChannel); static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; @@ -229,14 +184,10 @@ TEST(MidiThru, sameChannelOmni) // Acts like full TEST(MidiThru, differentChannel) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - Buffer buffer; midi.begin(12); - midi.setThruFilterMode(midi::Thru::DifferentChannel); + midi.setThruFilter(thruFilterDifferentChannel); static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; @@ -260,14 +211,10 @@ TEST(MidiThru, differentChannel) TEST(MidiThru, differentChannelOmni) // Acts like off { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); - midi.setThruFilterMode(midi::Thru::DifferentChannel); + midi.setThruFilter(thruFilterDifferentChannel); static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; @@ -293,14 +240,11 @@ TEST(MidiThru, multiByteThru) typedef VariableSettings MultiByteParsing; typedef midi::MidiInterface MultiByteMidiInterface; - SerialMock serial; - Transport transport(serial); MultiByteMidiInterface midi((Transport&)transport); Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); - midi.setThruFilterMode(midi::Thru::Full); static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0x9b, 12, 34, 56, 78 }; @@ -324,14 +268,11 @@ TEST(MidiThru, withTxRunningStatus) typedef VariableSettings Settings; typedef midi::MidiInterface RsMidiInterface; - SerialMock serial; - Transport transport(serial); RsMidiInterface midi((Transport&)transport); Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); - midi.setThruFilterMode(midi::Thru::Full); static const unsigned rxSize = 5; static const byte rxData[rxSize] = { 0x9b, 12, 34, 56, 78 }; @@ -364,26 +305,52 @@ TEST(MidiThru, withTxRunningStatus) })); } -TEST(MidiThru, invalidMode) +TEST(MidiThru, mapNoteOnFullVelocity) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); + Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); - midi.setThruFilterMode(midi::Thru::Mode(42)); + midi.setThruMap(thruMapNoteOnFullVelocity); static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; serial.mRxBuffer.write(rxData, rxSize); + EXPECT_EQ(midi.read(), false); + EXPECT_EQ(serial.mTxBuffer.getLength(), 0); EXPECT_EQ(midi.read(), false); + EXPECT_EQ(serial.mTxBuffer.getLength(), 0); EXPECT_EQ(midi.read(), true); + + buffer.clear(); + buffer.resize(3); + EXPECT_EQ(serial.mTxBuffer.getLength(), 3); + serial.mTxBuffer.read(&buffer[0], 3); + EXPECT_THAT(buffer, ElementsAreArray({ + 0x9b, 12, 127 // thru message full velocity + })); + EXPECT_EQ(midi.getType(), midi::NoteOn); + EXPECT_EQ(midi.getChannel(), 12); + EXPECT_EQ(midi.getData1(), 12); + EXPECT_EQ(midi.getData2(), 34); // mMessage velocity unchanged + EXPECT_EQ(midi.read(), false); + EXPECT_EQ(serial.mTxBuffer.getLength(), 0); EXPECT_EQ(midi.read(), false); + EXPECT_EQ(serial.mTxBuffer.getLength(), 0); EXPECT_EQ(midi.read(), true); - EXPECT_EQ(serial.mTxBuffer.getLength(), 0); + buffer.clear(); + buffer.resize(3); + EXPECT_EQ(serial.mTxBuffer.getLength(), 3); + serial.mTxBuffer.read(&buffer[0], 3); + EXPECT_THAT(buffer, ElementsAreArray({ + 0x9c, 56, 127 // thru message full velocity + })); + EXPECT_EQ(midi.getType(), midi::NoteOn); + EXPECT_EQ(midi.getChannel(), 13); + EXPECT_EQ(midi.getData1(), 56); + EXPECT_EQ(midi.getData2(), 78); // mMessage velocity unchanged } END_UNNAMED_NAMESPACE