From 98f7bb8831f553dedc5f534fecbf7dbf9a9deb00 Mon Sep 17 00:00:00 2001 From: Jacek Roszkowski Date: Fri, 22 Jun 2018 20:40:43 +0200 Subject: [PATCH 01/54] Corrected typo: Lenght - Length --- src/MIDI.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index 5fe356c1..90b1dc4b 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -234,7 +234,7 @@ class MidiInterface StatusByte mRunningStatus_RX; StatusByte mRunningStatus_TX; byte mPendingMessage[3]; - unsigned mPendingMessageExpectedLenght; + unsigned mPendingMessageExpectedLength; unsigned mPendingMessageIndex; unsigned mCurrentRpnNumber; unsigned mCurrentNrpnNumber; @@ -250,8 +250,8 @@ class MidiInterface // ----------------------------------------------------------------------------- -unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLenght); -unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLenght); +unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLength); +unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLength); END_MIDI_NAMESPACE From 2e8be813c4898e3b6cfc0c15f73f9af21aaebabc Mon Sep 17 00:00:00 2001 From: Jacek Roszkowski Date: Fri, 22 Jun 2018 20:43:21 +0200 Subject: [PATCH 02/54] Corrected typo: Lenght - Length --- src/MIDI.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index fa34533e..e5f7ab5e 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -36,7 +36,7 @@ inline MidiInterface::MidiInterface(SerialPort& inSerial) , mInputChannel(0) , mRunningStatus_RX(InvalidType) , mRunningStatus_TX(InvalidType) - , mPendingMessageExpectedLenght(0) + , mPendingMessageExpectedLength(0) , mPendingMessageIndex(0) , mCurrentRpnNumber(0xffff) , mCurrentNrpnNumber(0xffff) @@ -95,7 +95,7 @@ void MidiInterface::begin(Channel inChannel) mRunningStatus_RX = InvalidType; mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; + mPendingMessageExpectedLength = 0; mCurrentRpnNumber = 0xffff; mCurrentNrpnNumber = 0xffff; @@ -746,7 +746,7 @@ bool MidiInterface::parse() // Do not reset all input attributes, Running Status must remain unchanged. // We still need to reset these mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; + mPendingMessageExpectedLength = 0; return true; break; @@ -756,7 +756,7 @@ bool MidiInterface::parse() case AfterTouchChannel: case TimeCodeQuarterFrame: case SongSelect: - mPendingMessageExpectedLenght = 2; + mPendingMessageExpectedLength = 2; break; // 3 bytes messages @@ -766,13 +766,13 @@ bool MidiInterface::parse() case PitchBend: case AfterTouchPoly: case SongPosition: - mPendingMessageExpectedLenght = 3; + mPendingMessageExpectedLength = 3; break; case SystemExclusive: // The message can be any lenght // between 3 and MidiMessage::sSysExMaxSize bytes - mPendingMessageExpectedLenght = MidiMessage::sSysExMaxSize; + mPendingMessageExpectedLength = MidiMessage::sSysExMaxSize; mRunningStatus_RX = InvalidType; mMessage.sysexArray[0] = SystemExclusive; break; @@ -785,7 +785,7 @@ bool MidiInterface::parse() break; } - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) + if (mPendingMessageIndex >= (mPendingMessageExpectedLength - 1)) { // Reception complete mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); @@ -794,7 +794,7 @@ bool MidiInterface::parse() mMessage.data2 = 0; // Completed new message has 1 data byte mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; + mPendingMessageExpectedLength = 0; mMessage.valid = true; return true; } @@ -882,7 +882,7 @@ bool MidiInterface::parse() mPendingMessage[mPendingMessageIndex] = extracted; // Now we are going to check if we have reached the end of the message - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) + if (mPendingMessageIndex >= (mPendingMessageExpectedLength - 1)) { // "FML" case: fall down here with an overflown SysEx.. // This means we received the last possible data byte that can fit @@ -903,11 +903,11 @@ bool MidiInterface::parse() mMessage.data1 = mPendingMessage[1]; // Save data2 only if applicable - mMessage.data2 = mPendingMessageExpectedLenght == 3 ? mPendingMessage[2] : 0; + mMessage.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0; // Reset local variables mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; + mPendingMessageExpectedLength = 0; mMessage.valid = true; @@ -996,7 +996,7 @@ template inline void MidiInterface::resetInput() { mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; + mPendingMessageExpectedLength = 0; mRunningStatus_RX = InvalidType; } From 58474673b414b6244dbfb2d400474564be740fcf Mon Sep 17 00:00:00 2001 From: Jacek Roszkowski Date: Fri, 22 Jun 2018 20:44:12 +0200 Subject: [PATCH 03/54] Corrected typo: Lenght - Length --- src/MIDI.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index e14a98f8..3feb202b 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -37,8 +37,8 @@ BEGIN_MIDI_NAMESPACE data you want to send. \param inData The data to encode. \param outSysEx The output buffer where to store the encoded message. - \param inLength The lenght of the input buffer. - \return The lenght of the encoded output buffer. + \param inLength The length of the input buffer. + \return The length of the encoded output buffer. @see decodeSysEx Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com */ @@ -74,8 +74,8 @@ unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLength) your received message. \param inSysEx The SysEx data received from MIDI in. \param outData The output buffer where to store the decrypted message. - \param inLength The lenght of the input buffer. - \return The lenght of the output buffer. + \param inLength The length of the input buffer. + \return The length of the output buffer. @see encodeSysEx @see getSysExArrayLength Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com */ From e486667d8a6e8d1be7b6a6418110ebd27b78a4da Mon Sep 17 00:00:00 2001 From: Jacek Roszkowski Date: Fri, 22 Jun 2018 20:47:13 +0200 Subject: [PATCH 04/54] Corrected typo: Lenght - Length --- src/MIDI.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index e5f7ab5e..03b9a72f 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -770,7 +770,7 @@ bool MidiInterface::parse() break; case SystemExclusive: - // The message can be any lenght + // The message can be any length // between 3 and MidiMessage::sSysExMaxSize bytes mPendingMessageExpectedLength = MidiMessage::sSysExMaxSize; mRunningStatus_RX = InvalidType; @@ -1047,7 +1047,7 @@ inline const byte* MidiInterface::getSysExArray() const return mMessage.sysexArray; } -/*! \brief Get the lenght of the System Exclusive array. +/*! \brief Get the length of the System Exclusive array. It is coded using data1 as LSB and data2 as MSB. \return The array's length, in bytes. From d688030c14af12c209020e04f65ca3b7199a06a0 Mon Sep 17 00:00:00 2001 From: Eric Bateman Date: Sun, 11 Aug 2019 00:34:40 -0700 Subject: [PATCH 05/54] debugging --- .DS_Store | Bin 0 -> 6148 bytes src/MIDI.hpp | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9a874b5768f336915163bb88cd434575b859f936 GIT binary patch literal 6148 zcmeH~Jr2S!425ml0g0s}V-^m;4I%_5-~tF3k&vj^b9A16778<}(6eNJu~Vz<8=6`~ zboab&MFtUB!i}=AFfm2m$tVxGT*u4pe81nUlA49C} z?O@64YO)2RT{MRe%{!}2F))pG(Sih~)xkgosK7*lF7m<7{{#Hn{6A@7N(HFEpDCdI z{::send(MidiType inType, inChannel == MIDI_CHANNEL_OMNI || inType < 0x80) { + Serial.print("invalch: "); Serial.println(inType); return; // Don't send anything } if (inType <= PitchBend) // Channel messages { + Serial.print("chmsg: "); Serial.println(inType); // Protection: remove MSBs on data inData1 &= 0x7f; inData2 &= 0x7f; @@ -175,6 +178,7 @@ void MidiInterface::send(MidiType inType, } else if (inType >= Clock && inType <= SystemReset) { + Serial.print("sendRT: "); Serial.println(inType); sendRealTime(inType); // System Real-time and 1 byte. } } From dac0862657fe3adef0f6f3ca07c39fd4bf4203ed Mon Sep 17 00:00:00 2001 From: Eric Bateman Date: Sun, 11 Aug 2019 00:58:17 -0700 Subject: [PATCH 06/54] Update MIDI.hpp --- src/MIDI.hpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index fd87d335..99e348a4 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -26,7 +26,6 @@ */ #pragma once -#pragma message ("git clone MIDI.hpp" ) BEGIN_MIDI_NAMESPACE @@ -136,18 +135,15 @@ void MidiInterface::send(MidiType inType, DataByte inData2, Channel inChannel) { - // Then test if channel is valid - if (inChannel >= MIDI_CHANNEL_OFF || - inChannel == MIDI_CHANNEL_OMNI || - inType < 0x80) - { - Serial.print("invalch: "); Serial.println(inType); - return; // Don't send anything - } - if (inType <= PitchBend) // Channel messages { - Serial.print("chmsg: "); Serial.println(inType); + // Then test if channel is valid + if (inChannel >= MIDI_CHANNEL_OFF || + inChannel == MIDI_CHANNEL_OMNI || + inType < 0x80) + { + return; // Don't send anything + } // Protection: remove MSBs on data inData1 &= 0x7f; inData2 &= 0x7f; @@ -178,7 +174,6 @@ void MidiInterface::send(MidiType inType, } else if (inType >= Clock && inType <= SystemReset) { - Serial.print("sendRT: "); Serial.println(inType); sendRealTime(inType); // System Real-time and 1 byte. } } From 843a9e5d8d949f4e9bcc37d9314f24cf90ed62d0 Mon Sep 17 00:00:00 2001 From: Eric Bateman Date: Wed, 21 Aug 2019 23:36:22 -0700 Subject: [PATCH 07/54] Delete .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 9a874b5768f336915163bb88cd434575b859f936..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425ml0g0s}V-^m;4I%_5-~tF3k&vj^b9A16778<}(6eNJu~Vz<8=6`~ zboab&MFtUB!i}=AFfm2m$tVxGT*u4pe81nUlA49C} z?O@64YO)2RT{MRe%{!}2F))pG(Sih~)xkgosK7*lF7m<7{{#Hn{6A@7N(HFEpDCdI z{ Date: Mon, 20 Apr 2020 17:30:03 +0200 Subject: [PATCH 08/54] docs: Contributions --- CONTRIBUTING.md | 15 +++++++++++++-- README.md | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b319562..de91aceb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,18 +1,29 @@ +# Contributing Guidelines + +First, thanks for your help ! :+1: + +## Branches + +Please base your Pull Requests off the `master` branch. + ## Requirements Requirements to build and run the unit tests: -- CMake 2.8 or later -- GCC / Clang with C++11 support + +- CMake 2.8 or later +- GCC / Clang with C++11 support (GCC 4.8 or higher) ## Setup Pull Google Test / Google Mock subrepository: + ``` $ git submodule init $ git submodule update ``` Create build directory, run CMake, build and run unit tests: + ``` $ mkdir build && cd build $ cmake .. diff --git a/README.md b/README.md index 45d908a0..26f1837e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ This library adds MIDI I/O communications to an Arduino board. ### Features -- **New** : USB MIDI Device support with [`MIDIUSB`](https://github.com/arduino-libraries/MIDIUSB). +- **New** : MIDI over USB, Bluetooth & IP (see [Transports](#other-transport-mechanisms)). +- **New** : Active Sensing support - Compatible with all Arduino boards (and clones with an AVR processor). - Simple and fast way to send and receive every kind of MIDI message (including all System messages, SysEx, Clock, etc..). - OMNI input reading (read all channels). @@ -112,6 +113,21 @@ To report a bug, contribute, discuss on usage, or simply request support, please You can also contact me on Twitter: [@fortysevenfx](https://twitter.com/fortysevenfx). +## Contributors + +Special thanks to all who have contributed to this open-source project ! + +- [@lathoub](https://github.com/lathoub) +- [@jarosz](https://github.com/jarosz) +- [@ivankravets](https://github.com/ivankravets) +- [@insolace](https://github.com/insolace) +- [@softegg](https://github.com/softegg) +- [@per1234](https://github.com/per1234) +- [@LnnrtS](https://github.com/LnnrtS) +- [@DavidMenting](https://github.com/DavidMenting) + +You want to help ? Check out the [contribution guidelines](./CONTRIBUTING.md). + ## License MIT © 2009 - present [Francois Best](https://francoisbest.com) From 419c74adfe3fabdf2911cded419c793a642e155f Mon Sep 17 00:00:00 2001 From: Francois Best Date: Mon, 20 Apr 2020 17:33:46 +0200 Subject: [PATCH 09/54] doc: Add AppleMIDI & release notes --- README.md | 2 +- ReleaseNotes.md | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 26f1837e..507ea2a6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This library adds MIDI I/O communications to an Arduino board. ### Features -- **New** : MIDI over USB, Bluetooth & IP (see [Transports](#other-transport-mechanisms)). +- **New** : MIDI over USB, Bluetooth, IP & AppleMIDI (see [Transports](#other-transport-mechanisms)). - **New** : Active Sensing support - Compatible with all Arduino boards (and clones with an AVR processor). - Simple and fast way to send and receive every kind of MIDI message (including all System messages, SysEx, Clock, etc..). diff --git a/ReleaseNotes.md b/ReleaseNotes.md index a7138c05..154c0378 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,11 +1,13 @@ #### Changelog -* 11/06/2014 : Version 4.2 released. Bug fix for SysEx, overridable template settings. -* 16/04/2014 : Version 4.1 released. Bug fixes regarding running status. -* 13/02/2014 : Version 4.0 released. Moved to GitHub, added multiple instances & software serial support, and a few bug fixes. -* 29/01/2012 : Version 3.2 released. Release notes are [here](http://sourceforge.net/news/?group_id=265194). -* 06/05/2011 : Version 3.1 released. Added [callback](http://playground.arduino.cc/Main/MIDILibraryCallbacks) support. -* 06/03/2011 : Version 3.0 released. Project is now hosted on [SourceForge](http://sourceforge.net/projects/arduinomidilib). -* 14/12/2009 : Version 2.5 released. -* 28/07/2009 : Version 2.0 released. -* 28/03/2009 : Simplified version of MIDI.begin, Fast mode is now on by default. -* 08/03/2009 : Thru method operational. Added some features to enable thru. + +- 20/04/2020 : Version 5.0 released. Separation of transports by [@lathoub](https://github.com/lathoub), adds Active Sensing. +- 11/06/2014 : Version 4.2 released. Bug fix for SysEx, overridable template settings. +- 16/04/2014 : Version 4.1 released. Bug fixes regarding running status. +- 13/02/2014 : Version 4.0 released. Moved to GitHub, added multiple instances & software serial support, and a few bug fixes. +- 29/01/2012 : Version 3.2 released. Release notes are [here](http://sourceforge.net/news/?group_id=265194). +- 06/05/2011 : Version 3.1 released. Added [callback](http://playground.arduino.cc/Main/MIDILibraryCallbacks) support. +- 06/03/2011 : Version 3.0 released. Project is now hosted on [SourceForge](http://sourceforge.net/projects/arduinomidilib). +- 14/12/2009 : Version 2.5 released. +- 28/07/2009 : Version 2.0 released. +- 28/03/2009 : Simplified version of MIDI.begin, Fast mode is now on by default. +- 08/03/2009 : Thru method operational. Added some features to enable thru. From 99a981be5e9295a9b96ce54b20f7aa9570d7126a Mon Sep 17 00:00:00 2001 From: lathoub Date: Thu, 23 Apr 2020 22:08:15 +0200 Subject: [PATCH 10/54] thruActivated as a property of the Transport layer --- src/MIDI.hpp | 2 +- src/serialMIDI.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 07588cb7..17319163 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -94,7 +94,7 @@ void MidiInterface::begin(Channel inChannel) mMessage.length = 0; mThruFilterMode = Thru::Full; - mThruActivated = true; + mThruActivated = mTransport.thruActivated; } // ----------------------------------------------------------------------------- diff --git a/src/serialMIDI.h b/src/serialMIDI.h index b57d35c9..4b67115f 100644 --- a/src/serialMIDI.h +++ b/src/serialMIDI.h @@ -51,7 +51,9 @@ class SerialMIDI }; public: - void begin() + static const bool thruActivated = false; + + void begin() { // Initialise the Serial port #if defined(AVR_CAKE) From a5e31f15a4facece8d3c312545f5f38353a5b77c Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Thu, 23 Apr 2020 22:14:59 +0200 Subject: [PATCH 11/54] thruActivated defaults to true --- src/serialMIDI.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serialMIDI.h b/src/serialMIDI.h index 4b67115f..c7176c40 100644 --- a/src/serialMIDI.h +++ b/src/serialMIDI.h @@ -51,7 +51,7 @@ class SerialMIDI }; public: - static const bool thruActivated = false; + static const bool thruActivated = true; void begin() { From aa4912b5878e948282f29114c0a1e87f2599dff3 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 24 Apr 2020 06:47:49 +0200 Subject: [PATCH 12/54] chore: Update version in documentation --- doc/Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 07f94d41..49dc08c5 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Arduino MIDI Library" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "Version 4.4.0" +PROJECT_NUMBER = "Version 5.0.1" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a From 116dba12eb74edd8767cd3aa6b2fde974f373700 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 24 Apr 2020 06:49:55 +0200 Subject: [PATCH 13/54] chore: Update compiler config file --- .vscode/c_cpp_properties.json | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 1ed9b10f..6cdd9953 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -18,7 +18,10 @@ "macFrameworkPath": [ "/System/Library/Frameworks", "/Library/Frameworks" - ] + ], + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++17" }, { "name": "Linux", @@ -32,7 +35,10 @@ "/usr/include" ] }, - "intelliSenseMode": "clang-x64" + "intelliSenseMode": "clang-x64", + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++17" }, { "name": "Win32", @@ -46,8 +52,11 @@ "c:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include" ] }, - "intelliSenseMode": "msvc-x64" + "intelliSenseMode": "msvc-x64", + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++17" } ], - "version": 3 + "version": 4 } \ No newline at end of file From 1375b0b6aa3ee883c4f8a9e5e859f8362b3ef112 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 24 Apr 2020 06:52:02 +0200 Subject: [PATCH 14/54] doc: Update URL --- examples/Callbacks/Callbacks.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Callbacks/Callbacks.ino b/examples/Callbacks/Callbacks.ino index 642223b1..8c2b8bbd 100644 --- a/examples/Callbacks/Callbacks.ino +++ b/examples/Callbacks/Callbacks.ino @@ -7,7 +7,7 @@ MIDI_CREATE_DEFAULT_INSTANCE(); // This function will be automatically called when a NoteOn is received. // It must be a void-returning function with the correct parameters, // see documentation here: -// http://arduinomidilib.fortyseveneffects.com/a00022.html +// https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks void handleNoteOn(byte channel, byte pitch, byte velocity) { From 47ce2acc1638fac28c3c88099610451a619c17cc Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 24 Apr 2020 06:52:25 +0200 Subject: [PATCH 15/54] chore: Update version & package metadata --- README.md | 2 +- library.json | 49 +++++++++++++++++++++------------------------- library.properties | 6 +++--- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 507ea2a6..2dc9eff6 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); // ... ``` -now becomes in `5.0.0`: +now becomes in `5.x`: ```c++ #include diff --git a/library.json b/library.json index ce839e77..8e986b7d 100644 --- a/library.json +++ b/library.json @@ -1,28 +1,23 @@ { - "name": "MIDI Library", - "version": "5.0.0", - "keywords": "midi", - "description": "Enables MIDI I/O communications on the Arduino serial ports", - "license": "MIT", - "authors": - { - "name": "Francois Best", - "email": "francois.best@fortyseveneffects.com", - "url": "https://github.com/Franky47", - "maintainer": true - }, - "repository": - { - "type": "git", - "url": "https://github.com/FortySevenEffects/arduino_midi_library.git", - "branch": "master" - }, - "export": { - "include": [ - "src", - "examples" - ] - }, - "frameworks": "arduino", - "platforms": ["atmelavr", "atmelsam", "teensy"] - } + "name": "MIDI Library", + "version": "5.0.1", + "keywords": "midi", + "description": "Enables MIDI I/O communications on the Arduino serial ports", + "license": "MIT", + "authors": { + "name": "Francois Best", + "email": "contact@francoisbest.com", + "url": "https://github.com/Franky47", + "maintainer": true + }, + "repository": { + "type": "git", + "url": "https://github.com/FortySevenEffects/arduino_midi_library.git", + "branch": "master" + }, + "export": { + "include": ["src", "examples"] + }, + "frameworks": "arduino", + "platforms": ["atmelavr", "atmelsam", "teensy"] +} diff --git a/library.properties b/library.properties index c37afaba..ec4ef5d8 100644 --- a/library.properties +++ b/library.properties @@ -1,7 +1,7 @@ name=MIDI Library -version=5.0.0 -author=Forty Seven Effects, lathoub -maintainer=Francois Best +version=5.0.1 +author=Francois Best, lathoub +maintainer=Francois Best sentence=MIDI I/Os for Arduino paragraph=Read & send MIDI messages to interface with your controllers and synths category=Communication From ce57d26d2c1af5522d1e96e8011e53aab4bf3e5a Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 24 Apr 2020 07:10:02 +0200 Subject: [PATCH 16/54] doc: Update example --- examples/AltPinSerial/AltPinSerial.ino | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index 386fbcaa..65a0c490 100644 --- a/examples/AltPinSerial/AltPinSerial.ino +++ b/examples/AltPinSerial/AltPinSerial.ino @@ -1,34 +1,36 @@ #include -// Simple tutorial on how to receive and send MIDI messages. +// Simple tutorial on how to receive and send MIDI messages +// on a different serial port, using SoftwareSerial. // 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) - /* example not relevant for this hardware */ +#if defined(ARDUINO_SAM_DUE) || defined(SAMD_SERIES) + /* example not relevant for this hardware (SoftwareSerial not supported) */ MIDI_CREATE_DEFAULT_INSTANCE(); #else #include + using Transport = MIDI_NAMESPACE::SerialMIDI; int rxPin = 18; int txPin = 19; SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin); - MIDI_NAMESPACE::SerialMIDI serialMIDI(mySerial); - MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); + Transport serialMIDI(mySerial); + MIDI_NAMESPACE::MidiInterface MIDI((Transport&)serialMIDI); #endif void setup() { pinMode(LED_BUILTIN, OUTPUT); - MIDI.begin(4); // Launch MIDI and listen to channel 4 + MIDI.begin(4); // Launch MIDI and listen to channel 4 } void loop() { - if (MIDI.read()) // If we have received a message + if (MIDI.read()) // If we have received a message { digitalWrite(LED_BUILTIN, HIGH); MIDI.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1) - delay(1000); // Wait for a second + delay(1000); // Wait for a second MIDI.sendNoteOff(42, 0, 1); // Stop the note digitalWrite(LED_BUILTIN, LOW); } From d0110e48661c378e47467ded3d56eece1e5a6509 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 24 Apr 2020 07:10:21 +0200 Subject: [PATCH 17/54] doc: Thru is off by default in other transports --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2dc9eff6..6a170f9e 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,10 @@ All these Transport layers use this library for all the underlying MIDI work, making it easy to switch transport protocols or making transport protocol bridges. +### Differences between Serial & other transports + +- Software Thru is enabled by default on Serial, but not on other transports. + ## Contact To report a bug, contribute, discuss on usage, or simply request support, please [create an issue here](https://github.com/FortySevenEffects/arduino_midi_library/issues/new). From 80dfb8333c79882d6eb86e811523b67a75e920b5 Mon Sep 17 00:00:00 2001 From: Rolel 42 Date: Sun, 26 Apr 2020 17:08:56 +0200 Subject: [PATCH 18/54] [FIX] Repair missing MIDI_CREATE_CUSTOM_INSTANCE The MIDI_CREATE_CUSTOM_INSTANCE function was removed with 5.0 branch. This commit brings it back. --- src/serialMIDI.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/serialMIDI.h b/src/serialMIDI.h index c7176c40..da782715 100644 --- a/src/serialMIDI.h +++ b/src/serialMIDI.h @@ -113,5 +113,13 @@ class SerialMIDI MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); #endif +/*! \brief Create an instance of the library attached to a serial port with + custom settings. + @see DefaultSettings + @see MIDI_CREATE_INSTANCE + */ +#define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \ + MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ + MIDI_NAMESPACE::MidiInterface> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); END_MIDI_NAMESPACE From 0d9f6f4731ac05337499ac775a02fe6e182d2d6b Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 28 Apr 2020 07:31:10 +0200 Subject: [PATCH 19/54] chore: Release 5.0.2 Includes fix for missing MIDI_CREATE_CUSTOM_INSTANCE, see #150. --- doc/Doxyfile | 2 +- library.json | 2 +- library.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 49dc08c5..65d07121 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Arduino MIDI Library" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "Version 5.0.1" +PROJECT_NUMBER = "Version 5.0.2" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/library.json b/library.json index 8e986b7d..dd181dc2 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "MIDI Library", - "version": "5.0.1", + "version": "5.0.2", "keywords": "midi", "description": "Enables MIDI I/O communications on the Arduino serial ports", "license": "MIT", diff --git a/library.properties b/library.properties index ec4ef5d8..89adbca5 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=MIDI Library -version=5.0.1 +version=5.0.2 author=Francois Best, lathoub maintainer=Francois Best sentence=MIDI I/Os for Arduino From e5ee6201394133e71adc8e2841cdb67e98453f60 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 28 Apr 2020 07:34:38 +0200 Subject: [PATCH 20/54] doc: Add contributor --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6a170f9e..4fe6e749 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Special thanks to all who have contributed to this open-source project ! - [@per1234](https://github.com/per1234) - [@LnnrtS](https://github.com/LnnrtS) - [@DavidMenting](https://github.com/DavidMenting) +- [@Rolel](https://github.com/Rolel) You want to help ? Check out the [contribution guidelines](./CONTRIBUTING.md). From ff3052ceb437e2bbdf3ae483835f12175df060e2 Mon Sep 17 00:00:00 2001 From: lathoub Date: Sat, 16 May 2020 11:16:46 +0200 Subject: [PATCH 21/54] fix bug in MIDI_CREATE_CUSTOM_INSTANCE - MIDI Setting in MIDI_CREATE_CUSTOM_INSTANCE, but Setting for SerialMIDI - MACRO outside of Namespace --- src/midi_Settings.h | 4 ++-- src/serialMIDI.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/midi_Settings.h b/src/midi_Settings.h index 0066190c..179b773e 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -38,12 +38,12 @@ BEGIN_MIDI_NAMESPACE macro to create your instance. The settings you don't override will keep their default value. Eg: \code{.cpp} - struct MySettings : public midi::DefaultSettings + struct MySettings : public MIDI_NAMESPACE::DefaultSettings { static const unsigned SysExMaxSize = 1024; // Accept SysEx messages up to 1024 bytes long. }; - MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial2, midi, MySettings); + MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial2, MIDI, MySettings); \endcode */ struct DefaultSettings diff --git a/src/serialMIDI.h b/src/serialMIDI.h index da782715..9bd96694 100644 --- a/src/serialMIDI.h +++ b/src/serialMIDI.h @@ -91,6 +91,8 @@ class SerialMIDI SerialPort& mSerial; }; +END_MIDI_NAMESPACE + /*! \brief Create an instance of the library attached to a serial port. You can use HardwareSerial or SoftwareSerial for the serial port. Example: MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, midi2); @@ -119,7 +121,5 @@ class SerialMIDI @see MIDI_CREATE_INSTANCE */ #define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \ - MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ - MIDI_NAMESPACE::MidiInterface> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); - -END_MIDI_NAMESPACE + MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ + MIDI_NAMESPACE::MidiInterface, Settings> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); From 150cecd45053252f684bc641b7a414dce907f120 Mon Sep 17 00:00:00 2001 From: stfufane Date: Sat, 30 May 2020 17:50:37 +0200 Subject: [PATCH 22/54] Fix sendPitchBend multiplication --- src/MIDI.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 17319163..144e40e8 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -339,7 +339,7 @@ template void MidiInterface::sendPitchBend(double inPitchValue, Channel inChannel) { - const int scale = inPitchValue > 0.0 ? MIDI_PITCHBEND_MAX : MIDI_PITCHBEND_MIN; + const int scale = inPitchValue > 0.0 ? MIDI_PITCHBEND_MAX : - MIDI_PITCHBEND_MIN; const int value = int(inPitchValue * double(scale)); sendPitchBend(value, inChannel); } From e292cf61b058beed93cf3360c6a6eec59c22f7e1 Mon Sep 17 00:00:00 2001 From: Felix Uhl Date: Wed, 16 Sep 2020 16:07:45 +0200 Subject: [PATCH 23/54] Update library manager screenshot --- README.md | 2 +- res/library-manager.jpg | Bin 73944 -> 0 bytes res/library-manager.png | Bin 0 -> 18283 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 res/library-manager.jpg create mode 100644 res/library-manager.png diff --git a/README.md b/README.md index 4fe6e749..0c6756e8 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This library adds MIDI I/O communications to an Arduino board. ### Getting Started 1. Use the Arduino Library Manager to install the library. - ![Type "MIDI" in the Arduino IDE Library Manager](res/library-manager.jpg) + ![Type "MIDI" in the Arduino IDE Library Manager](res/library-manager.png) 2. Start coding: diff --git a/res/library-manager.jpg b/res/library-manager.jpg deleted file mode 100644 index 6648fa4ea72957c86a3bb13711709cdb281e9ffd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73944 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
-+$U!W16d+3o1QGp^I^U>wRUf0p+!D$FGZ(W<%#_OU&8K~f;H(;ht zt|K4flfO(ST|Nf7t8e<4Y^8GBhWq*1j$E$=fG2Z$Gt8yc4gJ_TH&&YMUU_W@VPPOw zJXtB*)S%S7Mkq-f>4H!>*pN~$u0z3fr6lU=pt#IP1>5?;CQKd=L1iJQk5nWcfQ`z%kVt)eM~zh(OTMHt8!NAy*6To zLQ=w1?lVpO{pg+BeQKmxyEw_DSk`uI`ta)YIzZuVXChrezxu7xsXT41B(ufMA4(G3 zOc$bJ08qq^da6YPN4Qf#`aL!RM$>|Z*?@{W)*nsoT$9GSf}C1&c5z z1JigxxI$h-Hp~$ikY-RF6PObaNZb^4T?_q!Ax?(5wYCd=4?t33aHE)3wdEGC@zE|^b|`i=2lkhqb8lD>$n?uYMI6|R8*%iCq4|h9K2o zaxEV=5ax0=9)YmMQI^-3eSi(q@z8)aE`LsygHt`;A8#auU+zz~+2YLfQAFe6;|0p- z+S7|6O{6ldBp;U>ckkSRga-IPkx^p0%e?d;N*RdC)t!MvtzI^Sw<=Y!cqVl;`pOKv zCRIBy2RKP#xmd@PYG62V7)Wd$QAUB!ShvFa&qibJO6nIxpNP}vL_Wvxy5c3IZ_D)v zbYr3I`t*eAwP4t-=G8RdBy%{j>agyVL6``}B>+%^F+iVfP<(&r^5Mm($&ZSWy`07k zYDmlwBCPohwTIWoACMGCk9_lp)jU+GUR!)Q%AZGyd`}t6=1@?N12jiVH?*#op!ynS zvzPP(54UOgBqSu?zk=Holco;nIfdjJ5}&*Zy+9ktDB-`Jm?tl&-ln^XLY9b`^cGT& z&dmtx#PBMkAu8#(uq|{7m9x0C_29%R|I%T2tdtF>nS@cAO3<+OMlrCj8&P02sWU4U zSAM>LL`guLY?hu-;o)-1*+luaz-)A`k8DJhp%uM~Oh_j8SFm~hwAdPd zY!p=CQlTHjI#cWR#ERXi)~88^*TE#uyQ0>gq;v`$>Nwi*V-ErN4EV43g2$38Q`C zxy}kD<;_c8cw3eH+G$YBqXIN(&Q^}y!ASy*fjQdG`j5nI7ut3iB`F^x6SH8*Wzm6{ zM}zYy@B{)G?#uSV1)rjGX_`m<6=^me+!tsH=aHy5{TUsRK$sU_l9B}2nWSW0z~RUI zGn-|cXt({nzw4{{YbippXW#~Itjy7w?sp_ea6J%v? zY1Wl{G#w{9Ytc`ORSomqtUIn2I)-Kkg0f z^|L*U21{33p6<7X|907PNFEJLP9=BT63UTu)&01z8a`t`*A49eLb#MUKLO*kT^rV} z6^F^F-zg;YQfn}e$MusA_s=D1(a*zmZ>38~8RfU8n32qQD0Z})#55%e4m5Z7DF;t+ zI$`Nj697Fj}AFeVl;{I8m%O0SM_X!LvO8?e$rFee0L4Z0P~yrL-UCbt^tVr?)T3M~ z=LFtAb16oq2ihU{jJmTQCp#A)8dNFu+Ftz$+^U<61;dlAvwTgwl0M<*2^EJXFQ)iBzu~N>?XKvyZ<5s2>+p^=IENOFUIOm3SmM7m)T1KXhKq4BL z1UK!B=m2x|LiIxqO0umu>f^$|W*up`TIJiBjRs51Iah|hq2+5hg7bF#|Ck=J%tH<(Y(2MB^<`FGF8uMCByK-I#J@z z(H7vq6w-CuG)gBTv+ATpN4#aiizQtW?HM~6L1Vb&YO@VyHZ`;&=|Cb0`EYpJ11agX z2tq$H1KsmZgx>7W3y|!-T2@s++`$eLG7stX*!Tqq5eBsL#M&aiayGA7Oh6J#CH{?8_JwQnfkyaL3r2J5ExLK55>hn&<44In70d zC4(?B*Q!wBE*LK&ihq1NdEXLOo2b%(J0a^0K~^8pmsM0hZVB5 zOJyTpp^hdN8pUPwqm9&PqfHA~3FG9LMlUSkNwtne+@<)$hy7;y zxpX(32NHn=CMDu_5ZX^kqObf&62cVy+{H_zeN~3h(}_RBhK}g7MtCV=O?ksR^X7gM zQLSGD8vA_Gbl1czSr5%w!>)by8oBn=sHd%i1+O==Bisq!IqeqZs{&C!HW;wQ9pZhN zt1m}V!0nj?0Edg5sd!J46BieYI*$g#KBnYM*F*jImYO?2)m8`|!h|EP;kB;r3+%+P zPnUnyqIxT`$r`shTNeRGljQ1MIYP^ioNliWSTY7XaVqqDVU3Z;^B9aT91=xPM%XU0 z&}m3q%@)Q+(su9g!d`d*q@F*kChIXK29bVWY1HQDY0`IYW^RL}(!DQ#{+{|3H9sK% zFC3v`)aITrVd5<;*as}MM3S2R#!S$C#L>mh())-dW4*Ufj|oBP4%JKd;%Jl>y=}r? z*@q-9h@DKbTzQ&e9Vf$O+D=CrO?I{g$U800;FCna+^;Pc;gooA+UueCH+93W06Ky+ z3ITScUt=k6KpUYvyY%Gmr$;~7=HCgwuP2-`VNSstq*TxF8&0JuXx0!ND$j_=HdoK3 zuN>P^?t}0TZ~flBla}>_BL?_kE%IW+YlkE|XP^vu!s6zws`Z@HC5i4{+vi|Lo$bjf zBs^$mxJZMy(GDEdNsZ;xsx+S?sLj!2dB7xb@{hB+>^e2CgL_9 ziT}jSp#PYQ-qrjohu=B*|1%CB|7``~f6ZsEZCC%(E@b$wV>{+g2*XCX430Y8 z%5y)AItmT1kgFf~IqTxnmZ;V9cxco(i#^O(^33s!wfCK-_Nb+PF&V()ZS;3t~J;_C$T-m z|D^9n&#`^H>pYf!=WEeE)6X^1+?M|d+k-U)KYwzcDfj0r_F{j6;;)4N9pE@0Rf9Jj+0MnQ}_R z%rPEJZ&}L=GAv36`%JrKF^*sQDoxq#X)J(g5a+& zlVZN9zD$%)582yiLJ6M_JxYfC#_U&#_gS1_p!D8udYnN9H_x3jt_Hi>kv`}7sGwcFXn)W&1APhml0XCg9{Vg#>Zxy4~UrCET23eo2@&ncgI*1U`+EtN@ z*I9z$Be)QCo{Mj|fO?6jxi1MW`ar!UgG2hu-*~oCNqJNyX-+L2vClknrw;=1=A#^8 zDfT+p6Q;MasGeQRRstE?Z1GUT^+=Vyft8`so4*Ea{q7aw{Y!AR^Kd6_+Bwq$Thd4m s?$w}wYMkjPHd>U%wH>ST|NMfSkpbMXI5B`w!_q)h&3&f1(*2SD0b6$!6951J diff --git a/res/library-manager.png b/res/library-manager.png new file mode 100644 index 0000000000000000000000000000000000000000..b76f122a6edbf023482f9ef50fba1d636d7c4fa6 GIT binary patch literal 18283 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 Date: Wed, 16 Sep 2020 16:12:08 +0200 Subject: [PATCH 24/54] Update search term for library manager --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c6756e8..4a3aa91c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This library adds MIDI I/O communications to an Arduino board. ### Getting Started 1. Use the Arduino Library Manager to install the library. - ![Type "MIDI" in the Arduino IDE Library Manager](res/library-manager.png) + ![Type "MIDI I/Os for Arduino" in the Arduino IDE Library Manager](res/library-manager.png) 2. Start coding: From 10ef10bfde03f1cde1084d06141cf13faf76a2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Wed, 9 Dec 2020 11:20:27 +0100 Subject: [PATCH 25/54] feat: Add issue template for bug report --- .github/ISSUE_TEMPLATE/bug_report.md | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..4193f273 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,57 @@ +--- +name: Bug report +about: Report something that does not work as intended +title: '' +labels: bug +assignees: '' + +--- + +## Context + +Please answer a few questions to help us understand your problem better and guide you to a solution: + + + +- What board are you using ? + - `example: Arduino Leonardo` + - _Please list any shields or other **relevant** hardware you're using_ +- What version of the Arduino IDE are you using ? + - `example: 1.8.5` +- How are you using MIDI ? + - [ ] Hardware Serial (DIN plugs) + - [ ] USB + - [ ] Other (please specify) +- Is your problem related to: + - [ ] MIDI Input (reading messages from other devices) + - [ ] MIDI Output (sending messages to other devices) +- How comfortable are you with code ? + - [ ] Complete beginner + - [ ] I've done basic projects + - [ ] I know my way around C/C++ + - [ ] Advanced / professional + +## Describe your project and what you expect to happen: + + + +## Describe your problem (what does not work): + + + +## Steps to reproduce + + From 33b294624a6a31fd46768049242501d7efeeb9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Wed, 9 Dec 2020 11:26:09 +0100 Subject: [PATCH 26/54] chore: Add Feature Request template Also redirect issues to discussions for project help. --- .github/ISSUE_TEMPLATE/bug_report.md | 9 +++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4193f273..415e2891 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,15 @@ assignees: '' --- + + ## Context Please answer a few questions to help us understand your problem better and guide you to a solution: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..c2b58c05 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: new feature +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From d3c361033ddd79b7e8248d88e598b1e6b435e01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Wed, 9 Dec 2020 11:28:26 +0100 Subject: [PATCH 27/54] chore: Suggest using Discussions first --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4a3aa91c..433aec61 100644 --- a/README.md +++ b/README.md @@ -111,9 +111,9 @@ protocol bridges. - Software Thru is enabled by default on Serial, but not on other transports. -## Contact +## Contact & Contribution -To report a bug, contribute, discuss on usage, or simply request support, please [create an issue here](https://github.com/FortySevenEffects/arduino_midi_library/issues/new). +To report a bug, contribute, discuss on usage, or request support, please [discuss it here](https://github.com/FortySevenEffects/arduino_midi_library/discussions/new). You can also contact me on Twitter: [@fortysevenfx](https://twitter.com/fortysevenfx). From d967c0c389aecb37c858891ebdb7d85b58c0c56b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Wed, 9 Dec 2020 11:34:22 +0100 Subject: [PATCH 28/54] chore: Disable generic issues General community-related Q&A is moved to Discussions --- .github/ISSUE_TEMPLATE/config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..3ba13e0c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false From a93fb27a1c4708191ee0892f7789197920739a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Wed, 9 Dec 2020 11:39:42 +0100 Subject: [PATCH 29/54] chore: Add link to Discussions in issue templates --- .github/ISSUE_TEMPLATE/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3ba13e0c..ed58563a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,5 @@ blank_issues_enabled: false +contact_links: + - name: Discussions + url: https://github.com/FortySevenEffects/arduino_midi_library/discussions + about: Please ask and answer questions here. From d9149d19df867abbf78cfcd37ef9d0e14dedca7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Wed, 9 Dec 2020 14:04:49 +0100 Subject: [PATCH 30/54] chore: Better description for Discussions --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ed58563a..3f0ebf1f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,4 @@ blank_issues_enabled: false contact_links: - name: Discussions url: https://github.com/FortySevenEffects/arduino_midi_library/discussions - about: Please ask and answer questions here. + about: Not a bug or a feature request ? Discuss your problem, ask for help or show what you've built in Discussions. From d501f4bb3dfff548a7cef4d85371bec0b438bdd7 Mon Sep 17 00:00:00 2001 From: lathoub Date: Sat, 5 Jun 2021 08:55:25 +0200 Subject: [PATCH 31/54] end method for Serial Transport The Transport layer is started, but could never be stopped. This change will allow the underlying library to call the end() method in the Transport layers (all other Transport layer have the end() method implemented) Next step is to call the Transport end() method, when ending the MIDI instance --- src/serialMIDI.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/serialMIDI.h b/src/serialMIDI.h index 9bd96694..e69e9b2d 100644 --- a/src/serialMIDI.h +++ b/src/serialMIDI.h @@ -63,6 +63,11 @@ class SerialMIDI #endif } + void end() + { + mSerial.end(); + } + bool beginTransmission(MidiType) { return true; From 3f0a2d3fdbd322ec71b5c84646f335148185c1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Here=C3=B1=C3=BA?= Date: Sun, 1 Aug 2021 14:23:42 -0300 Subject: [PATCH 32/54] Minor fix (line 65) --- doc/sysex-codec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sysex-codec.md b/doc/sysex-codec.md index 47395cd3..7f9f04b5 100644 --- a/doc/sysex-codec.md +++ b/doc/sysex-codec.md @@ -62,7 +62,7 @@ becomes: ``` The order of the bits in the "header" byte is reversed. -To follow this beheaviour, set the inFlipHeaderBits argument to true. +To follow this behaviour, set the inFlipHeaderBits argument to true. Example: ```c++ From 0d605dc8a7e423fd2c74ccc0287adc164d46de7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 1 Aug 2021 20:02:40 +0200 Subject: [PATCH 33/54] doc: Update contributors Added @kant for PR #230 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 433aec61..00a5af5f 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ Special thanks to all who have contributed to this open-source project ! - [@LnnrtS](https://github.com/LnnrtS) - [@DavidMenting](https://github.com/DavidMenting) - [@Rolel](https://github.com/Rolel) +- [@kant](https://github.com/kant) You want to help ? Check out the [contribution guidelines](./CONTRIBUTING.md). From 7ca289d84e6b237fadedd48cbc2132df3f7e9593 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 6 Aug 2021 09:33:22 +0200 Subject: [PATCH 34/54] chore: Fix internal method casing --- src/MIDI.h | 2 +- src/MIDI.hpp | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index d15888fc..0e927cea 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -254,7 +254,7 @@ class MidiInterface inline void handleNullVelocityNoteOnAsNoteOff(); inline bool inputFilter(Channel inChannel); inline void resetInput(); - inline void UpdateLastSentTime(); + inline void updateLastSentTime(); // ------------------------------------------------------------------------- // Transport diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 144e40e8..db2661bc 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -48,7 +48,7 @@ inline MidiInterface::MidiInterface(Transport& in , mReceiverActiveSensingActivated(false) , mLastError(0) { - mSenderActiveSensingPeriodicity = Settings::SenderActiveSensingPeriodicity; + mSenderActiveSensingPeriodicity = Settings::SenderActiveSensingPeriodicity; } /*! \brief Destructor for MidiInterface. @@ -141,7 +141,7 @@ void MidiInterface::send(const MidiMessage& inMes } } mTransport.endTransmission(); - UpdateLastSentTime(); + updateLastSentTime(); } @@ -202,7 +202,7 @@ void MidiInterface::send(MidiType inType, } mTransport.endTransmission(); - UpdateLastSentTime(); + updateLastSentTime(); } } else if (inType >= Clock && inType <= SystemReset) @@ -372,7 +372,7 @@ void MidiInterface::sendSysEx(unsigned inLength, mTransport.write(MidiType::SystemExclusiveEnd); mTransport.endTransmission(); - UpdateLastSentTime(); + updateLastSentTime(); } if (Settings::UseRunningStatus) @@ -475,7 +475,7 @@ void MidiInterface::sendCommon(MidiType inType, u break; // LCOV_EXCL_LINE - Coverage blind spot } mTransport.endTransmission(); - UpdateLastSentTime(); + updateLastSentTime(); } if (Settings::UseRunningStatus) @@ -506,7 +506,7 @@ void MidiInterface::sendRealTime(MidiType inType) { mTransport.write((byte)inType); mTransport.endTransmission(); - UpdateLastSentTime(); + updateLastSentTime(); } break; default: @@ -673,6 +673,13 @@ inline void MidiInterface::endNrpn(Channel inChan mCurrentNrpnNumber = 0xffff; } +template +inline void MidiInterface::updateLastSentTime() +{ + if (Settings::UseSenderActiveSensing && mSenderActiveSensingPeriodicity) + mLastMessageSentTime = Platform::now(); +} + /*! @} */ // End of doc group MIDI Output // ----------------------------------------------------------------------------- @@ -1381,12 +1388,6 @@ inline void MidiInterface::turnThruOff() mThruFilterMode = Thru::Off; } -template -inline void MidiInterface::UpdateLastSentTime() -{ - if (Settings::UseSenderActiveSensing && mSenderActiveSensingPeriodicity) - mLastMessageSentTime = Platform::now(); -} /*! @} */ // End of doc group MIDI Thru From f356c99ca232bc1716663766a4e10438a26ed0ca Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 6 Aug 2021 09:35:01 +0200 Subject: [PATCH 35/54] chore: Allow copying Messages This is ground work for the `map` Thru function in PR #232. --- src/midi_Message.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/midi_Message.h b/src/midi_Message.h index dadb8960..bbd8d6a1 100644 --- a/src/midi_Message.h +++ b/src/midi_Message.h @@ -54,6 +54,20 @@ struct Message memset(sysexArray, 0, sSysExMaxSize * sizeof(DataByte)); } + inline Message(const Message& inOther) + : channel(inOther.channel) + , type(inOther.type) + , data1(inOther.data1) + , data2(inOther.data2) + , valid(inOther.valid) + , length(inOther.length) + { + if (type == midi::SystemExclusive) + { + memcpy(sysexArray, inOther.sysexArray, sSysExMaxSize * sizeof(DataByte)); + } + } + /*! The maximum size for the System Exclusive array. */ static const unsigned sSysExMaxSize = SysExMaxSize; @@ -94,7 +108,7 @@ struct Message /*! Total Length of the message. */ unsigned length; - + inline unsigned getSysExSize() const { const unsigned size = unsigned(data2) << 8 | data1; From 9f5317f299af196277a05ed051882e047ed29d09 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 6 Aug 2021 09:35:46 +0200 Subject: [PATCH 36/54] chore: Config --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 007ba052..fc1d2102 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,7 @@ "string": "cpp", "string_view": "cpp", "vector": "cpp", - "istream": "cpp" + "istream": "cpp", + "system_error": "cpp" } } \ No newline at end of file From d2f11d02e2c0b245088b83a20ea5d3d125f8a453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Mon, 9 Aug 2021 22:03:59 +0200 Subject: [PATCH 37/54] chore: Add CMake CI --- .github/workflows/cmake.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/cmake.yml diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 00000000..cfa5b882 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,36 @@ +name: CMake + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Run Unit Tests + working-directory: ${{github.workspace}}/build + run: ./test/unit-tests/unit-tests + From 2f03d9b29daaf6f8329466f9686acbc38988f8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Mon, 9 Aug 2021 22:06:09 +0200 Subject: [PATCH 38/54] chore: Checkout submodules in CI --- .github/workflows/cmake.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index cfa5b882..0646bf2e 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -20,6 +20,8 @@ jobs: steps: - uses: actions/checkout@v2 + with: + submodules: recursive - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. From bf80e99d5412f4212217a89fc3b444bb8295a9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Tue, 10 Aug 2021 16:12:51 +0200 Subject: [PATCH 39/54] chore: Move CI to GitHub Actions - Build and run unit tests with CMake - Track code coverage with Coveralls - Build examples on officially supported boards - Removed TravisCI configuration Closes #233. --- .github/workflows/cmake.yml | 33 ++++++-- .github/workflows/platformio.yml | 59 ++++++++++++++ .travis.yml | 104 ------------------------- examples/AltPinSerial/AltPinSerial.ino | 2 +- 4 files changed, 87 insertions(+), 111 deletions(-) create mode 100644 .github/workflows/platformio.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 0646bf2e..9b006b6a 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -2,13 +2,14 @@ name: CMake on: push: - branches: [ master ] pull_request: branches: [ master ] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release + BUILD_TYPE: Debug + GENERATE_COVERAGE: true + LCOV_ROOT: ${{github.workspace}}/lcov jobs: build: @@ -22,11 +23,19 @@ jobs: - uses: actions/checkout@v2 with: submodules: recursive - + + - name: Install lcov + run: | + mkdir -p "$LCOV_ROOT" + wget https://github.com/linux-test-project/lcov/releases/download/v1.15/lcov-1.15.tar.gz --output-document="$LCOV_ROOT/lcov.tar.gz" + tar -xf "$LCOV_ROOT/lcov.tar.gz" --strip-components=1 -C "$LCOV_ROOT" + echo "$LCOV_ROOT/bin" >> $GITHUB_PATH + shell: bash + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILDER_ENABLE_PROFILING=true - name: Build # Build your program with the given configuration @@ -34,5 +43,17 @@ jobs: - name: Run Unit Tests working-directory: ${{github.workspace}}/build - run: ./test/unit-tests/unit-tests - + run: ctest --verbose + + - name: Generate code coverage report + working-directory: ${{github.workspace}}/build + run: | + lcov --directory . --capture --output-file coverage.info + lcov --remove coverage.info '/usr/*' "${{github.workspace}}/test/*" "${{github.workspace}}/external/*" --output-file coverage.info + lcov --list coverage.info + + - uses: coverallsapp/github-action@9ba913c152ae4be1327bfb9085dc806cedb44057 + name: Upload code coverage report to Coveralls + with: + path-to-lcov: ${{github.workspace}}/build/coverage.info + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/platformio.yml b/.github/workflows/platformio.yml new file mode 100644 index 00000000..64331444 --- /dev/null +++ b/.github/workflows/platformio.yml @@ -0,0 +1,59 @@ +name: PlatformIO + +on: + push: + branches: [master] + pull_request: + branches: [ master ] + +jobs: + platformio: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + example: + - AltPinSerial + - Basic_IO + - Bench + - Callbacks + - DualMerger + - ErrorCallback + - Input + - RPN_NRPN + - SimpleSynth + board: + - uno + - due + - zero + - leonardo + - micro + - nanoatmega328 + - megaatmega2560 + - teensy2 + - teensy30 + - teensy31 + - teensylc + 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 + - name: Run PlatformIO + run: pio ci --lib="." --board="${{matrix.board}}" + env: + PLATFORMIO_CI_SRC: examples/${{ matrix.example }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index de7c28d0..00000000 --- a/.travis.yml +++ /dev/null @@ -1,104 +0,0 @@ -# Kudos to these guys: -# https://github.com/Return-To-The-Roots/s25client/blob/master/.travis.yml -# http://docs.platformio.org/en/stable/ci/travis.html - -sudo: false -language: python - -os: - - linux - -python: - - "2.7" - -# Cache PlatformIO packages using Travis CI container-based infrastructure -cache: - directories: - - "~/.platformio" - -env: - global: - - BUILD_TYPE=Debug - matrix: - - BUILD_UNIT_TESTS=1 - - PLATFORMIO_CI_SRC=examples/Basic_IO - - PLATFORMIO_CI_SRC=examples/Bench - - PLATFORMIO_CI_SRC=examples/Callbacks - - PLATFORMIO_CI_SRC=examples/DualMerger - - PLATFORMIO_CI_SRC=examples/ErrorCallback - - PLATFORMIO_CI_SRC=examples/Input - - PLATFORMIO_CI_SRC=examples/RPN_NRPN - - PLATFORMIO_CI_SRC=examples/SimpleSynth - - PLATFORMIO_CI_SRC=examples/AltPinSerial - -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 - - cmake - -install: - - | - if [ "${BUILD_UNIT_TESTS}" ]; then - # GCov 4.6 cannot handle the file structure - export CXX="g++-4.8" - export GCOV="gcov-4.8" - - # Install newer lcov (1.9 seems to fail: http://gronlier.fr/blog/2015/01/adding-code-coverage-to-your-c-project/) - export LCOV_ROOT="$HOME/lcov" - mkdir -p "$LCOV_ROOT" - wget http://ftp.de.debian.org/debian/pool/main/l/lcov/lcov_1.14.orig.tar.gz --output-document="$LCOV_ROOT/lcov.tar.gz" - tar xf "$LCOV_ROOT/lcov.tar.gz" --strip-components=1 -C $LCOV_ROOT - export PATH="$LCOV_ROOT/bin:$PATH" - which lcov - - # Install coveralls tool - gem install coveralls-lcov - export GENERATE_COVERAGE=1 - else - # Install PlatformIO - pip install -U platformio - fi - -script: - # Build unit tests & generate code coverage - - | - if [ "${BUILD_UNIT_TESTS}" ]; then - mkdir build && cd build - cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILDER_ENABLE_PROFILING=${GENERATE_COVERAGE} --generator="Unix Makefiles" .. - make all - ctest --verbose - fi - - # Build current example - - | - if [ ! "${BUILD_UNIT_TESTS}" ]; then - platformio ci --lib="." \ - --board="uno" \ - --board="due" \ - --board="zero" \ - --board="leonardo" \ - --board="micro" \ - --board="nanoatmega328" \ - --board="megaatmega2560" \ - --board="teensy2" \ - --board="teensy30" \ - --board="teensy31" \ - --board="teensylc" - fi - -after_success: - - | - if [ "${GENERATE_COVERAGE}" ]; then - # Generate code coverage information & send to Coveralls - rm -rf ./external/google-test/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.gcda - lcov --gcov-tool $GCOV --directory . --capture --output-file coverage.info - lcov --gcov-tool $GCOV --remove coverage.info 'test/*' '/usr/*' 'external/*' --output-file coverage.info - lcov --list coverage.info - coveralls-lcov --repo-token ${COVERALLS_TOKEN} coverage.info - fi - -notifications: - email: false diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index 65a0c490..ce57cb0f 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) +#if defined(ARDUINO_SAM_DUE) || defined(SAMD_SERIES) || defined(_VARIANT_ARDUINO_ZERO_) /* example not relevant for this hardware (SoftwareSerial not supported) */ MIDI_CREATE_DEFAULT_INSTANCE(); #else From d9873560ad58ca0b4a36f43e92266f1a9942b42c Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 10 Aug 2021 16:21:18 +0200 Subject: [PATCH 40/54] chore: Improve coverage blind spots --- src/MIDI.hpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index db2661bc..e071178b 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -471,8 +471,10 @@ void MidiInterface::sendCommon(MidiType inType, u break; case TuneRequest: break; + // LCOV_EXCL_START - Coverage blind spot default: - break; // LCOV_EXCL_LINE - Coverage blind spot + break; + // LCOV_EXCL_STOP } mTransport.endTransmission(); updateLastSentTime(); @@ -987,9 +989,10 @@ bool MidiInterface::parse() resetInput(); return false; } - + // LCOV_EXCL_START - Coverage blind spot default: - break; // LCOV_EXCL_LINE - Coverage blind spot + break; + // LCOV_EXCL_STOP } } @@ -1334,9 +1337,11 @@ void MidiInterface::launchCallback() case SystemReset: if (mSystemResetCallback != nullptr) mSystemResetCallback(); break; + // LCOV_EXCL_START - Unreacheable code, but prevents unhandled case warning. case InvalidType: default: - break; // LCOV_EXCL_LINE - Unreacheable code, but prevents unhandled case warning. + break; + // LCOV_EXCL_STOP } } From b4daa697a9927c4f52d766f18dcd0f7ad305c3d8 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 10 Aug 2021 16:26:57 +0200 Subject: [PATCH 41/54] doc: Update badges with CI status --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 00a5af5f..5a20be2f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # Arduino MIDI Library -[![Build Status](https://travis-ci.org/FortySevenEffects/arduino_midi_library.svg?branch=master)](https://travis-ci.org/FortySevenEffects/arduino_midi_library) -[![Coveralls](https://img.shields.io/coveralls/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](https://coveralls.io/github/FortySevenEffects/arduino_midi_library) [![GitHub release](https://img.shields.io/github/release/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](https://github.com/FortySevenEffects/arduino_midi_library/releases/latest) [![License](https://img.shields.io/github/license/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](LICENSE) +[![Build](https://github.com/FortySevenEffects/arduino_midi_library/actions/workflows/cmake.yml/badge.svg?branch=master)](https://github.com/FortySevenEffects/arduino_midi_library/actions/workflows/cmake.yml) +[![Examples](https://github.com/FortySevenEffects/arduino_midi_library/actions/workflows/platformio.yml/badge.svg?branch=master)](https://github.com/FortySevenEffects/arduino_midi_library/actions/workflows/platformio.yml) +[![Coveralls](https://img.shields.io/coveralls/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](https://coveralls.io/github/FortySevenEffects/arduino_midi_library) This library adds MIDI I/O communications to an Arduino board. From 2012e2a84a16aaaec85d0d7113bec8f26d8d7c58 Mon Sep 17 00:00:00 2001 From: lathoub Date: Sun, 10 Oct 2021 12:04:08 +0200 Subject: [PATCH 42/54] chaining for all commands, callbacks and setters --- src/MIDI.h | 136 ++++++++++++++++++++++---------------------- src/MIDI.hpp | 156 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 170 insertions(+), 122 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index 0e927cea..ebdccd0c 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -62,94 +62,94 @@ class MidiInterface inline ~MidiInterface(); public: - void begin(Channel inChannel = 1); + MidiInterface& begin(Channel inChannel = 1); // ------------------------------------------------------------------------- // MIDI Output public: - inline void sendNoteOn(DataByte inNoteNumber, + inline MidiInterface& sendNoteOn(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel); - inline void sendNoteOff(DataByte inNoteNumber, + inline MidiInterface& sendNoteOff(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel); - inline void sendProgramChange(DataByte inProgramNumber, + inline MidiInterface& sendProgramChange(DataByte inProgramNumber, Channel inChannel); - inline void sendControlChange(DataByte inControlNumber, + inline MidiInterface& sendControlChange(DataByte inControlNumber, DataByte inControlValue, Channel inChannel); - inline void sendPitchBend(int inPitchValue, Channel inChannel); - inline void sendPitchBend(double inPitchValue, Channel inChannel); + inline MidiInterface& sendPitchBend(int inPitchValue, Channel inChannel); + inline MidiInterface& sendPitchBend(double inPitchValue, Channel inChannel); - inline void sendPolyPressure(DataByte inNoteNumber, + inline MidiInterface& sendPolyPressure(DataByte inNoteNumber, DataByte inPressure, Channel inChannel) __attribute__ ((deprecated)); - inline void sendAfterTouch(DataByte inPressure, + inline MidiInterface& sendAfterTouch(DataByte inPressure, Channel inChannel); - inline void sendAfterTouch(DataByte inNoteNumber, + inline MidiInterface& sendAfterTouch(DataByte inNoteNumber, DataByte inPressure, Channel inChannel); - inline void sendSysEx(unsigned inLength, + inline MidiInterface& sendSysEx(unsigned inLength, const byte* inArray, bool inArrayContainsBoundaries = false); - inline void sendTimeCodeQuarterFrame(DataByte inTypeNibble, + inline MidiInterface& sendTimeCodeQuarterFrame(DataByte inTypeNibble, DataByte inValuesNibble); - inline void sendTimeCodeQuarterFrame(DataByte inData); + inline MidiInterface& sendTimeCodeQuarterFrame(DataByte inData); - inline void sendSongPosition(unsigned inBeats); - inline void sendSongSelect(DataByte inSongNumber); - inline void sendTuneRequest(); + inline MidiInterface& sendSongPosition(unsigned inBeats); + inline MidiInterface& sendSongSelect(DataByte inSongNumber); + inline MidiInterface& sendTuneRequest(); - inline void sendCommon(MidiType inType, unsigned = 0); + inline MidiInterface& sendCommon(MidiType inType, unsigned = 0); - inline void sendClock() { sendRealTime(Clock); }; - inline void sendStart() { sendRealTime(Start); }; - inline void sendStop() { sendRealTime(Stop); }; - inline void sendTick() { sendRealTime(Tick); }; - inline void sendContinue() { sendRealTime(Continue); }; - inline void sendActiveSensing() { sendRealTime(ActiveSensing); }; - inline void sendSystemReset() { sendRealTime(SystemReset); }; + inline MidiInterface& sendClock() { sendRealTime(Clock); }; + inline MidiInterface& sendStart() { sendRealTime(Start); }; + inline MidiInterface& sendStop() { sendRealTime(Stop); }; + inline MidiInterface& sendTick() { sendRealTime(Tick); }; + inline MidiInterface& sendContinue() { sendRealTime(Continue); }; + inline MidiInterface& sendActiveSensing() { sendRealTime(ActiveSensing); }; + inline MidiInterface& sendSystemReset() { sendRealTime(SystemReset); }; - inline void sendRealTime(MidiType inType); + inline MidiInterface& sendRealTime(MidiType inType); - inline void beginRpn(unsigned inNumber, + inline MidiInterface& beginRpn(unsigned inNumber, Channel inChannel); - inline void sendRpnValue(unsigned inValue, + inline MidiInterface& sendRpnValue(unsigned inValue, Channel inChannel); - inline void sendRpnValue(byte inMsb, + inline MidiInterface& sendRpnValue(byte inMsb, byte inLsb, Channel inChannel); - inline void sendRpnIncrement(byte inAmount, + inline MidiInterface& sendRpnIncrement(byte inAmount, Channel inChannel); - inline void sendRpnDecrement(byte inAmount, + inline MidiInterface& sendRpnDecrement(byte inAmount, Channel inChannel); - inline void endRpn(Channel inChannel); + inline MidiInterface& endRpn(Channel inChannel); - inline void beginNrpn(unsigned inNumber, + inline MidiInterface& beginNrpn(unsigned inNumber, Channel inChannel); - inline void sendNrpnValue(unsigned inValue, + inline MidiInterface& sendNrpnValue(unsigned inValue, Channel inChannel); - inline void sendNrpnValue(byte inMsb, + inline MidiInterface& sendNrpnValue(byte inMsb, byte inLsb, Channel inChannel); - inline void sendNrpnIncrement(byte inAmount, + inline MidiInterface& sendNrpnIncrement(byte inAmount, Channel inChannel); - inline void sendNrpnDecrement(byte inAmount, + inline MidiInterface& sendNrpnDecrement(byte inAmount, Channel inChannel); - inline void endNrpn(Channel inChannel); + inline MidiInterface& endNrpn(Channel inChannel); - inline void send(const MidiMessage&); + inline MidiInterface& send(const MidiMessage&); public: - void send(MidiType inType, + MidiInterface& send(MidiType inType, DataByte inData1, DataByte inData2, Channel inChannel); @@ -172,7 +172,7 @@ class MidiInterface public: inline Channel getInputChannel() const; - inline void setInputChannel(Channel inChannel); + inline MidiInterface& setInputChannel(Channel inChannel); public: static inline MidiType getTypeFromStatusByte(byte inStatus); @@ -183,32 +183,32 @@ class MidiInterface // Input Callbacks public: - inline void setHandleMessage(void (*fptr)(const MidiMessage&)) { mMessageCallback = fptr; }; - inline void setHandleError(ErrorCallback fptr) { mErrorCallback = fptr; } - inline void setHandleNoteOff(NoteOffCallback fptr) { mNoteOffCallback = fptr; } - inline void setHandleNoteOn(NoteOnCallback fptr) { mNoteOnCallback = fptr; } - inline void setHandleAfterTouchPoly(AfterTouchPolyCallback fptr) { mAfterTouchPolyCallback = fptr; } - inline void setHandleControlChange(ControlChangeCallback fptr) { mControlChangeCallback = fptr; } - inline void setHandleProgramChange(ProgramChangeCallback fptr) { mProgramChangeCallback = fptr; } - inline void setHandleAfterTouchChannel(AfterTouchChannelCallback fptr) { mAfterTouchChannelCallback = fptr; } - inline void setHandlePitchBend(PitchBendCallback fptr) { mPitchBendCallback = fptr; } - inline void setHandleSystemExclusive(SystemExclusiveCallback fptr) { mSystemExclusiveCallback = fptr; } - inline void setHandleTimeCodeQuarterFrame(TimeCodeQuarterFrameCallback fptr) { mTimeCodeQuarterFrameCallback = fptr; } - inline void setHandleSongPosition(SongPositionCallback fptr) { mSongPositionCallback = fptr; } - inline void setHandleSongSelect(SongSelectCallback fptr) { mSongSelectCallback = fptr; } - inline void setHandleTuneRequest(TuneRequestCallback fptr) { mTuneRequestCallback = fptr; } - inline void setHandleClock(ClockCallback fptr) { mClockCallback = fptr; } - inline void setHandleStart(StartCallback fptr) { mStartCallback = fptr; } - inline void setHandleTick(TickCallback fptr) { mTickCallback = fptr; } - inline void setHandleContinue(ContinueCallback fptr) { mContinueCallback = fptr; } - inline void setHandleStop(StopCallback fptr) { mStopCallback = fptr; } - inline void setHandleActiveSensing(ActiveSensingCallback fptr) { mActiveSensingCallback = fptr; } - inline void setHandleSystemReset(SystemResetCallback fptr) { mSystemResetCallback = fptr; } - - inline void disconnectCallbackFromType(MidiType inType); + inline MidiInterface& setHandleMessage(void (*fptr)(const MidiMessage&)) { mMessageCallback = fptr; return *this; }; + inline MidiInterface& setHandleError(ErrorCallback fptr) { mErrorCallback = fptr; return *this; }; + inline MidiInterface& setHandleNoteOff(NoteOffCallback fptr) { mNoteOffCallback = fptr; return *this; }; + inline MidiInterface& setHandleNoteOn(NoteOnCallback fptr) { mNoteOnCallback = fptr; return *this; }; + inline MidiInterface& setHandleAfterTouchPoly(AfterTouchPolyCallback fptr) { mAfterTouchPolyCallback = fptr; return *this; }; + inline MidiInterface& setHandleControlChange(ControlChangeCallback fptr) { mControlChangeCallback = fptr; return *this; }; + inline MidiInterface& setHandleProgramChange(ProgramChangeCallback fptr) { mProgramChangeCallback = fptr; return *this; }; + inline MidiInterface& setHandleAfterTouchChannel(AfterTouchChannelCallback fptr) { mAfterTouchChannelCallback = fptr; return *this; }; + inline MidiInterface& setHandlePitchBend(PitchBendCallback fptr) { mPitchBendCallback = fptr; return *this; }; + inline MidiInterface& setHandleSystemExclusive(SystemExclusiveCallback fptr) { mSystemExclusiveCallback = fptr; return *this; }; + inline MidiInterface& setHandleTimeCodeQuarterFrame(TimeCodeQuarterFrameCallback fptr) { mTimeCodeQuarterFrameCallback = fptr; return *this; }; + inline MidiInterface& setHandleSongPosition(SongPositionCallback fptr) { mSongPositionCallback = fptr; return *this; }; + inline MidiInterface& setHandleSongSelect(SongSelectCallback fptr) { mSongSelectCallback = fptr; return *this; }; + inline MidiInterface& setHandleTuneRequest(TuneRequestCallback fptr) { mTuneRequestCallback = fptr; return *this; }; + inline MidiInterface& setHandleClock(ClockCallback fptr) { mClockCallback = fptr; return *this; }; + inline MidiInterface& setHandleStart(StartCallback fptr) { mStartCallback = fptr; return *this; }; + inline MidiInterface& setHandleTick(TickCallback fptr) { mTickCallback = fptr; return *this; }; + inline MidiInterface& setHandleContinue(ContinueCallback fptr) { mContinueCallback = fptr; return *this; }; + inline MidiInterface& setHandleStop(StopCallback fptr) { mStopCallback = fptr; return *this; }; + inline MidiInterface& setHandleActiveSensing(ActiveSensingCallback fptr) { mActiveSensingCallback = fptr; return *this; }; + inline MidiInterface& setHandleSystemReset(SystemResetCallback fptr) { mSystemResetCallback = fptr; return *this; }; + + inline MidiInterface& disconnectCallbackFromType(MidiType inType); private: - void launchCallback(); + MidiInterface& launchCallback(); void (*mMessageCallback)(const MidiMessage& message) = nullptr; ErrorCallback mErrorCallback = nullptr; @@ -239,12 +239,12 @@ class MidiInterface inline Thru::Mode getFilterMode() const; inline bool getThruState() const; - inline void turnThruOn(Thru::Mode inThruFilterMode = Thru::Full); - inline void turnThruOff(); - inline void setThruFilterMode(Thru::Mode inThruFilterMode); + inline MidiInterface& turnThruOn(Thru::Mode inThruFilterMode = Thru::Full); + inline MidiInterface& turnThruOff(); + inline MidiInterface& setThruFilterMode(Thru::Mode inThruFilterMode); private: - void thruFilter(byte inChannel); + MidiInterface& thruFilter(byte inChannel); // ------------------------------------------------------------------------- // MIDI Parsing diff --git a/src/MIDI.hpp b/src/MIDI.hpp index e071178b..00f78b41 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -69,7 +69,7 @@ inline MidiInterface::~MidiInterface() - Full thru mirroring */ template -void MidiInterface::begin(Channel inChannel) +MidiInterface& MidiInterface::begin(Channel inChannel) { // Initialise the Transport layer mTransport.begin(); @@ -95,6 +95,8 @@ void MidiInterface::begin(Channel inChannel) mThruFilterMode = Thru::Full; mThruActivated = mTransport.thruActivated; + + return *this; } // ----------------------------------------------------------------------------- @@ -115,7 +117,7 @@ void MidiInterface::begin(Channel inChannel) them thru. */ template -void MidiInterface::send(const MidiMessage& inMessage) +MidiInterface& MidiInterface::send(const MidiMessage& inMessage) { if (!inMessage.valid) return; @@ -142,6 +144,8 @@ void MidiInterface::send(const MidiMessage& inMes } mTransport.endTransmission(); updateLastSentTime(); + + return *this; } @@ -157,7 +161,7 @@ void MidiInterface::send(const MidiMessage& inMes from your code, at your own risks. */ template -void MidiInterface::send(MidiType inType, +MidiInterface& MidiInterface::send(MidiType inType, DataByte inData1, DataByte inData2, Channel inChannel) @@ -209,6 +213,8 @@ void MidiInterface::send(MidiType inType, { sendRealTime(inType); // System Real-time and 1 byte. } + + return *this; } // ----------------------------------------------------------------------------- @@ -223,11 +229,11 @@ void MidiInterface::send(MidiType inType, http://www.phys.unsw.edu.au/jw/notes.html */ template -void MidiInterface::sendNoteOn(DataByte inNoteNumber, +MidiInterface& MidiInterface::sendNoteOn(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel) { - send(NoteOn, inNoteNumber, inVelocity, inChannel); + return send(NoteOn, inNoteNumber, inVelocity, inChannel); } /*! \brief Send a Note Off message @@ -242,11 +248,11 @@ void MidiInterface::sendNoteOn(DataByte inNoteNum http://www.phys.unsw.edu.au/jw/notes.html */ template -void MidiInterface::sendNoteOff(DataByte inNoteNumber, +MidiInterface& MidiInterface::sendNoteOff(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel) { - send(NoteOff, inNoteNumber, inVelocity, inChannel); + return send(NoteOff, inNoteNumber, inVelocity, inChannel); } /*! \brief Send a Program Change message @@ -254,10 +260,10 @@ void MidiInterface::sendNoteOff(DataByte inNoteNu \param inChannel The channel on which the message will be sent (1 to 16). */ template -void MidiInterface::sendProgramChange(DataByte inProgramNumber, +MidiInterface& MidiInterface::sendProgramChange(DataByte inProgramNumber, Channel inChannel) { - send(ProgramChange, inProgramNumber, 0, inChannel); + return send(ProgramChange, inProgramNumber, 0, inChannel); } /*! \brief Send a Control Change message @@ -267,11 +273,11 @@ void MidiInterface::sendProgramChange(DataByte in @see MidiControlChangeNumber */ template -void MidiInterface::sendControlChange(DataByte inControlNumber, +MidiInterface& MidiInterface::sendControlChange(DataByte inControlNumber, DataByte inControlValue, Channel inChannel) { - send(ControlChange, inControlNumber, inControlValue, inChannel); + return send(ControlChange, inControlNumber, inControlValue, inChannel); } /*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) @@ -282,11 +288,11 @@ void MidiInterface::sendControlChange(DataByte in library, @see sendAfterTouch to send polyphonic and monophonic AfterTouch messages. */ template -void MidiInterface::sendPolyPressure(DataByte inNoteNumber, +MidiInterface& MidiInterface::sendPolyPressure(DataByte inNoteNumber, DataByte inPressure, Channel inChannel) { - send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); + return send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); } /*! \brief Send a MonoPhonic AfterTouch message (applies to all notes) @@ -294,10 +300,10 @@ void MidiInterface::sendPolyPressure(DataByte inN \param inChannel The channel on which the message will be sent (1 to 16). */ template -void MidiInterface::sendAfterTouch(DataByte inPressure, +MidiInterface& MidiInterface::sendAfterTouch(DataByte inPressure, Channel inChannel) { - send(AfterTouchChannel, inPressure, 0, inChannel); + return send(AfterTouchChannel, inPressure, 0, inChannel); } /*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) @@ -307,11 +313,11 @@ void MidiInterface::sendAfterTouch(DataByte inPre @see Replaces sendPolyPressure (which is now deprecated). */ template -void MidiInterface::sendAfterTouch(DataByte inNoteNumber, +MidiInterface& MidiInterface::sendAfterTouch(DataByte inNoteNumber, DataByte inPressure, Channel inChannel) { - send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); + return send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); } /*! \brief Send a Pitch Bend message using a signed integer value. @@ -321,11 +327,11 @@ void MidiInterface::sendAfterTouch(DataByte inNot \param inChannel The channel on which the message will be sent (1 to 16). */ template -void MidiInterface::sendPitchBend(int inPitchValue, +MidiInterface& MidiInterface::sendPitchBend(int inPitchValue, Channel inChannel) { const unsigned bend = unsigned(inPitchValue - int(MIDI_PITCHBEND_MIN)); - send(PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, inChannel); + return send(PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, inChannel); } @@ -336,12 +342,12 @@ void MidiInterface::sendPitchBend(int inPitchValu \param inChannel The channel on which the message will be sent (1 to 16). */ template -void MidiInterface::sendPitchBend(double inPitchValue, +MidiInterface& MidiInterface::sendPitchBend(double inPitchValue, Channel inChannel) { const int scale = inPitchValue > 0.0 ? MIDI_PITCHBEND_MAX : - MIDI_PITCHBEND_MIN; const int value = int(inPitchValue * double(scale)); - sendPitchBend(value, inChannel); + return sendPitchBend(value, inChannel); } /*! \brief Generate and send a System Exclusive frame. @@ -354,7 +360,7 @@ void MidiInterface::sendPitchBend(double inPitchV with previous versions of the library. */ template -void MidiInterface::sendSysEx(unsigned inLength, +MidiInterface& MidiInterface::sendSysEx(unsigned inLength, const byte* inArray, bool inArrayContainsBoundaries) { @@ -377,6 +383,8 @@ void MidiInterface::sendSysEx(unsigned inLength, if (Settings::UseRunningStatus) mRunningStatus_TX = InvalidType; + + return *this; } /*! \brief Send a Tune Request message. @@ -385,9 +393,9 @@ void MidiInterface::sendSysEx(unsigned inLength, it should tune its oscillators (if equipped with any). */ template -void MidiInterface::sendTuneRequest() +MidiInterface& MidiInterface::sendTuneRequest() { - sendCommon(TuneRequest); + return sendCommon(TuneRequest); } /*! \brief Send a MIDI Time Code Quarter Frame. @@ -397,11 +405,11 @@ void MidiInterface::sendTuneRequest() See MIDI Specification for more information. */ template -void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, +MidiInterface& MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, DataByte inValuesNibble) { const byte data = byte((((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0f))); - sendTimeCodeQuarterFrame(data); + return sendTimeCodeQuarterFrame(data); } /*! \brief Send a MIDI Time Code Quarter Frame. @@ -411,25 +419,25 @@ void MidiInterface::sendTimeCodeQuarterFrame(Data you can send the byte here. */ template -void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) +MidiInterface& MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) { - sendCommon(TimeCodeQuarterFrame, inData); + return sendCommon(TimeCodeQuarterFrame, inData); } /*! \brief Send a Song Position Pointer message. \param inBeats The number of beats since the start of the song. */ template -void MidiInterface::sendSongPosition(unsigned inBeats) +MidiInterface& MidiInterface::sendSongPosition(unsigned inBeats) { - sendCommon(SongPosition, inBeats); + return sendCommon(SongPosition, inBeats); } /*! \brief Send a Song Select message */ template -void MidiInterface::sendSongSelect(DataByte inSongNumber) +MidiInterface& MidiInterface::sendSongSelect(DataByte inSongNumber) { - sendCommon(SongSelect, inSongNumber); + return sendCommon(SongSelect, inSongNumber); } /*! \brief Send a Common message. Common messages reset the running status. @@ -440,7 +448,7 @@ void MidiInterface::sendSongSelect(DataByte inSon \param inData1 The byte that goes with the common message. */ template -void MidiInterface::sendCommon(MidiType inType, unsigned inData1) +MidiInterface& MidiInterface::sendCommon(MidiType inType, unsigned inData1) { switch (inType) { @@ -482,6 +490,8 @@ void MidiInterface::sendCommon(MidiType inType, u if (Settings::UseRunningStatus) mRunningStatus_TX = InvalidType; + + return *this; } /*! \brief Send a Real Time (one byte) message. @@ -491,7 +501,7 @@ void MidiInterface::sendCommon(MidiType inType, u @see MidiType */ template -void MidiInterface::sendRealTime(MidiType inType) +MidiInterface& MidiInterface::sendRealTime(MidiType inType) { // Do not invalidate Running Status for real-time messages // as they can be interleaved within any message. @@ -515,6 +525,8 @@ void MidiInterface::sendRealTime(MidiType inType) // Invalid Real Time marker break; } + + return *this; } /*! \brief Start a Registered Parameter Number frame. @@ -522,7 +534,7 @@ void MidiInterface::sendRealTime(MidiType inType) \param inChannel The channel on which the message will be sent (1 to 16). */ template -inline void MidiInterface::beginRpn(unsigned inNumber, +inline MidiInterface& MidiInterface::beginRpn(unsigned inNumber, Channel inChannel) { if (mCurrentRpnNumber != inNumber) @@ -533,6 +545,8 @@ inline void MidiInterface::beginRpn(unsigned inNu sendControlChange(RPNMSB, numMsb, inChannel); mCurrentRpnNumber = inNumber; } + + return *this; } /*! \brief Send a 14-bit value for the currently selected RPN number. @@ -540,13 +554,15 @@ inline void MidiInterface::beginRpn(unsigned inNu \param inChannel The channel on which the message will be sent (1 to 16). */ template -inline void MidiInterface::sendRpnValue(unsigned inValue, +inline MidiInterface& MidiInterface::sendRpnValue(unsigned inValue, Channel inChannel) {; const byte valMsb = 0x7f & (inValue >> 7); const byte valLsb = 0x7f & inValue; sendControlChange(DataEntryMSB, valMsb, inChannel); sendControlChange(DataEntryLSB, valLsb, inChannel); + + return *this; } /*! \brief Send separate MSB/LSB values for the currently selected RPN number. @@ -555,32 +571,38 @@ inline void MidiInterface::sendRpnValue(unsigned \param inChannel The channel on which the message will be sent (1 to 16). */ template -inline void MidiInterface::sendRpnValue(byte inMsb, +inline MidiInterface& MidiInterface::sendRpnValue(byte inMsb, byte inLsb, Channel inChannel) { sendControlChange(DataEntryMSB, inMsb, inChannel); sendControlChange(DataEntryLSB, inLsb, inChannel); + + return *this; } /* \brief Increment the value of the currently selected RPN number by the specified amount. \param inAmount The amount to add to the currently selected RPN value. */ template -inline void MidiInterface::sendRpnIncrement(byte inAmount, +inline MidiInterface& MidiInterface::sendRpnIncrement(byte inAmount, Channel inChannel) { sendControlChange(DataIncrement, inAmount, inChannel); + + return *this; } /* \brief Decrement the value of the currently selected RPN number by the specified amount. \param inAmount The amount to subtract to the currently selected RPN value. */ template -inline void MidiInterface::sendRpnDecrement(byte inAmount, +inline MidiInterface& MidiInterface::sendRpnDecrement(byte inAmount, Channel inChannel) { sendControlChange(DataDecrement, inAmount, inChannel); + + return *this; } /*! \brief Terminate an RPN frame. @@ -588,11 +610,13 @@ This will send a Null Function to deselect the currently selected RPN. \param inChannel The channel on which the message will be sent (1 to 16). */ template -inline void MidiInterface::endRpn(Channel inChannel) +inline MidiInterface& MidiInterface::endRpn(Channel inChannel) { sendControlChange(RPNLSB, 0x7f, inChannel); sendControlChange(RPNMSB, 0x7f, inChannel); mCurrentRpnNumber = 0xffff; + + return *this; } @@ -602,7 +626,7 @@ inline void MidiInterface::endRpn(Channel inChann \param inChannel The channel on which the message will be sent (1 to 16). */ template -inline void MidiInterface::beginNrpn(unsigned inNumber, +inline MidiInterface& MidiInterface::beginNrpn(unsigned inNumber, Channel inChannel) { if (mCurrentNrpnNumber != inNumber) @@ -613,6 +637,8 @@ inline void MidiInterface::beginNrpn(unsigned inN sendControlChange(NRPNMSB, numMsb, inChannel); mCurrentNrpnNumber = inNumber; } + + return *this; } /*! \brief Send a 14-bit value for the currently selected NRPN number. @@ -620,13 +646,15 @@ inline void MidiInterface::beginNrpn(unsigned inN \param inChannel The channel on which the message will be sent (1 to 16). */ template -inline void MidiInterface::sendNrpnValue(unsigned inValue, +inline MidiInterface& MidiInterface::sendNrpnValue(unsigned inValue, Channel inChannel) -{; +{ const byte valMsb = 0x7f & (inValue >> 7); const byte valLsb = 0x7f & inValue; sendControlChange(DataEntryMSB, valMsb, inChannel); sendControlChange(DataEntryLSB, valLsb, inChannel); + + return *this; } /*! \brief Send separate MSB/LSB values for the currently selected NRPN number. @@ -635,32 +663,38 @@ inline void MidiInterface::sendNrpnValue(unsigned \param inChannel The channel on which the message will be sent (1 to 16). */ template -inline void MidiInterface::sendNrpnValue(byte inMsb, +inline MidiInterface& MidiInterface::sendNrpnValue(byte inMsb, byte inLsb, Channel inChannel) { sendControlChange(DataEntryMSB, inMsb, inChannel); sendControlChange(DataEntryLSB, inLsb, inChannel); + + return *this; } /* \brief Increment the value of the currently selected NRPN number by the specified amount. \param inAmount The amount to add to the currently selected NRPN value. */ template -inline void MidiInterface::sendNrpnIncrement(byte inAmount, +inline MidiInterface& MidiInterface::sendNrpnIncrement(byte inAmount, Channel inChannel) { sendControlChange(DataIncrement, inAmount, inChannel); + + return *this; } /* \brief Decrement the value of the currently selected NRPN number by the specified amount. \param inAmount The amount to subtract to the currently selected NRPN value. */ template -inline void MidiInterface::sendNrpnDecrement(byte inAmount, +inline MidiInterface& MidiInterface::sendNrpnDecrement(byte inAmount, Channel inChannel) { sendControlChange(DataDecrement, inAmount, inChannel); + + return *this; } /*! \brief Terminate an NRPN frame. @@ -668,11 +702,13 @@ This will send a Null Function to deselect the currently selected NRPN. \param inChannel The channel on which the message will be sent (1 to 16). */ template -inline void MidiInterface::endNrpn(Channel inChannel) +inline MidiInterface& MidiInterface::endNrpn(Channel inChannel) { sendControlChange(NRPNLSB, 0x7f, inChannel); sendControlChange(NRPNMSB, 0x7f, inChannel); mCurrentNrpnNumber = 0xffff; + + return *this; } template @@ -1212,9 +1248,11 @@ inline Channel MidiInterface::getInputChannel() c if you want to listen to all channels, and MIDI_CHANNEL_OFF to disable input. */ template -inline void MidiInterface::setInputChannel(Channel inChannel) +inline MidiInterface& MidiInterface::setInputChannel(Channel inChannel) { mInputChannel = inChannel; + + return *this; } // ----------------------------------------------------------------------------- @@ -1269,7 +1307,7 @@ bool MidiInterface::isChannelMessage(MidiType inT When a message of this type is received, no function will be called. */ template -void MidiInterface::disconnectCallbackFromType(MidiType inType) +MidiInterface& MidiInterface::disconnectCallbackFromType(MidiType inType) { switch (inType) { @@ -1295,13 +1333,15 @@ void MidiInterface::disconnectCallbackFromType(Mi default: break; } + + return *this; } /*! @} */ // End of doc group MIDI Callbacks // Private - launch callback function based on received type. template -void MidiInterface::launchCallback() +MidiInterface& MidiInterface::launchCallback() { if (mMessageCallback != 0) mMessageCallback(mMessage); @@ -1361,10 +1401,12 @@ void MidiInterface::launchCallback() @see Thru::Mode */ template -inline void MidiInterface::setThruFilterMode(Thru::Mode inThruFilterMode) +inline MidiInterface& MidiInterface::setThruFilterMode(Thru::Mode inThruFilterMode) { mThruFilterMode = inThruFilterMode; mThruActivated = mThruFilterMode != Thru::Off; + + return *this; } template @@ -1380,17 +1422,21 @@ inline bool MidiInterface::getThruState() const } template -inline void MidiInterface::turnThruOn(Thru::Mode inThruFilterMode) +inline MidiInterface& MidiInterface::turnThruOn(Thru::Mode inThruFilterMode) { mThruActivated = true; mThruFilterMode = inThruFilterMode; + + return *this; } template -inline void MidiInterface::turnThruOff() +inline MidiInterface& MidiInterface::turnThruOff() { mThruActivated = false; mThruFilterMode = Thru::Off; + + return *this; } @@ -1403,7 +1449,7 @@ inline void MidiInterface::turnThruOff() // - Channel messages are passed to the output whether their channel // is matching the input channel and the filter setting template -void MidiInterface::thruFilter(Channel inChannel) +MidiInterface& MidiInterface::thruFilter(Channel inChannel) { // If the feature is disabled, don't do anything. if (!mThruActivated || (mThruFilterMode == Thru::Off)) @@ -1486,6 +1532,8 @@ void MidiInterface::thruFilter(Channel inChannel) break; // LCOV_EXCL_LINE - Unreacheable code, but prevents unhandled case warning. } } + + return *this; } END_MIDI_NAMESPACE From e1035a03f75f377ed2840f3a1da0236f3465bf9d Mon Sep 17 00:00:00 2001 From: lathoub Date: Sun, 10 Oct 2021 12:13:30 +0200 Subject: [PATCH 43/54] Update MIDI.hpp fixed errors (return -> return *this where needed) --- src/MIDI.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 00f78b41..3e5be14c 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -120,7 +120,7 @@ template MidiInterface& MidiInterface::send(const MidiMessage& inMessage) { if (!inMessage.valid) - return; + return *this; if (mTransport.beginTransmission(inMessage.type)) { @@ -173,7 +173,7 @@ MidiInterface& MidiInterface& MidiInterface& MidiInterface Date: Sun, 10 Oct 2021 12:16:15 +0200 Subject: [PATCH 44/54] Update MIDI.hpp --- src/MIDI.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 3e5be14c..7f92c788 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -1453,7 +1453,7 @@ MidiInterface& MidiInterface= NoteOff && mMessage.type <= PitchBend) From c7922927e5e6f8c3992dfb9901a2ac6d9a0c78e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 10 Oct 2021 14:01:47 +0200 Subject: [PATCH 45/54] chore: Fix PlatformIO checks See platformio/platformio-core#4078 --- .github/workflows/platformio.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/platformio.yml b/.github/workflows/platformio.yml index 64331444..b604f765 100644 --- a/.github/workflows/platformio.yml +++ b/.github/workflows/platformio.yml @@ -53,6 +53,7 @@ jobs: 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: From c9d3b9b5926abc63c24a8442c48a168cb599993f Mon Sep 17 00:00:00 2001 From: lathoub Date: Sun, 10 Oct 2021 15:33:28 +0200 Subject: [PATCH 46/54] no chaining for private functions, added example --- examples/Chaining/Chaining.ino | 37 ++++++++++++++++++++++++++++++++++ src/MIDI.h | 4 ++-- src/MIDI.hpp | 8 +++----- 3 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 examples/Chaining/Chaining.ino diff --git a/examples/Chaining/Chaining.ino b/examples/Chaining/Chaining.ino new file mode 100644 index 00000000..be36ee97 --- /dev/null +++ b/examples/Chaining/Chaining.ino @@ -0,0 +1,37 @@ +#include + +MIDI_CREATE_DEFAULT_INSTANCE(); + +void setup() +{ + pinMode(2, INPUT); + + MIDI // chaining MIDI commands - order is from top to bottom (turnThruOff,... begin) + .turnThruOff() + // using a lamdba function for this callbacks + .setHandleNoteOn([](byte channel, byte note, byte velocity) + { + // Do whatever you want when a note is pressed. + + // Try to keep your callbacks short (no delays ect) + // otherwise it would slow down the loop() and have a bad impact + // on real-time performance. + }) + .setHandleNoteOff([](byte channel, byte note, byte velocity) + { + // Do something when the note is released. + // Note that NoteOn messages with 0 velocity are interpreted as NoteOffs. + }) + .begin(MIDI_CHANNEL_OMNI); // Initiate MIDI communications, listen to all channels +} + +void loop() +{ + // Call MIDI.read the fastest you can for real-time performance. + MIDI.read(); + + if (digitalRead(2)) + MIDI // chained sendNoteOn commands + .sendNoteOn(42, 127, 1) + .sendNoteOn(40, 54, 1); +} diff --git a/src/MIDI.h b/src/MIDI.h index ebdccd0c..f1e49f06 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -208,7 +208,7 @@ class MidiInterface inline MidiInterface& disconnectCallbackFromType(MidiType inType); private: - MidiInterface& launchCallback(); + void launchCallback(); void (*mMessageCallback)(const MidiMessage& message) = nullptr; ErrorCallback mErrorCallback = nullptr; @@ -244,7 +244,7 @@ class MidiInterface inline MidiInterface& setThruFilterMode(Thru::Mode inThruFilterMode); private: - MidiInterface& thruFilter(byte inChannel); + void thruFilter(byte inChannel); // ------------------------------------------------------------------------- // MIDI Parsing diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 7f92c788..bb9ffc1c 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -1341,7 +1341,7 @@ MidiInterface& MidiInterface -MidiInterface& MidiInterface::launchCallback() +void MidiInterface::launchCallback() { if (mMessageCallback != 0) mMessageCallback(mMessage); @@ -1449,11 +1449,11 @@ inline MidiInterface& MidiInterface -MidiInterface& MidiInterface::thruFilter(Channel inChannel) +void MidiInterface::thruFilter(Channel inChannel) { // If the feature is disabled, don't do anything. if (!mThruActivated || (mThruFilterMode == Thru::Off)) - return *this; + return; // First, check if the received message is Channel if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) @@ -1532,8 +1532,6 @@ MidiInterface& MidiInterface Date: Sun, 10 Oct 2021 15:48:29 +0200 Subject: [PATCH 47/54] Update platformio.yml added Chaining example --- .github/workflows/platformio.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/platformio.yml b/.github/workflows/platformio.yml index 64331444..5f1c4998 100644 --- a/.github/workflows/platformio.yml +++ b/.github/workflows/platformio.yml @@ -17,6 +17,7 @@ jobs: - Basic_IO - Bench - Callbacks + - Chaining - DualMerger - ErrorCallback - Input From be557c1f4b4e0924aa02a4c39cd89b447b919307 Mon Sep 17 00:00:00 2001 From: lathoub Date: Sun, 10 Oct 2021 17:04:46 +0200 Subject: [PATCH 48/54] Update MIDI.h added return value (missed by PR) --- src/MIDI.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index f1e49f06..767feb9c 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -110,13 +110,13 @@ class MidiInterface inline MidiInterface& sendCommon(MidiType inType, unsigned = 0); - inline MidiInterface& sendClock() { sendRealTime(Clock); }; - inline MidiInterface& sendStart() { sendRealTime(Start); }; - inline MidiInterface& sendStop() { sendRealTime(Stop); }; - inline MidiInterface& sendTick() { sendRealTime(Tick); }; - inline MidiInterface& sendContinue() { sendRealTime(Continue); }; - inline MidiInterface& sendActiveSensing() { sendRealTime(ActiveSensing); }; - inline MidiInterface& sendSystemReset() { sendRealTime(SystemReset); }; + inline MidiInterface& sendClock() { return sendRealTime(Clock); }; + inline MidiInterface& sendStart() { return sendRealTime(Start); }; + inline MidiInterface& sendStop() { return sendRealTime(Stop); }; + inline MidiInterface& sendTick() { return sendRealTime(Tick); }; + inline MidiInterface& sendContinue() { return sendRealTime(Continue); }; + inline MidiInterface& sendActiveSensing() { return sendRealTime(ActiveSensing); }; + inline MidiInterface& sendSystemReset() { return sendRealTime(SystemReset); }; inline MidiInterface& sendRealTime(MidiType inType); From 663b24b0b56085ad07697a5f4e18ed3fe89bf7a2 Mon Sep 17 00:00:00 2001 From: Paul <43148870+paul-emile-element@users.noreply.github.com> Date: Wed, 3 Nov 2021 10:04:52 -0400 Subject: [PATCH 49/54] fix: Updated send(midiMessage&) to better handle system real time (#250) * updated send(midiMessage&) to better handle system real time, sysex and other system common messages * use fully qualified function names as requested Co-authored-by: Element --- src/MIDI.hpp | 26 +++++++++++++++----------- src/midi_Message.h | 12 ++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index bb9ffc1c..be7c28a1 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -124,22 +124,25 @@ MidiInterface& MidiInterface 1) mTransport.write(inMessage.data1); if (inMessage.length > 2) mTransport.write(inMessage.data2); - } else + } else if (inMessage.type == MidiType::SystemExclusive) { - // sysexArray does not contain the start and end tags - mTransport.write(MidiType::SystemExclusiveStart); - - for (size_t i = 0; i < inMessage.getSysExSize(); i++) + const unsigned size = inMessage.getSysExSize(); + for (size_t i = 0; i < size; i++) mTransport.write(inMessage.sysexArray[i]); - - mTransport.write(MidiType::SystemExclusiveEnd); + } else // at this point, it it assumed to be a system common message + { + mTransport.write(inMessage.type); + if (inMessage.length > 1) mTransport.write(inMessage.data1); + if (inMessage.length > 2) mTransport.write(inMessage.data2); } } mTransport.endTransmission(); @@ -1083,6 +1086,7 @@ bool MidiInterface::parse() mMessage.data1 = mPendingMessage[1]; // Save data2 only if applicable mMessage.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0; + mMessage.length = mPendingMessageExpectedLength; // Reset local variables mPendingMessageIndex = 0; diff --git a/src/midi_Message.h b/src/midi_Message.h index bbd8d6a1..0879fa37 100644 --- a/src/midi_Message.h +++ b/src/midi_Message.h @@ -114,6 +114,18 @@ struct Message const unsigned size = unsigned(data2) << 8 | data1; return size > sSysExMaxSize ? sSysExMaxSize : size; } + inline bool isSystemRealTime () const + { + return (type & 0xf8) == 0xf8; + } + inline bool isSystemCommon () const + { + return (type & 0xf8) == 0xf0; + } + inline bool isChannelMessage () const + { + return (type & 0xf0) != 0xf0; + } }; END_MIDI_NAMESPACE From 1944aca891df74be1a989bafba5fe618e4d9bd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Wed, 3 Nov 2021 15:06:11 +0100 Subject: [PATCH 50/54] chore: Add @paul-emile-element as contributor --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a20be2f..db948c6b 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ Special thanks to all who have contributed to this open-source project ! - [@DavidMenting](https://github.com/DavidMenting) - [@Rolel](https://github.com/Rolel) - [@kant](https://github.com/kant) +- [@paul-emile-element](https://github.com/paul-emile-element) You want to help ? Check out the [contribution guidelines](./CONTRIBUTING.md). From 8e9805fc926f3b116df4579ff0b0b72bc0c97ec7 Mon Sep 17 00:00:00 2001 From: Mikhail Diatchenko Date: Mon, 29 Nov 2021 22:54:16 +1300 Subject: [PATCH 51/54] feat: Added missing MidiControlChangeNumber definitions (#260) * Added missing MidiControlChangeNumber definitions * Fixed duplicate enum * Added keywords for new definitions --- keywords.txt | 11 +++++++++++ src/midi_Defs.h | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/keywords.txt b/keywords.txt index cfb39f4f..845d7987 100644 --- a/keywords.txt +++ b/keywords.txt @@ -146,7 +146,18 @@ GeneralPurposeController1 LITERAL1 GeneralPurposeController2 LITERAL1 GeneralPurposeController3 LITERAL1 GeneralPurposeController4 LITERAL1 +BankSelectLSB LITERAL1 +ModulationWheelLSB LITERAL1 +BreathControllerLSB LITERAL1 +FootControllerLSB LITERAL1 +PortamentoTimeLSB LITERAL1 DataEntryLSB LITERAL1 +ChannelVolumeLSB LITERAL1 +BalanceLSB LITERAL1 +PanLSB LITERAL1 +ExpressionControllerLSB LITERAL1 +EffectControl1LSB LITERAL1 +EffectControl2LSB LITERAL1 Sustain LITERAL1 Portamento LITERAL1 Sostenuto LITERAL1 diff --git a/src/midi_Defs.h b/src/midi_Defs.h index ef74621c..1da019d8 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -164,8 +164,22 @@ enum MidiControlChangeNumber: uint8_t GeneralPurposeController2 = 17, GeneralPurposeController3 = 18, GeneralPurposeController4 = 19, - + // CC20 to CC31 undefined + BankSelectLSB = 32, + ModulationWheelLSB = 33, + BreathControllerLSB = 34, + // CC35 undefined + FootControllerLSB = 36, + PortamentoTimeLSB = 37, DataEntryLSB = 38, + ChannelVolumeLSB = 39, + BalanceLSB = 40, + // CC41 undefined + PanLSB = 42, + ExpressionControllerLSB = 43, + EffectControl1LSB = 44, + EffectControl2LSB = 45, + // CC46 to CC63 undefined // Switches ---------------------------------------------------------------- Sustain = 64, @@ -203,6 +217,7 @@ enum MidiControlChangeNumber: uint8_t NRPNMSB = 99, ///< Non-Registered Parameter Number (MSB) RPNLSB = 100, ///< Registered Parameter Number (LSB) RPNMSB = 101, ///< Registered Parameter Number (MSB) + // CC102 to CC119 undefined // Channel Mode messages --------------------------------------------------- AllSoundOff = 120, From 8f8c7cfcc62df16f5982eff3f9e961137790774e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Tue, 30 Nov 2021 09:22:26 +0100 Subject: [PATCH 52/54] doc: Add @muxa to the contributors list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index db948c6b..7f557f35 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,7 @@ Special thanks to all who have contributed to this open-source project ! - [@Rolel](https://github.com/Rolel) - [@kant](https://github.com/kant) - [@paul-emile-element](https://github.com/paul-emile-element) +- [@muxa](https://github.com/muxa) You want to help ? Check out the [contribution guidelines](./CONTRIBUTING.md). From 7c0d716641b7260c6746a3d126c84bc04dc92024 Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Tue, 11 Jan 2022 07:24:58 +0100 Subject: [PATCH 53/54] added CustomBaudRate example (#262) * added CustomBaudRate example - added customer baudrate example - fixed old refs to examples in Doxygen * fixed compile error for boards that have no Serial --- .github/workflows/platformio.yml | 1 + doc/midi_DoxygenMainPage.h | 12 ++++----- examples/CustomBaudRate/CustomBaudRate.ino | 31 ++++++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 examples/CustomBaudRate/CustomBaudRate.ino diff --git a/.github/workflows/platformio.yml b/.github/workflows/platformio.yml index 5ccfad38..b4f4891e 100644 --- a/.github/workflows/platformio.yml +++ b/.github/workflows/platformio.yml @@ -23,6 +23,7 @@ jobs: - Input - RPN_NRPN - SimpleSynth + - CustomBaudRate board: - uno - due diff --git a/doc/midi_DoxygenMainPage.h b/doc/midi_DoxygenMainPage.h index 4ece5d01..4e27fa4b 100644 --- a/doc/midi_DoxygenMainPage.h +++ b/doc/midi_DoxygenMainPage.h @@ -12,7 +12,7 @@ // Examples /*! - \example MIDI_Basic_IO.ino + \example Basic_IO.ino This example shows how to perform simple input and output MIDI. \n \n When any message arrives to the Arduino, the LED is turned on, @@ -29,15 +29,15 @@ */ /*! - \example MIDI_Callbacks.ino + \example Callbacks.ino This example shows how to use callbacks for easier MIDI input handling. \n */ /*! - \example MIDI_Bench.ino - \example MIDI_DualMerger.ino - \example MIDI_Input.ino - \example MIDI_SimpleSynth.ino + \example Bench.ino + \example DualMerger.ino + \example Input.ino + \example SimpleSynth.ino */ // ----------------------------------------------------------------------------- diff --git a/examples/CustomBaudRate/CustomBaudRate.ino b/examples/CustomBaudRate/CustomBaudRate.ino new file mode 100644 index 00000000..912eabbf --- /dev/null +++ b/examples/CustomBaudRate/CustomBaudRate.ino @@ -0,0 +1,31 @@ +#include + +// Override the default MIDI baudrate to +// a decoding program such as Hairless MIDI (set baudrate to 115200) + +struct CustomBaudRate : public MIDI_NAMESPACE::DefaultSettings { + static const long BaudRate = 115200; +}; + +#if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) + // Leonardo, Due and other USB boards use Serial1 by default. + MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial1, MIDI, CustomBaudRate); +#else + MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial, MIDI, CustomBaudRate); +#endif + +void setup() { + pinMode(LED_BUILTIN, OUTPUT); + MIDI.begin(MIDI_CHANNEL_OMNI); +} + +void loop() { + if (MIDI.read()) // If we have received a message + { + digitalWrite(LED_BUILTIN, HIGH); + MIDI.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1) + delay(1000); // Wait for a second + MIDI.sendNoteOff(42, 0, 1); // Stop the note + digitalWrite(LED_BUILTIN, LOW); + } +} \ No newline at end of file From 2d64cc3c2ff85bbee654a7054e36c59694d8d8e4 Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Tue, 11 Jan 2022 08:00:48 +0100 Subject: [PATCH 54/54] Update CustomBaudRate.ino override DefaultSerialSettings to override BaudRate (and not DefaultSettings ) --- examples/CustomBaudRate/CustomBaudRate.ino | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/CustomBaudRate/CustomBaudRate.ino b/examples/CustomBaudRate/CustomBaudRate.ino index 912eabbf..d554871d 100644 --- a/examples/CustomBaudRate/CustomBaudRate.ino +++ b/examples/CustomBaudRate/CustomBaudRate.ino @@ -2,16 +2,17 @@ // Override the default MIDI baudrate to // a decoding program such as Hairless MIDI (set baudrate to 115200) - -struct CustomBaudRate : public MIDI_NAMESPACE::DefaultSettings { +struct CustomBaudRateSettings : public MIDI_NAMESPACE::DefaultSerialSettings { static const long BaudRate = 115200; }; #if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) // Leonardo, Due and other USB boards use Serial1 by default. - MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial1, MIDI, CustomBaudRate); + MIDI_NAMESPACE::SerialMIDI serialMIDI(Serial1); + MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); #else - MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial, MIDI, CustomBaudRate); + MIDI_NAMESPACE::SerialMIDI serialMIDI(Serial); + MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); #endif void setup() { @@ -28,4 +29,4 @@ void loop() { MIDI.sendNoteOff(42, 0, 1); // Stop the note digitalWrite(LED_BUILTIN, LOW); } -} \ No newline at end of file +}