From a6c3a485718e87e3087066526a5ba696c3df7940 Mon Sep 17 00:00:00 2001 From: Eric Sherman Date: Thu, 5 Aug 2021 20:38:25 -0400 Subject: [PATCH 01/26] thru filter overhaul * resolves #40 with franky47's proposed thru filter overhaul * removes thru filter modes * adds thru filter callback * adds thru map callback * old thru filter unit tests have been replicated with filter callbacks * does not yet include documentation changes I believe this implements the latest proposal for #40 and exercises everything necessary in the unit tests, including the immutability of `mMessage` after a thru map callback has modified the outgoing message. The thru filter callbacks in the unit tests are not suitable for copying and pasting by end-users due to the difference in the MIDI namespace when setup via the unit tests vs via `MIDI_CREATE_DEFAULT_INSTANCE()`. If the changes here are deemed suitable, I'll work on documentation. --- keywords.txt | 2 - src/MIDI.h | 25 ++- src/MIDI.hpp | 99 +++--------- src/midi_Defs.h | 15 -- test/unit-tests/CMakeLists.txt | 2 + test/unit-tests/tests/unit-tests_MidiThru.cpp | 147 ++++++++---------- 6 files changed, 110 insertions(+), 180 deletions(-) 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..7d4d64b4 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -236,15 +236,30 @@ 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 thruFilter(); + 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,8 +292,6 @@ class MidiInterface unsigned mPendingMessageIndex; unsigned mCurrentRpnNumber; unsigned mCurrentNrpnNumber; - bool mThruActivated : 1; - Thru::Mode mThruFilterMode : 7; MidiMessage mMessage; unsigned long mLastMessageSentTime; unsigned long mLastMessageReceivedTime; diff --git a/src/MIDI.hpp b/src/MIDI.hpp index be7c28a1..db3afd5b 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -40,8 +40,6 @@ inline MidiInterface::MidiInterface(Transport& in , mPendingMessageIndex(0) , mCurrentRpnNumber(0xffff) , mCurrentNrpnNumber(0xffff) - , mThruActivated(true) - , mThruFilterMode(Thru::Full) , mLastMessageSentTime(0) , mLastMessageReceivedTime(0) , mSenderActiveSensingPeriodicity(0) @@ -93,9 +91,8 @@ MidiInterface& MidiInterface::read(Channel inChannel if (channelMatch) launchCallback(); - thruFilter(inChannel); + thruFilter(); return channelMatch; } @@ -1399,51 +1396,26 @@ 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 { - return mThruActivated; + return mThruFilterCallback != thruOff; } template -inline MidiInterface& MidiInterface::turnThruOn(Thru::Mode inThruFilterMode) +inline MidiInterface& MidiInterface::turnThruOn(ThruFilterCallback fptr) { - mThruActivated = true; - mThruFilterMode = inThruFilterMode; - + mThruFilterCallback = fptr; return *this; } template inline MidiInterface& MidiInterface::turnThruOff() { - mThruActivated = false; - mThruFilterMode = Thru::Off; - + mThruFilterCallback = thruOff; return *this; } - /*! @} */ // End of doc group MIDI Thru // This method is called upon reception of a message @@ -1453,51 +1425,20 @@ inline MidiInterface& MidiInterface -void MidiInterface::thruFilter(Channel inChannel) +void MidiInterface::thruFilter() { - // If the feature is disabled, don't do anything. - if (!mThruActivated || (mThruFilterMode == Thru::Off)) - return; + if (!mThruFilterCallback(mMessage)) + return; + + MidiMessage thruMessage = mThruMapCallback(mMessage); // First, check if the received message is Channel if (mMessage.type >= NoteOff && mMessage.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 { @@ -1517,19 +1458,19 @@ void MidiInterface::thruFilter(Channel inChannel) 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..28adbd53 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -56,7 +56,6 @@ static const uint16_t ActiveSensingTimeout = 300; typedef byte StatusByte; typedef byte DataByte; typedef byte Channel; -typedef byte FilterMode; // ----------------------------------------------------------------------------- // Errors @@ -123,20 +122,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/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_MidiThru.cpp b/test/unit-tests/tests/unit-tests_MidiThru.cpp index dc0c0c1b..27319a7f 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,66 @@ struct VariableSysExSettings : midi::DefaultSettings static const unsigned SysExMaxSize = Size; }; +SerialMock serial; +Transport transport(serial); +MidiInterface midi((Transport&)transport); + +bool thruFilterSameChannel(const MidiMessage& inMessage) +{ + if (!midi.isChannelMessage(inMessage.type)) + return true; + + return MIDI_CHANNEL_OMNI == midi.getInputChannel() || + inMessage.channel == midi.getInputChannel(); +} + +bool thruFilterDifferentChannel(const MidiMessage& inMessage) +{ + if (!midi.isChannelMessage(inMessage.type)) + return true; + + return MIDI_CHANNEL_OMNI != midi.getInputChannel() && + inMessage.channel != midi.getInputChannel(); +} + +MidiMessage thruMapNoteOnFullVelocity(const MidiMessage& inMessage) +{ + if (inMessage.type != midi::MidiType::NoteOn) + return inMessage; + + MidiMessage modified = inMessage; + modified.data2 = 127; + return modified; +} + // ----------------------------------------------------------------------------- TEST(MidiThru, defaultValues) { - 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); } TEST(MidiThru, beginEnablesThru) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - 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); } TEST(MidiThru, setGet) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - 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); } TEST(MidiThru, off) { - SerialMock serial; - Transport transport(serial); - MidiInterface midi((Transport&)transport); - midi.begin(MIDI_CHANNEL_OMNI); midi.turnThruOff(); @@ -110,14 +102,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 +141,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 +168,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 +208,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 +235,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 +264,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 +292,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 +329,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 From 1fe67bec4f81ed98bf73d5d5e7fe12b39814630d Mon Sep 17 00:00:00 2001 From: Eric Sherman Date: Fri, 6 Aug 2021 03:10:36 -0400 Subject: [PATCH 02/26] makes changes requested in #232 discussion --- src/MIDI.h | 4 +--- src/MIDI.hpp | 18 +++++--------- test/unit-tests/tests/unit-tests_MidiThru.cpp | 24 ------------------- 3 files changed, 7 insertions(+), 39 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index 7d4d64b4..995202b9 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -236,8 +236,6 @@ class MidiInterface // MIDI Soft Thru public: - inline bool getThruState() const; - using ThruFilterCallback = bool (*)(const MidiMessage& inMessage); using ThruMapCallback = MidiMessage (*)(const MidiMessage& inMessage); inline MidiInterface& turnThruOn(ThruFilterCallback fptr = thruOn); @@ -254,7 +252,7 @@ class MidiInterface } private: - void thruFilter(); + 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; } diff --git a/src/MIDI.hpp b/src/MIDI.hpp index db3afd5b..0cca12a1 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -816,7 +816,7 @@ inline bool MidiInterface::read(Channel inChannel if (channelMatch) launchCallback(); - thruFilter(); + processThru(); return channelMatch; } @@ -1397,13 +1397,7 @@ void MidiInterface::launchCallback() */ template -inline bool MidiInterface::getThruState() const -{ - return mThruFilterCallback != thruOff; -} - -template -inline MidiInterface& MidiInterface::turnThruOn(ThruFilterCallback fptr) +inline void MidiInterface::turnThruOn(ThruFilterCallback fptr) { mThruFilterCallback = fptr; return *this; @@ -1425,7 +1419,7 @@ inline MidiInterface& MidiInterface -void MidiInterface::thruFilter() +void MidiInterface::processThru() { if (!mThruFilterCallback(mMessage)) return; @@ -1433,7 +1427,7 @@ void MidiInterface::thruFilter() 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) { send(thruMessage.type, thruMessage.data1, @@ -1443,7 +1437,7 @@ void MidiInterface::thruFilter() else { // Send the message to the output - switch (mMessage.type) + switch (thruMessage.type) { // Real Time and 1 byte case Clock: @@ -1453,7 +1447,7 @@ void MidiInterface::thruFilter() case ActiveSensing: case SystemReset: case TuneRequest: - sendRealTime(mMessage.type); + sendRealTime(thruMessage.type); break; case SystemExclusive: diff --git a/test/unit-tests/tests/unit-tests_MidiThru.cpp b/test/unit-tests/tests/unit-tests_MidiThru.cpp index 27319a7f..b89c262b 100644 --- a/test/unit-tests/tests/unit-tests_MidiThru.cpp +++ b/test/unit-tests/tests/unit-tests_MidiThru.cpp @@ -59,30 +59,6 @@ MidiMessage thruMapNoteOnFullVelocity(const MidiMessage& inMessage) // ----------------------------------------------------------------------------- -TEST(MidiThru, defaultValues) -{ - EXPECT_EQ(midi.getThruState(), true); - midi.begin(); // Should not change the state - EXPECT_EQ(midi.getThruState(), true); -} - -TEST(MidiThru, beginEnablesThru) -{ - midi.turnThruOff(); - EXPECT_EQ(midi.getThruState(), false); - midi.begin(); - EXPECT_EQ(midi.getThruState(), true); -} - -TEST(MidiThru, setGet) -{ - midi.turnThruOff(); - EXPECT_EQ(midi.getThruState(), false); - - midi.turnThruOn(); - EXPECT_EQ(midi.getThruState(), true); -} - TEST(MidiThru, off) { midi.begin(MIDI_CHANNEL_OMNI); From 4469f2aa7fa9417d1745eab88a0a3c76cb746bc9 Mon Sep 17 00:00:00 2001 From: Eric Sherman Date: Fri, 6 Aug 2021 10:41:05 -0400 Subject: [PATCH 03/26] uses Transport::thruActivated for initial thru state --- src/MIDI.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 0cca12a1..d44c5da8 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -91,7 +91,7 @@ MidiInterface& MidiInterface& MidiInterface void MidiInterface::processThru() { - if (!mThruFilterCallback(mMessage)) + if (!Transport::thruActivated || !mThruFilterCallback(mMessage)) return; MidiMessage thruMessage = mThruMapCallback(mMessage); From c2a838d8cc01562ec0fa7b9bb4915642989c033b Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 31 Aug 2021 15:23:54 +0200 Subject: [PATCH 04/26] chore: Add example --- examples/ThruFilterMap/ThruFilterMap.ino | 52 ++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 examples/ThruFilterMap/ThruFilterMap.ino diff --git a/examples/ThruFilterMap/ThruFilterMap.ino b/examples/ThruFilterMap/ThruFilterMap.ino new file mode 100644 index 00000000..be046795 --- /dev/null +++ b/examples/ThruFilterMap/ThruFilterMap.ino @@ -0,0 +1,52 @@ +#include + +using Message = midi::Message; + +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 Message& message) +{ + if (message.type == midi::NoteOn) + { + // Only forward NoteOn messages + return true; + } + return false; +} + +Message map(const Message& message) +{ + // Make a copy of the message + Message 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(); +} From f42c3443754b96a6bcf57748518a9b51f69997ce Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 2 Sep 2021 22:49:14 +0200 Subject: [PATCH 05/26] feat: Export type definitions when using macros Types have names prepended by the port name (defaults to `MIDI`), to allow multi-port applications. This allows referencing those types elsewhere in the application, without re-writing the template arguments, and simplifies referencing the underlying Message type, for SoftThru `filter`/`map`function definitions. Proposition & discussion: https://github.com/FortySevenEffects/arduino_midi_library/pull/232#issuecomment-910355200 --- examples/ThruFilterMap/ThruFilterMap.ino | 8 +++----- src/serialMIDI.h | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/ThruFilterMap/ThruFilterMap.ino b/examples/ThruFilterMap/ThruFilterMap.ino index be046795..40c2f56a 100644 --- a/examples/ThruFilterMap/ThruFilterMap.ino +++ b/examples/ThruFilterMap/ThruFilterMap.ino @@ -1,7 +1,5 @@ #include -using Message = midi::Message; - MIDI_CREATE_DEFAULT_INSTANCE(); /** @@ -17,7 +15,7 @@ MIDI_CREATE_DEFAULT_INSTANCE(); * allowing to use a keyboard to change patches on a MIDI device. */ -bool filter(const Message& message) +bool filter(const MIDIMessage& message) { if (message.type == midi::NoteOn) { @@ -27,10 +25,10 @@ bool filter(const Message& message) return false; } -Message map(const Message& message) +MIDIMessage map(const MIDIMessage& message) { // Make a copy of the message - Message output(message); + MIDIMessage output(message); if (message.type == midi::NoteOn) { output.type = midi::ProgramChange; diff --git a/src/serialMIDI.h b/src/serialMIDI.h index e69e9b2d..f783439a 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. @@ -125,6 +128,9 @@ END_MIDI_NAMESPACE @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); +#define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \ + 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); From 350f6d1ec4d1f370360cf95854d41ffd269371bc Mon Sep 17 00:00:00 2001 From: lathoub Date: Sat, 16 May 2020 16:35:03 +0200 Subject: [PATCH 06/26] reworked active Sensing --- src/MIDI.h | 1 - src/MIDI.hpp | 9 ++++++--- src/midi_Settings.h | 5 ++--- src/serialMIDI.h | 12 +++++------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index 995202b9..320e7780 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -293,7 +293,6 @@ class MidiInterface MidiMessage mMessage; unsigned long mLastMessageSentTime; unsigned long mLastMessageReceivedTime; - unsigned long mSenderActiveSensingPeriodicity; bool mReceiverActiveSensingActivated; int8_t mLastError; diff --git a/src/MIDI.hpp b/src/MIDI.hpp index d44c5da8..7eeab892 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -42,11 +42,10 @@ inline MidiInterface::MidiInterface(Transport& in , mCurrentNrpnNumber(0xffff) , mLastMessageSentTime(0) , mLastMessageReceivedTime(0) - , mSenderActiveSensingPeriodicity(0) , mReceiverActiveSensingActivated(false) , mLastError(0) + , mSenderActiveSensingPeriodicity(Settings::SenderActiveSensingPeriodicity) { - mSenderActiveSensingPeriodicity = Settings::SenderActiveSensingPeriodicity; } /*! \brief Destructor for MidiInterface. @@ -765,7 +764,7 @@ inline bool MidiInterface::read(Channel inChannel // 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) + if (Settings::UseSenderActiveSensing && (Platform::now() - mLastMessageSentTime) > Settings::SenderActiveSensingPeriodicity) { sendActiveSensing(); mLastMessageSentTime = Platform::now(); @@ -1407,6 +1406,10 @@ template inline MidiInterface& MidiInterface::turnThruOff() { mThruFilterCallback = thruOff; + if (Settings::UseSenderActiveSensing) + { + mLastMessageSentTime = Platform::now(); + } return *this; } diff --git a/src/midi_Settings.h b/src/midi_Settings.h index 179b773e..f142593a 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -75,6 +75,7 @@ struct DefaultSettings /*! 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) + as often as possible (1000 / SenderActiveSensingPeriodicity per second). */ static const bool UseSenderActiveSensing = false; @@ -95,10 +96,8 @@ struct DefaultSettings Typical value is 250 (ms) - an Active Sensing command is send every 250ms. (All Roland devices send Active Sensing every 250ms) - - Setting this field to 0 will disable sending MIDI active sensing. */ - static const uint16_t SenderActiveSensingPeriodicity = 0; + static const uint16_t SenderActiveSensingPeriodicity = 250; }; END_MIDI_NAMESPACE diff --git a/src/serialMIDI.h b/src/serialMIDI.h index f783439a..2bb673ed 100644 --- a/src/serialMIDI.h +++ b/src/serialMIDI.h @@ -124,13 +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) \ - 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); +#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); + From b82f2413497e9e5a033a9a29d9b213d8d54356cd Mon Sep 17 00:00:00 2001 From: lathoub Date: Sun, 17 May 2020 12:26:41 +0200 Subject: [PATCH 07/26] reworked ActiveSensing reworked ActiveSensing using input from a variety of device MIDI Implementation manuals (Roland, KORG, Yamaha) found on the internet. Receiving ActiveSensing: 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. Sending ActiveSensing: send x ms after the last sent command --- .../ReceiverActiveSensing.ino | 51 ++++++++++++++ .../SenderActiveSensing.ino | 58 ++++++++++++++++ src/MIDI.h | 2 +- src/MIDI.hpp | 68 +++++++++++-------- src/midi_Defs.h | 4 -- src/midi_Settings.h | 29 +++++--- 6 files changed, 169 insertions(+), 43 deletions(-) create mode 100644 examples/ReceiverActiveSensing/ReceiverActiveSensing.ino create mode 100644 examples/SenderActiveSensing/SenderActiveSensing.ino diff --git a/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino new file mode 100644 index 00000000..e8bcde55 --- /dev/null +++ b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino @@ -0,0 +1,51 @@ +#include +USING_NAMESPACE_MIDI + +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 errorHandler(int8_t err) +{ + if (bitRead(err, ErrorActiveSensingTimeout)) + { + 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.setHandleError(errorHandler); + 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/src/MIDI.h b/src/MIDI.h index 320e7780..7598cc6b 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -293,7 +293,7 @@ class MidiInterface MidiMessage mMessage; unsigned long mLastMessageSentTime; unsigned long mLastMessageReceivedTime; - bool mReceiverActiveSensingActivated; + bool mReceiverActiveSensingActive; int8_t mLastError; private: diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 7eeab892..1e9d7dc0 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -42,10 +42,11 @@ inline MidiInterface::MidiInterface(Transport& in , mCurrentNrpnNumber(0xffff) , mLastMessageSentTime(0) , mLastMessageReceivedTime(0) - , mReceiverActiveSensingActivated(false) + , 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. @@ -81,7 +82,8 @@ 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 && (Platform::now() - mLastMessageSentTime) > Settings::SenderActiveSensingPeriodicity) + // 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())) + if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActive) { - mReceiverActiveSensingActivated = false; - - mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit - if (mErrorCallback) - mErrorCallback(mLastError); + if ((Platform::now() - mLastMessageReceivedTime > Settings::ReceiverActiveSensingTimeout)) + { + // 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. + mReceiverActiveSensingActive = false; + + // its up to the error handler to send the stop processing messages + // (also, no clue what the channel is on which to send them) + + // no need to check if bit is already set, it is not (due to the mActiveSensingActive switch) + mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit + if (mErrorCallback) + mErrorCallback(mLastError); + } } #endif @@ -788,25 +799,26 @@ 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 + // 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. + mReceiverActiveSensingActive = true; + + // Clear the ErrorActiveSensingTimeout bit + mLastError &= ~(1UL << ErrorActiveSensingTimeout); if (mErrorCallback) mErrorCallback(mLastError); } } - // Keep the time of the last received message, so we can check for the timeout - if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated) - mLastMessageReceivedTime = Platform::now(); - #endif handleNullVelocityNoteOnAsNoteOff(); diff --git a/src/midi_Defs.h b/src/midi_Defs.h index 28adbd53..89e7d0f1 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -46,10 +46,6 @@ BEGIN_MIDI_NAMESPACE #define MIDI_PITCHBEND_MIN -8192 #define MIDI_PITCHBEND_MAX 8191 -/*! Receiving Active Sensing -*/ -static const uint16_t ActiveSensingTimeout = 300; - // ----------------------------------------------------------------------------- // Type definitions diff --git a/src/midi_Settings.h b/src/midi_Settings.h index f142593a..0accbac6 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -74,16 +74,14 @@ struct DefaultSettings /*! 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) - as often as possible (1000 / SenderActiveSensingPeriodicity per second). - */ - 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 @@ -95,9 +93,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 = 250; + static const uint16_t SenderActiveSensingPeriodicity = 300; + + /*! 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 bool UseReceiverActiveSensing = false; + + static const uint16_t ReceiverActiveSensingTimeout = 300; + }; END_MIDI_NAMESPACE From bb58efec1dfc587d3e6dc032e9638eeae12e8dd9 Mon Sep 17 00:00:00 2001 From: lathoub Date: Sun, 17 May 2020 12:26:53 +0200 Subject: [PATCH 08/26] Update midi_Settings.h --- src/midi_Settings.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/midi_Settings.h b/src/midi_Settings.h index 0accbac6..6c40922c 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -72,8 +72,6 @@ struct DefaultSettings */ static const unsigned SysExMaxSize = 128; - /*! Global switch to turn on/off sender ActiveSensing - Set to true to send ActiveSensing /*! 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) From 0db685980223f3f47127bde9d04ff67e0b08ab08 Mon Sep 17 00:00:00 2001 From: lathoub Date: Sun, 17 May 2020 15:39:18 +0200 Subject: [PATCH 09/26] Create Hairless.ino --- examples/Hairless/Hairless.ino | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 examples/Hairless/Hairless.ino 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); + } +} From 59fafdbf1aef1d2b5fc15a7acf89215e2a76c6da Mon Sep 17 00:00:00 2001 From: lathoub Date: Sat, 23 May 2020 10:51:47 +0200 Subject: [PATCH 10/26] ActiveSensingTimeout has its own callback handler removed ErrorActiveSensingTimeout --- .../ReceiverActiveSensing.ino | 6 ++-- src/MIDI.h | 1 + src/MIDI.hpp | 28 ++++++------------- src/midi_Defs.h | 2 +- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino index e8bcde55..6d91822b 100644 --- a/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino +++ b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino @@ -22,9 +22,9 @@ struct MyMIDISettings : public MIDI_NAMESPACE::DefaultSettings MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial1, MIDI, MyMIDISettings); -void errorHandler(int8_t err) +void activeSensingTimeoutExceptionHandler(bool active) { - if (bitRead(err, ErrorActiveSensingTimeout)) + if (!active) { MIDI.sendControlChange(AllSoundOff, 0, 1); MIDI.sendControlChange(AllNotesOff, 0, 1); @@ -41,7 +41,7 @@ void setup() pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); - MIDI.setHandleError(errorHandler); + MIDI.setHandleActiveSensingTimeout(activeSensingTimeoutExceptionHandler); MIDI.begin(1); } diff --git a/src/MIDI.h b/src/MIDI.h index 7598cc6b..67d316aa 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; diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 1e9d7dc0..c51e83e7 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -769,24 +769,20 @@ inline bool MidiInterface::read(Channel inChannel sendActiveSensing(); } + // 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) { if ((Platform::now() - mLastMessageReceivedTime > Settings::ReceiverActiveSensingTimeout)) { - // 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. mReceiverActiveSensingActive = false; - // its up to the error handler to send the stop processing messages + // its up to the handler to send the stop processing messages // (also, no clue what the channel is on which to send them) - - // no need to check if bit is already set, it is not (due to the mActiveSensingActive switch) - mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit - if (mErrorCallback) - mErrorCallback(mLastError); + mActiveSensingTimeoutCallback(true); } } #endif @@ -805,17 +801,9 @@ inline bool MidiInterface::read(Channel inChannel if (mMessage.type == ActiveSensing && !mReceiverActiveSensingActive) { - // 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. mReceiverActiveSensingActive = true; - // Clear the ErrorActiveSensingTimeout bit - mLastError &= ~(1UL << ErrorActiveSensingTimeout); - if (mErrorCallback) - mErrorCallback(mLastError); + mActiveSensingTimeoutCallback(false); } } diff --git a/src/midi_Defs.h b/src/midi_Defs.h index 89e7d0f1..409407bd 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -56,13 +56,13 @@ typedef byte Channel; // ----------------------------------------------------------------------------- // 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); From 12f05866cf7dcdd2c24abc8641f454c87abe5aa8 Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Sat, 23 May 2020 20:28:14 +0200 Subject: [PATCH 11/26] return mReceiverActiveSensingActive in SensingTimeout Handler return mReceiverActiveSensingActive in SensingTimeout Handler: - true when all is OK and active sensing is received on time - false when active sensing is **not** received on tome --- src/MIDI.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index c51e83e7..4cd78d56 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -782,7 +782,7 @@ inline bool MidiInterface::read(Channel inChannel // its up to the handler to send the stop processing messages // (also, no clue what the channel is on which to send them) - mActiveSensingTimeoutCallback(true); + mActiveSensingTimeoutCallback(mReceiverActiveSensingActive); } } #endif @@ -803,7 +803,7 @@ inline bool MidiInterface::read(Channel inChannel { mReceiverActiveSensingActive = true; - mActiveSensingTimeoutCallback(false); + mActiveSensingTimeoutCallback(mReceiverActiveSensingActive); } } From 21a4ad23dfa1f3d6c673418de910bdfcf06cfca3 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 8 Oct 2022 14:50:54 +0200 Subject: [PATCH 12/26] chore: Fix build (things got lost in merge) --- src/MIDI.h | 1 + src/MIDI.hpp | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index 67d316aa..5bc2bc5d 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -294,6 +294,7 @@ class MidiInterface MidiMessage mMessage; unsigned long mLastMessageSentTime; unsigned long mLastMessageReceivedTime; + unsigned long mSenderActiveSensingPeriodicity; bool mReceiverActiveSensingActive; int8_t mLastError; diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 4cd78d56..2562f247 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -42,9 +42,9 @@ inline MidiInterface::MidiInterface(Transport& in , mCurrentNrpnNumber(0xffff) , mLastMessageSentTime(0) , mLastMessageReceivedTime(0) + , 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."); } @@ -82,7 +82,7 @@ MidiInterface& MidiInterface::read(Channel inChannel sendActiveSensing(); } - // 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 + // 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) { @@ -1396,7 +1396,7 @@ void MidiInterface::launchCallback() */ template -inline void MidiInterface::turnThruOn(ThruFilterCallback fptr) +inline MidiInterface& MidiInterface::turnThruOn(ThruFilterCallback fptr) { mThruFilterCallback = fptr; return *this; From f6d95113f53af1ea5bf4a27b41754c1ce286e8e1 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 8 Oct 2022 15:23:09 +0200 Subject: [PATCH 13/26] chore: Add test for system common messages --- src/MIDI.hpp | 10 ++-- .../tests/unit-tests_MidiOutput.cpp | 47 ++++++++++++++++--- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 2562f247..610e1441 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -383,7 +383,9 @@ MidiInterface& MidiInterface& MidiInterface& MidiInterface 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; From 99fe61c9933cdbfb5d29921dc238bb650a4c523f Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 8 Oct 2022 17:02:14 +0200 Subject: [PATCH 14/26] chore: Add Nano variants to CI checks --- .github/workflows/platformio.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/platformio.yml b/.github/workflows/platformio.yml index b4f4891e..2076b113 100644 --- a/.github/workflows/platformio.yml +++ b/.github/workflows/platformio.yml @@ -31,6 +31,8 @@ jobs: - leonardo - micro - nanoatmega328 + - nano_every + - nano33ble - megaatmega2560 - teensy2 - teensy30 From a649f77146634b2247c81b5dcca635ac9fcdb089 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 15 Feb 2023 10:53:30 +0100 Subject: [PATCH 15/26] feat: Add MIDI analyzer from raw byte dump as CSV --- test/CMakeLists.txt | 1 + test/analyzer/CMakeLists.txt | 22 ++++++++ test/analyzer/analyzer.cpp | 25 +++++++++ test/analyzer/analyzer.h | 7 +++ test/analyzer/analyzer_MIDI.cpp | 81 ++++++++++++++++++++++++++++++ test/analyzer/analyzer_MIDI.h | 27 ++++++++++ test/analyzer/analyzer_Namespace.h | 13 +++++ test/mocks/test-mocks_SerialMock.h | 1 + 8 files changed, 177 insertions(+) create mode 100644 test/analyzer/CMakeLists.txt create mode 100644 test/analyzer/analyzer.cpp create mode 100644 test/analyzer/analyzer.h create mode 100644 test/analyzer/analyzer_MIDI.cpp create mode 100644 test/analyzer/analyzer_MIDI.h create mode 100644 test/analyzer/analyzer_Namespace.h 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..76facc19 --- /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 argc, char **argv) { + 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 From b67db7be899d8981e93c550fe3e208a320450578 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 08:48:07 +0200 Subject: [PATCH 16/26] chore: Bump dependencies (CMake & gTest) --- CMakeLists.txt | 2 +- external/google-test | 2 +- test/analyzer/analyzer.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a06c2e49..47cd69cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.7) +cmake_minimum_required(VERSION 3.26.4) project(arduino_midi_library CXX) add_subdirectory(builder) 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/test/analyzer/analyzer.cpp b/test/analyzer/analyzer.cpp index 76facc19..fb248a6c 100644 --- a/test/analyzer/analyzer.cpp +++ b/test/analyzer/analyzer.cpp @@ -10,7 +10,7 @@ USING_NAMESPACE_ANALYZER -int main(int argc, char **argv) { +int main(int, char**) { MIDIAnalyzer analyzer; analyzer.setup(); From 1920a75498f46831e5e6dcbb09369ef62f12c87c Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 08:48:37 +0200 Subject: [PATCH 17/26] chore: Code-fence Nano 33BLE --- examples/AltPinSerial/AltPinSerial.ino | 2 +- examples/Bench/Bench.ino | 2 +- examples/DualMerger/DualMerger.ino | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index ce57cb0f..0110c152 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_NANO33BLE) /* example not relevant for this hardware (SoftwareSerial not supported) */ MIDI_CREATE_DEFAULT_INSTANCE(); #else diff --git a/examples/Bench/Bench.ino b/examples/Bench/Bench.ino index 75855c36..72e6d6fe 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_NANO33BLE) // Print through USB and bench with Hardware serial MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiBench); #else diff --git a/examples/DualMerger/DualMerger.ino b/examples/DualMerger/DualMerger.ino index 4e99ea1a..5f2586d8 100644 --- a/examples/DualMerger/DualMerger.ino +++ b/examples/DualMerger/DualMerger.ino @@ -9,7 +9,7 @@ #if defined(ARDUINO_SAM_DUE) MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); -#elif defined(ARDUINO_SAMD_ZERO) +#elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_NANO33BLE) MIDI_CREATE_INSTANCE(Serial_, SerialUSB, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); #elif defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) From 8214157bec9ef5163ab1d4d12421c488693419b3 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 08:48:57 +0200 Subject: [PATCH 18/26] test: Add ESP32 boards --- .github/workflows/platformio.yml | 5 +++ .vscode/settings.json | 66 +++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/.github/workflows/platformio.yml b/.github/workflows/platformio.yml index 2076b113..ab0a9d21 100644 --- a/.github/workflows/platformio.yml +++ b/.github/workflows/platformio.yml @@ -25,6 +25,7 @@ jobs: - SimpleSynth - CustomBaudRate board: + # Arduino - uno - due - zero @@ -34,10 +35,14 @@ jobs: - nano_every - nano33ble - megaatmega2560 + # Teensy - teensy2 - teensy30 - teensy31 - teensylc + # ESP-32 + - featheresp32 + - pico32 steps: - uses: actions/checkout@v2 - name: Cache pip 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 +} From 561972ce5f64510b05034e18156d1bbcaeb929c5 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 08:55:33 +0200 Subject: [PATCH 19/26] chore: Require C++14 for gTest update --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 47cd69cf..53f1d1a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ 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() From fe731bc6cd98cf805415fce0f34ce1d6b0f29824 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 09:31:24 +0200 Subject: [PATCH 20/26] chore: Fix Basic_IO example for ESP32 --- examples/Basic_IO/Basic_IO.ino | 5 +++++ 1 file changed, 5 insertions(+) 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() From a6b3317b1ea2347b51d68012e6ca72020c858fd7 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 09:34:07 +0200 Subject: [PATCH 21/26] chore: Fix example board detection --- examples/AltPinSerial/AltPinSerial.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index 0110c152..0ae347e8 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_) || defined(ARDUINO_NANO33BLE) +#if defined(ARDUINO_SAM_DUE) || defined(SAMD_SERIES) || defined(_VARIANT_ARDUINO_ZERO_) || defined(ARDUINO_ARDUINO_NANO33BLE) /* example not relevant for this hardware (SoftwareSerial not supported) */ MIDI_CREATE_DEFAULT_INSTANCE(); #else From 8afa6efc5660a1df2df767dc3509780f18ccc826 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 09:40:09 +0200 Subject: [PATCH 22/26] chore: Verbose PIO CI --- .editorconfig | 4 +++ .github/workflows/platformio.yml | 48 ++++++++++++++++---------------- 2 files changed, 28 insertions(+), 24 deletions(-) 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 ab0a9d21..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: @@ -44,26 +44,26 @@ jobs: - 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 }} From b8f2c9a57876f7979b3ad1fedb13110e3d54edb2 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 10:09:23 +0200 Subject: [PATCH 23/26] chore: Disable SoftSerial test on ESP32 --- examples/AltPinSerial/AltPinSerial.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index 0ae347e8..683a98ec 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_) || defined(ARDUINO_ARDUINO_NANO33BLE) +#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 From c0fd2a79f977be52b836b030cf41838aeffe059f Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 10:11:45 +0200 Subject: [PATCH 24/26] chore: Fencing tests for BLE & ESP32 --- examples/Bench/Bench.ino | 2 +- examples/DualMerger/DualMerger.ino | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Bench/Bench.ino b/examples/Bench/Bench.ino index 72e6d6fe..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) || defined(ARDUINO_NANO33BLE) +#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/DualMerger/DualMerger.ino b/examples/DualMerger/DualMerger.ino index 5f2586d8..abb1ccdd 100644 --- a/examples/DualMerger/DualMerger.ino +++ b/examples/DualMerger/DualMerger.ino @@ -9,7 +9,7 @@ #if defined(ARDUINO_SAM_DUE) MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); -#elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_NANO33BLE) +#elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARDUINO_NANO33BLE) || defined(ESP32) MIDI_CREATE_INSTANCE(Serial_, SerialUSB, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); #elif defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) From d1e1540775f9d7877db7988953f8b70b29c9d46b Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 10:13:08 +0200 Subject: [PATCH 25/26] chore: Fix builtin LED defs --- examples/AltPinSerial/AltPinSerial.ino | 5 +++++ examples/CustomBaudRate/CustomBaudRate.ino | 5 +++++ examples/ErrorCallback/ErrorCallback.ino | 5 +++++ examples/ReceiverActiveSensing/ReceiverActiveSensing.ino | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index 683a98ec..1e623243 100644 --- a/examples/AltPinSerial/AltPinSerial.ino +++ b/examples/AltPinSerial/AltPinSerial.ino @@ -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/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/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/ReceiverActiveSensing/ReceiverActiveSensing.ino b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino index 6d91822b..d93d2820 100644 --- a/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino +++ b/examples/ReceiverActiveSensing/ReceiverActiveSensing.ino @@ -1,6 +1,11 @@ #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 From 7e9222b4634cdc510f7b76e4c356dfb25f69b174 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 18 Jul 2023 10:26:11 +0200 Subject: [PATCH 26/26] chore: Fix merger example on ESP32/BLE --- examples/DualMerger/DualMerger.ino | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/DualMerger/DualMerger.ino b/examples/DualMerger/DualMerger.ino index abb1ccdd..3e54d758 100644 --- a/examples/DualMerger/DualMerger.ino +++ b/examples/DualMerger/DualMerger.ino @@ -6,16 +6,16 @@ // 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) || defined(ARDUINO_ARDUINO_NANO33BLE) || defined(ESP32) +#elif defined(ARDUINO_SAMD_ZERO) MIDI_CREATE_INSTANCE(Serial_, SerialUSB, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); #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