From 3396ec942997c5f7e303a1a2adad8e6c3698053b Mon Sep 17 00:00:00 2001 From: Lennart Date: Mon, 17 Jul 2017 18:55:45 +0200 Subject: [PATCH 01/48] fixed some warnings when compiling with -Wconversion and -Wsign-conversion --- src/MIDI.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 9951261d..3ee7c8fb 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -375,7 +375,7 @@ template void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, DataByte inValuesNibble) { - const byte data = (((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0f)); + const byte data = byte((((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0f))); sendTimeCodeQuarterFrame(data); } @@ -620,7 +620,7 @@ template StatusByte MidiInterface::getStatus(MidiType inType, Channel inChannel) const { - return ((byte)inType | ((inChannel - 1) & 0x0f)); + return StatusByte(((byte)inType | ((inChannel - 1) & 0x0f))); } // ----------------------------------------------------------------------------- @@ -856,7 +856,7 @@ bool MidiInterface::parse() // Get length mMessage.data1 = mPendingMessageIndex & 0xff; // LSB - mMessage.data2 = mPendingMessageIndex >> 8; // MSB + mMessage.data2 = byte(mPendingMessageIndex >> 8); // MSB mMessage.channel = 0; mMessage.valid = true; @@ -1116,7 +1116,7 @@ MidiType MidiInterface::getTypeFromStatusByte(byte inStatu template inline Channel MidiInterface::getChannelFromStatusByte(byte inStatus) { - return (inStatus & 0x0f) + 1; + return Channel((inStatus & 0x0f) + 1); } template @@ -1221,7 +1221,7 @@ void MidiInterface::launchCallback() // Occasional messages case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != 0) mTimeCodeQuarterFrameCallback(mMessage.data1); break; - case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7)); break; + case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback(unsigned((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7))); break; case SongSelect: if (mSongSelectCallback != 0) mSongSelectCallback(mMessage.data1); break; case TuneRequest: if (mTuneRequestCallback != 0) mTuneRequestCallback(); break; From 9b9905fb2ac644b12990973f0c6f3a4e8fbd4678 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 10 Mar 2018 15:44:46 +0100 Subject: [PATCH 02/48] chore: Bump version to 4.4.0 --- doc/Doxyfile | 2 +- library.json | 4 ++-- library.properties | 2 +- src/midi_Defs.h | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 5773a06e..07f94d41 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.3.1" +PROJECT_NUMBER = "Version 4.4.0" # 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 ad0375b3..bf270c54 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "MIDI Library", - "version": "4.3.1", + "version": "4.4.0", "keywords": "midi", "description": "Enables MIDI I/O communications on the Arduino serial ports", "license": "MIT", @@ -17,7 +17,7 @@ "url": "https://github.com/FortySevenEffects/arduino_midi_library.git", "branch": "master" }, - "export": { + "export": { "include": [ "src", "examples" diff --git a/library.properties b/library.properties index de8a53f7..2e67670f 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=MIDI Library -version=4.3.1 +version=4.4.0 author=Forty Seven Effects maintainer=Francois Best sentence=MIDI I/Os for Arduino diff --git a/src/midi_Defs.h b/src/midi_Defs.h index 1c2aeaa6..d0f86895 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -38,9 +38,9 @@ typedef uint8_t byte; BEGIN_MIDI_NAMESPACE -#define MIDI_LIBRARY_VERSION 0x040300 +#define MIDI_LIBRARY_VERSION 0x040400 #define MIDI_LIBRARY_VERSION_MAJOR 4 -#define MIDI_LIBRARY_VERSION_MINOR 3 +#define MIDI_LIBRARY_VERSION_MINOR 4 #define MIDI_LIBRARY_VERSION_PATCH 0 // ----------------------------------------------------------------------------- From 1c01effbd929c709116c92d7e1a2ba6c3c2068e2 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 10 Mar 2018 16:58:17 +0100 Subject: [PATCH 03/48] feat: Handle Korg-style of flipping SysEx data header bits Closes #92 --- doc/sysex-codec.md | 87 ++++++ src/MIDI.cpp | 20 +- src/MIDI.h | 10 +- .../tests/unit-tests_SysExCodec.cpp | 258 +++++++++++------- 4 files changed, 267 insertions(+), 108 deletions(-) create mode 100644 doc/sysex-codec.md diff --git a/doc/sysex-codec.md b/doc/sysex-codec.md new file mode 100644 index 00000000..7d2ddddc --- /dev/null +++ b/doc/sysex-codec.md @@ -0,0 +1,87 @@ +# SysEx Encoding & Decoding + +There are various ways of encoding & decoding arbitrary 8-bit wide data into +SysEx, which is 7-bit wide. + +The [official documentation](http://www.somascape.org/midi/tech/spec.html#nusx_fd) +for FileDump data exchanges states the following: + + > The 8-bit file data needs to be converted to 7-bit form, + > with the result that every 7 bytes of file data translates + > to 8 bytes in the MIDI stream. + > + > For each group of 7 bytes (of file data) the top bit from each + > is used to construct an eigth byte, which is sent first. + > So: + > ``` + > AAAAaaaa BBBBbbbb CCCCcccc DDDDdddd EEEEeeee FFFFffff GGGGgggg + > ``` + > becomes: + > ``` + > 0ABCDEFG 0AAAaaaa 0BBBbbbb 0CCCcccc 0DDDdddd 0EEEeeee 0FFFffff 0GGGgggg + > ``` + > + > The final group may have less than 7 bytes, and is coded as follows + > (e.g. with 3 bytes in the final group): + > ``` + > 0ABC0000 0AAAaaaa 0BBBbbbb 0CCCcccc + > ``` + +## SysEx encoding / decoding functions + +The MIDI library supplies two functions to do this, `encodeSysEx` and `decodeSysEx`. + +Example usage: +```c++ +#include + +static const byte myData[12] = { + // Hex dump: CAFEBABE BAADF00D FACADE42 + 0xca, 0xfe, 0xba, 0xbe, 0xba, 0xad, 0xf0, 0x0d, + 0xfa, 0xca, 0xde, 0x42 +}; + +byte encoded[16]; +const unsigned encodedSize = midi::encodeSysEx(myData, encoded, 12); +// Encoded hex dump: 07 4a 7e 3a 3e 3a 2d 70 07 0d 7a 4a 5e 42 + +byte decoded[12]; +const unsigned decoded = midi::decodeSysEx(encoded, decoded, encodedSize); +``` + +## Special case for Korg devices + +Korg apparently uses another convention for their SysEx encoding / decoding, +where: +``` +AAAAaaaa BBBBbbbb CCCCcccc DDDDdddd EEEEeeee FFFFffff GGGGgggg +``` +becomes: +``` +0GFEDCBA 0AAAaaaa 0BBBbbbb 0CCCcccc 0DDDdddd 0EEEeeee 0FFFffff 0GGGgggg +``` + +The order of the bits in the "header" byte is reversed. +To follow this beheaviour, set the inFlipHeaderBits argument to true. + +Example: +```c++ +void handleSysEx(byte* inData, unsigned inSize) +{ + // SysEx body data starts at 3rd byte: F0 42 aa bb cc dd F7 + // 42 being the hex value of the Korg SysEx ID. + const unsigned dataStartOffset = 2; + const unsigned encodedDataLength = inSize - 3; // Remove F0 42 & F7 + + // Create a large enough buffer where to decode the message + byte decodedData[64]; + + const unsigned decodedSize = decodeSysEx(inData + dataStartOffset, + decodedData, + encodedDataLength, + true); // flip header bits + // Do stuff with your message +} +``` + +See original discussion in issue #92. diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 77618213..c5b88246 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -38,11 +38,15 @@ BEGIN_MIDI_NAMESPACE \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. + \param inFlipHeaderBits True for Korg and other who store MSB in reverse order \return The lenght of the encoded output buffer. @see decodeSysEx Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com */ -unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLength) +unsigned encodeSysEx(const byte* inData, + byte* outSysEx, + unsigned inLength, + bool inFlipHeaderBits) { unsigned outLength = 0; // Num bytes in output array. byte count = 0; // Num 7bytes in a block. @@ -54,7 +58,7 @@ unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLength) const byte msb = data >> 7; const byte body = data & 0x7f; - outSysEx[0] |= (msb << (6 - count)); + outSysEx[0] |= (msb << (inFlipHeaderBits ? count : (6 - count))); outSysEx[1 + count] = body; if (count++ == 6) @@ -75,11 +79,15 @@ unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLength) \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. + \param inFlipHeaderBits True for Korg and other who store MSB in reverse order \return The lenght of the output buffer. @see encodeSysEx @see getSysExArrayLength Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com */ -unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLength) +unsigned decodeSysEx(const byte* inSysEx, + byte* outData, + unsigned inLength, + bool inFlipHeaderBits) { unsigned count = 0; byte msbStorage = 0; @@ -94,8 +102,10 @@ unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLength) } else { - const byte body = inSysEx[i]; - const byte msb = ((msbStorage >> byteIndex--) & 1) << 7; + const byte body = inSysEx[i]; + const byte shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex; + const byte msb = ((msbStorage >> shift) & 1) << 7; + byteIndex--; outData[count++] = msb | body; } } diff --git a/src/MIDI.h b/src/MIDI.h index 5fe356c1..e00bf982 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -250,8 +250,14 @@ 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 inLenght, + bool inFlipHeaderBits = false); +unsigned decodeSysEx(const byte* inSysEx, + byte* outData, + unsigned inLenght, + bool inFlipHeaderBits = false); END_MIDI_NAMESPACE diff --git a/test/unit-tests/tests/unit-tests_SysExCodec.cpp b/test/unit-tests/tests/unit-tests_SysExCodec.cpp index e1b9428e..ab544dcc 100644 --- a/test/unit-tests/tests/unit-tests_SysExCodec.cpp +++ b/test/unit-tests/tests/unit-tests_SysExCodec.cpp @@ -11,115 +11,171 @@ BEGIN_UNNAMED_NAMESPACE using namespace testing; -TEST(SysExCodec, Encoder) +TEST(SysExCodec, EncoderAscii) { - // ASCII content - { - const byte input[] = "Hello, World!"; - byte buffer[16]; - memset(buffer, 0, 16 * sizeof(byte)); - const unsigned encodedSize = midi::encodeSysEx(input, buffer, 13); - EXPECT_EQ(encodedSize, unsigned(15)); - const byte expected[16] = { - 0, 'H', 'e', 'l', 'l', 'o', ',', ' ', - 0, 'W', 'o', 'r', 'l', 'd', '!', 0, - }; - EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 - EXPECT_THAT(buffer, ContainerEq(expected)); - } - // Non-ASCII content - { - const byte input[] = { - 182, 236, 167, 177, 61, 91, 120, // 01111000 -> 120 - 107, 94, 209, 87, 94 // 000100xx -> 16 - }; - byte buffer[16]; - memset(buffer, 0, 16 * sizeof(byte)); - const unsigned encodedSize = midi::encodeSysEx(input, buffer, 12); - EXPECT_EQ(encodedSize, unsigned(14)); - const byte expected[16] = { - // MSB Data - 120, 54, 108, 39, 49, 61, 91, 120, - 16, 107, 94, 81, 87, 94, 0, 0, - }; - EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 - EXPECT_THAT(buffer, ContainerEq(expected)); - } + const byte input[] = "Hello, World!"; + byte buffer[16]; + memset(buffer, 0, 16 * sizeof(byte)); + const unsigned encodedSize = midi::encodeSysEx(input, buffer, 13); + EXPECT_EQ(encodedSize, unsigned(15)); + const byte expected[16] = { + 0, 'H', 'e', 'l', 'l', 'o', ',', ' ', + 0, 'W', 'o', 'r', 'l', 'd', '!', 0, + }; + EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 + EXPECT_THAT(buffer, ContainerEq(expected)); } -TEST(SysExCodec, Decoder) +TEST(SysExCodec, EncoderNonAscii) { - // ASCII content - { - const byte input[] = { - 0, 'H', 'e', 'l', 'l', 'o', ',', ' ', - 0, 'W', 'o', 'r', 'l', 'd', '!', - }; - byte buffer[16]; - memset(buffer, 0, 16 * sizeof(byte)); - const unsigned decodedSize = midi::decodeSysEx(input, buffer, 15); - EXPECT_EQ(decodedSize, unsigned(13)); - const byte expected[16] = { - 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', - 'o', 'r', 'l', 'd', '!', 0, 0, 0, - }; - EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 - EXPECT_THAT(buffer, ContainerEq(expected)); - } - // Non-ASCII content - { - const byte input[] = { - // MSB Data - 120, 54, 108, 39, 49, 61, 91, 120, - 16, 107, 94, 81, 87, 94, - }; - byte buffer[16]; - memset(buffer, 0, 16 * sizeof(byte)); - const unsigned encodedSize = midi::decodeSysEx(input, buffer, 14); - EXPECT_EQ(encodedSize, unsigned(12)); - const byte expected[16] = { - 182, 236, 167, 177, 61, 91, 120, - 107, 94, 209, 87, 94, 0, 0, - 0, 0, - }; - EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127 - EXPECT_THAT(buffer, ContainerEq(expected)); - } + const byte input[] = { + 182, 236, 167, 177, 61, 91, 120, // 01111000 -> 120 + 107, 94, 209, 87, 94 // 000100xx -> 16 + }; + byte buffer[16]; + memset(buffer, 0, 16 * sizeof(byte)); + const unsigned encodedSize = midi::encodeSysEx(input, buffer, 12); + EXPECT_EQ(encodedSize, unsigned(14)); + const byte expected[16] = { + // MSB Data + 120, 54, 108, 39, 49, 61, 91, 120, + 16, 107, 94, 81, 87, 94, 0, 0, + }; + EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 + EXPECT_THAT(buffer, ContainerEq(expected)); +} + +TEST(SysExCodec, EncoderNonAsciiFlipHeader) +{ + const byte input[] = { + 182, 236, 167, 177, 61, 91, 120, // 00011111 -> 15 + 107, 94, 209, 87, 94 // 0xx00100 -> 4 + }; + byte buffer[16]; + memset(buffer, 0, 16 * sizeof(byte)); + const unsigned encodedSize = midi::encodeSysEx(input, buffer, 12, true); + EXPECT_EQ(encodedSize, unsigned(14)); + const byte expected[16] = { + // MSB Data + 15, 54, 108, 39, 49, 61, 91, 120, + 4, 107, 94, 81, 87, 94, 0, 0, + }; + EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 + EXPECT_THAT(buffer, ContainerEq(expected)); } -TEST(SysExCodec, Codec) +// ----------------------------------------------------------------------------- + +TEST(SysExCodec, DecoderAscii) { - // ASCII content - { - const byte input[] = "Hello, World!"; - byte buffer1[16]; - byte buffer2[16]; - memset(buffer1, 0, 16 * sizeof(byte)); - memset(buffer2, 0, 16 * sizeof(byte)); - const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 13); - EXPECT_EQ(encodedSize, unsigned(15)); - const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize); - EXPECT_EQ(decodedSize, unsigned(13)); - EXPECT_STREQ(reinterpret_cast(buffer2), - reinterpret_cast(input)); - } + const byte input[] = { + 0, 'H', 'e', 'l', 'l', 'o', ',', ' ', + 0, 'W', 'o', 'r', 'l', 'd', '!', + }; + byte buffer[16]; + memset(buffer, 0, 16 * sizeof(byte)); + const unsigned decodedSize = midi::decodeSysEx(input, buffer, 15); + EXPECT_EQ(decodedSize, unsigned(13)); + const byte expected[16] = { + 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', + 'o', 'r', 'l', 'd', '!', 0, 0, 0, + }; + EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 + EXPECT_THAT(buffer, ContainerEq(expected)); +} + // Non-ASCII content - { - const byte input[] = { - // MSB Data - 182, 236, 167, 177, 61, 91, 120, - 107, 94, 209, 87, 94 - }; - byte buffer1[14]; - byte buffer2[12]; - memset(buffer1, 0, 14 * sizeof(byte)); - memset(buffer2, 0, 12 * sizeof(byte)); - const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 12); - EXPECT_EQ(encodedSize, unsigned(14)); - const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize); - EXPECT_EQ(decodedSize, unsigned(12)); - EXPECT_THAT(buffer2, ContainerEq(input)); - } +TEST(SysExCodec, DecoderNonAscii) +{ + const byte input[] = { + // MSB Data + 120, 54, 108, 39, 49, 61, 91, 120, + 16, 107, 94, 81, 87, 94, + }; + byte buffer[16]; + memset(buffer, 0, 16 * sizeof(byte)); + const unsigned encodedSize = midi::decodeSysEx(input, buffer, 14); + EXPECT_EQ(encodedSize, unsigned(12)); + const byte expected[16] = { + 182, 236, 167, 177, 61, 91, 120, + 107, 94, 209, 87, 94, 0, 0, + 0, 0, + }; + EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127 + EXPECT_THAT(buffer, ContainerEq(expected)); +} + +TEST(SysExCodec, DecoderNonAsciiFlipHeader) +{ + const byte input[] = { + // MSB Data + 15, 54, 108, 39, 49, 61, 91, 120, + 4, 107, 94, 81, 87, 94, + }; + byte buffer[16]; + memset(buffer, 0, 16 * sizeof(byte)); + const unsigned encodedSize = midi::decodeSysEx(input, buffer, 14, true); + EXPECT_EQ(encodedSize, unsigned(12)); + const byte expected[16] = { + 182, 236, 167, 177, 61, 91, 120, + 107, 94, 209, 87, 94, 0, 0, + 0, 0, + }; + EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127 + EXPECT_THAT(buffer, ContainerEq(expected)); +} + +// ----------------------------------------------------------------------------- + +TEST(SysExCodec, CodecAscii) +{ + const byte input[] = "Hello, World!"; + byte buffer1[16]; + byte buffer2[16]; + memset(buffer1, 0, 16 * sizeof(byte)); + memset(buffer2, 0, 16 * sizeof(byte)); + const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 13); + EXPECT_EQ(encodedSize, unsigned(15)); + const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize); + EXPECT_EQ(decodedSize, unsigned(13)); + EXPECT_STREQ(reinterpret_cast(buffer2), + reinterpret_cast(input)); +} + +TEST(SysExCodec, CodecNonAscii) +{ + const byte input[] = { + // MSB Data + 182, 236, 167, 177, 61, 91, 120, + 107, 94, 209, 87, 94 + }; + byte buffer1[14]; + byte buffer2[12]; + memset(buffer1, 0, 14 * sizeof(byte)); + memset(buffer2, 0, 12 * sizeof(byte)); + const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 12); + EXPECT_EQ(encodedSize, unsigned(14)); + const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize); + EXPECT_EQ(decodedSize, unsigned(12)); + EXPECT_THAT(buffer2, ContainerEq(input)); +} + +TEST(SysExCodec, CodecNonAsciiFlipHeader) +{ + const byte input[] = { + // MSB Data + 182, 236, 167, 177, 61, 91, 120, + 107, 94, 209, 87, 94 + }; + byte buffer1[14]; + byte buffer2[12]; + memset(buffer1, 0, 14 * sizeof(byte)); + memset(buffer2, 0, 12 * sizeof(byte)); + const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 12, true); + EXPECT_EQ(encodedSize, unsigned(14)); + const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize, true); + EXPECT_EQ(decodedSize, unsigned(12)); + EXPECT_THAT(buffer2, ContainerEq(input)); } END_UNNAMED_NAMESPACE From 064c9d956836d47a2e239fb0c74403a52067e45c Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 10 Mar 2018 17:07:55 +0100 Subject: [PATCH 04/48] fix: Issue link in documentation [ci skip] --- 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 7d2ddddc..47395cd3 100644 --- a/doc/sysex-codec.md +++ b/doc/sysex-codec.md @@ -84,4 +84,4 @@ void handleSysEx(byte* inData, unsigned inSize) } ``` -See original discussion in issue #92. +See original discussion in issue [#92](FortySevenEffects/arduino_midi_library#92). From d9cad6034f17823dfeb26c73668eca55648a42f4 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 10 Mar 2018 17:10:47 +0100 Subject: [PATCH 05/48] chore: Upgrade Google Test to release-1.8.0 --- external/google-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/google-test b/external/google-test index a2b8a8e0..ec44c6c1 160000 --- a/external/google-test +++ b/external/google-test @@ -1 +1 @@ -Subproject commit a2b8a8e07628e5fd60644b6dd99c1b5e7d7f1f47 +Subproject commit ec44c6c1675c25b9827aacd08c02433cccde7780 From c5833214a162d9ef24694dd81ffcbbdbfefcac61 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 10 Mar 2018 17:44:16 +0100 Subject: [PATCH 06/48] feat: Increase warning level for sources Don't do it globally as externals may have warnings we can't do much about. --- builder/CMakeLists.txt | 17 ++++++++++++++++- src/CMakeLists.txt | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/builder/CMakeLists.txt b/builder/CMakeLists.txt index 036c701e..5f367f2d 100644 --- a/builder/CMakeLists.txt +++ b/builder/CMakeLists.txt @@ -8,7 +8,18 @@ macro(setup_builder) include_directories(${ROOT_SOURCE_DIR}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -W -Wshadow -Wunused-variable -Wunused-parameter -Wunused-function -Wunused -Wno-system-headers -Wno-deprecated -Woverloaded-virtual") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ + -Wall \ + -W \ + -Wshadow \ + -Wunused-variable \ + -Wunused-parameter \ + -Wunused-function \ + -Wunused \ + -Wno-system-headers \ + -Wno-deprecated \ + -Woverloaded-virtual \ + ") if (BUILDER_ENABLE_PROFILING) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} --coverage") @@ -16,3 +27,7 @@ macro(setup_builder) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") endmacro() + +macro(increase_warning_level) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wconversion -Wsign-conversion") +endmacro() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ef00cf1f..66f3355d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,5 @@ +increase_warning_level() + project(midi) add_library(midi STATIC From 33bd77dd13f24d8f7923c3601446a71df0d846c4 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 10 Mar 2018 17:59:17 +0100 Subject: [PATCH 07/48] fix: Fix more sign / implicit type conversion warnings --- src/MIDI.cpp | 2 +- src/MIDI.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 77618213..e14a98f8 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -95,7 +95,7 @@ unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLength) else { const byte body = inSysEx[i]; - const byte msb = ((msbStorage >> byteIndex--) & 1) << 7; + const byte msb = byte(((msbStorage >> byteIndex--) & 1) << 7); outData[count++] = msb | body; } } diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 3ee7c8fb..fa34533e 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -292,7 +292,7 @@ template void MidiInterface::sendPitchBend(int inPitchValue, Channel inChannel) { - const unsigned bend = inPitchValue - MIDI_PITCHBEND_MIN; + const unsigned bend = unsigned(inPitchValue - int(MIDI_PITCHBEND_MIN)); send(PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, inChannel); } From 1cd638361efedbfc83b10c7f698a796e8e9ebaca Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 10 Mar 2018 18:43:15 +0100 Subject: [PATCH 08/48] chore: Update VSCode C++ helper file --- .vscode/c_cpp_properties.json | 54 ++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 7c19b39d..1ed9b10f 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -6,26 +6,48 @@ "/Applications/Arduino.app/Contents/Java/hardware/tools/avr/include", "/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino" ], - "browse" : { - "limitSymbolsToIncludedHeaders" : true, - "databaseFilename" : "" - } + "browse": { + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "", + "path": [ + "/Applications/Arduino.app/Contents/Java/hardware/tools/avr/include", + "/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino" + ] + }, + "intelliSenseMode": "clang-x64", + "macFrameworkPath": [ + "/System/Library/Frameworks", + "/Library/Frameworks" + ] }, { "name": "Linux", - "includePath": ["/usr/include"], - "browse" : { - "limitSymbolsToIncludedHeaders" : true, - "databaseFilename" : "" - } + "includePath": [ + "/usr/include" + ], + "browse": { + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "", + "path": [ + "/usr/include" + ] + }, + "intelliSenseMode": "clang-x64" }, { "name": "Win32", - "includePath": ["c:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include"], - "browse" : { - "limitSymbolsToIncludedHeaders" : true, - "databaseFilename" : "" - } + "includePath": [ + "c:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include" + ], + "browse": { + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "", + "path": [ + "c:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include" + ] + }, + "intelliSenseMode": "msvc-x64" } - ] -} + ], + "version": 3 +} \ No newline at end of file From 83ad031ee6cbd6bb9f28f5f317b71d96393851cf Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 11 Oct 2018 10:31:23 +0200 Subject: [PATCH 09/48] fix: Add tests and fix RingBuffer implementation --- src/midi_RingBuffer.h | 12 +- src/midi_RingBuffer.hpp | 62 ++++--- test/unit-tests/CMakeLists.txt | 1 + .../tests/unit-tests_RingBuffer.cpp | 174 ++++++++++++++++++ .../tests/unit-tests_SerialMock.cpp | 6 +- 5 files changed, 221 insertions(+), 34 deletions(-) create mode 100644 test/unit-tests/tests/unit-tests_RingBuffer.cpp diff --git a/src/midi_RingBuffer.h b/src/midi_RingBuffer.h index da2266c5..3627d5d6 100644 --- a/src/midi_RingBuffer.h +++ b/src/midi_RingBuffer.h @@ -34,13 +34,16 @@ BEGIN_MIDI_NAMESPACE template class RingBuffer { +private: + static const int sMask = Size - 1; + public: RingBuffer(); ~RingBuffer(); public: - int getLength() const; - bool isEmpty() const; + inline int getLength() const; + inline bool isEmpty() const; public: void write(DataType inData); @@ -53,8 +56,9 @@ class RingBuffer private: DataType mData[Size]; - DataType* mWriteHead; - DataType* mReadHead; + int mLength; + int mWriteHead; + int mReadHead; }; END_MIDI_NAMESPACE diff --git a/src/midi_RingBuffer.hpp b/src/midi_RingBuffer.hpp index 9c273c94..2dbdbbeb 100644 --- a/src/midi_RingBuffer.hpp +++ b/src/midi_RingBuffer.hpp @@ -27,13 +27,27 @@ #pragma once +BEGIN_UNNAMED_NAMESPACE + +template +struct isPowerOfTwo +{ + static const bool value = N && !(N & (N - 1)); +}; + +END_UNNAMED_NAMESPACE + +// -- + BEGIN_MIDI_NAMESPACE template RingBuffer::RingBuffer() - : mWriteHead(mData) - , mReadHead(mData) + : mLength(0) + , mWriteHead(0) + , mReadHead(0) { + static_assert(isPowerOfTwo::value, "Size must be a power of two."); memset(mData, DataType(0), Size * sizeof(DataType)); } @@ -45,26 +59,15 @@ RingBuffer::~RingBuffer() // ----------------------------------------------------------------------------- template -int RingBuffer::getLength() const +inline int RingBuffer::getLength() const { - if (mReadHead == mWriteHead) - { - return 0; - } - else if (mWriteHead > mReadHead) - { - return int(mWriteHead - mReadHead); - } - else - { - return int(mWriteHead - mData) + Size - int(mReadHead - mData); - } + return mLength; } template -bool RingBuffer::isEmpty() const +inline bool RingBuffer::isEmpty() const { - return mReadHead == mWriteHead; + return mLength == 0; } // ----------------------------------------------------------------------------- @@ -72,10 +75,12 @@ bool RingBuffer::isEmpty() const template void RingBuffer::write(DataType inData) { - *mWriteHead++ = inData; - if (mWriteHead >= mData + Size) - { - mWriteHead = mData; + mData[mWriteHead] = inData; + mWriteHead = (mWriteHead + 1) & sMask; + mLength++; + if (mLength > Size) { + mLength = Size; + mReadHead = (mReadHead + 1) & sMask; } } @@ -92,8 +97,9 @@ template void RingBuffer::clear() { memset(mData, DataType(0), Size * sizeof(DataType)); - mReadHead = mData; - mWriteHead = mData; + mReadHead = 0; + mWriteHead = 0; + mLength = 0; } // ----------------------------------------------------------------------------- @@ -101,11 +107,13 @@ void RingBuffer::clear() template DataType RingBuffer::read() { - const DataType data = *mReadHead++; - if (mReadHead >= mData + Size) - { - mReadHead = mData; + mLength--; + if (mLength < 0) { + mLength = 0; + return 0; } + const DataType data = mData[mReadHead]; + mReadHead = (mReadHead + 1) & sMask; return data; } diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index 39f4bb3d..a57b161a 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(unit-tests tests/unit-tests_Settings.cpp tests/unit-tests_Settings.h tests/unit-tests_SysExCodec.cpp + tests/unit-tests_RingBuffer.cpp tests/unit-tests_SerialMock.cpp tests/unit-tests_MidiInput.cpp tests/unit-tests_MidiInputCallbacks.cpp diff --git a/test/unit-tests/tests/unit-tests_RingBuffer.cpp b/test/unit-tests/tests/unit-tests_RingBuffer.cpp new file mode 100644 index 00000000..d33352d8 --- /dev/null +++ b/test/unit-tests/tests/unit-tests_RingBuffer.cpp @@ -0,0 +1,174 @@ +#include "unit-tests.h" +#include + +BEGIN_UNNAMED_NAMESPACE +using namespace testing; +using Buffer = midi::RingBuffer; + +TEST(RingBuffer, writeScalar) +{ + Buffer buffer; + EXPECT_EQ(buffer.isEmpty(), true); + EXPECT_EQ(buffer.getLength(), 0); + buffer.write(42); + buffer.write(47); + EXPECT_EQ(buffer.isEmpty(), false); + EXPECT_EQ(buffer.getLength(), 2); +} + +TEST(RingBuffer, readScalar) +{ + Buffer buffer; + buffer.write(42); + EXPECT_EQ(buffer.getLength(), 1); + buffer.write(47); + EXPECT_EQ(buffer.getLength(), 2); + EXPECT_EQ(buffer.read(), 42); + EXPECT_EQ(buffer.getLength(), 1); + EXPECT_EQ(buffer.read(), 47); + EXPECT_EQ(buffer.isEmpty(), true); + EXPECT_EQ(buffer.getLength(), 0); +} + +TEST(RingBuffer, clear) +{ + Buffer buffer; + buffer.write(42); + buffer.write(47); + buffer.clear(); + EXPECT_EQ(buffer.isEmpty(), true); + EXPECT_EQ(buffer.getLength(), 0); +} + +TEST(RingBuffer, writeArray) +{ + Buffer buffer; + const uint8_t input[4] = { + 1, 2, 3, 4 + }; + buffer.write(input, 4); + EXPECT_EQ(buffer.isEmpty(), false); + EXPECT_EQ(buffer.getLength(), 4); +} + +TEST(RingBuffer, writeOverflow) +{ + Buffer buffer; + + buffer.write(1); + EXPECT_EQ(buffer.getLength(), 1); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(2); + EXPECT_EQ(buffer.getLength(), 2); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(3); + EXPECT_EQ(buffer.getLength(), 3); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(4); + EXPECT_EQ(buffer.getLength(), 4); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(5); + EXPECT_EQ(buffer.getLength(), 5); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(6); + EXPECT_EQ(buffer.getLength(), 6); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(7); + EXPECT_EQ(buffer.getLength(), 7); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(8); + EXPECT_EQ(buffer.getLength(), 8); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(9); + EXPECT_EQ(buffer.getLength(), 8); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(10); + EXPECT_EQ(buffer.getLength(), 8); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(11); + EXPECT_EQ(buffer.getLength(), 8); + EXPECT_EQ(buffer.isEmpty(), false); + buffer.write(12); + EXPECT_EQ(buffer.getLength(), 8); + EXPECT_EQ(buffer.isEmpty(), false); +} + +TEST(RingBuffer, readOverflow) +{ + Buffer buffer; + + buffer.write(1); + buffer.write(2); + buffer.write(3); + buffer.write(4); + buffer.write(5); + buffer.write(6); + buffer.write(7); + buffer.write(8); + buffer.write(9); + buffer.write(10); + buffer.write(11); + buffer.write(12); + EXPECT_EQ(buffer.read(), 5); + EXPECT_EQ(buffer.getLength(), 7); + EXPECT_EQ(buffer.read(), 6); + EXPECT_EQ(buffer.getLength(), 6); + EXPECT_EQ(buffer.read(), 7); + EXPECT_EQ(buffer.getLength(), 5); + EXPECT_EQ(buffer.read(), 8); + EXPECT_EQ(buffer.getLength(), 4); + EXPECT_EQ(buffer.read(), 9); + EXPECT_EQ(buffer.getLength(), 3); + EXPECT_EQ(buffer.read(), 10); + EXPECT_EQ(buffer.getLength(), 2); + EXPECT_EQ(buffer.read(), 11); + EXPECT_EQ(buffer.getLength(), 1); + EXPECT_EQ(buffer.read(), 12); + EXPECT_EQ(buffer.getLength(), 0); + EXPECT_EQ(buffer.read(), 0); + EXPECT_EQ(buffer.getLength(), 0); +} + +TEST(RingBuffer, writeArrayOverflow) +{ + Buffer buffer; + const uint8_t input[12] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + }; + buffer.write(input, 12); + EXPECT_EQ(buffer.isEmpty(), false); + EXPECT_EQ(buffer.getLength(), 8); // Max size +} + +TEST(RingBuffer, readArray) +{ + Buffer buffer; + const uint8_t input[4] = { + 1, 2, 3, 4 + }; + uint8_t output[4] = { 0 }; + buffer.write(input, 4); + buffer.read(output, 4); + EXPECT_THAT(output, ContainerEq(input)); +} + +TEST(RingBuffer, readArrayOverflow) +{ + Buffer buffer; + const uint8_t input[12] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + }; + const uint8_t expected[8] = { + 5, 6, 7, 8, 9, 10, 11, 12, + }; + uint8_t output[8] = { 0 }; + + buffer.write(input, 12); + buffer.read(output, 8); + EXPECT_THAT(output, ContainerEq(expected)); + EXPECT_EQ(buffer.isEmpty(), true); + EXPECT_EQ(buffer.getLength(), 0); +} + +END_UNNAMED_NAMESPACE + diff --git a/test/unit-tests/tests/unit-tests_SerialMock.cpp b/test/unit-tests/tests/unit-tests_SerialMock.cpp index b3555c0e..ae204fe2 100644 --- a/test/unit-tests/tests/unit-tests_SerialMock.cpp +++ b/test/unit-tests/tests/unit-tests_SerialMock.cpp @@ -6,7 +6,7 @@ BEGIN_UNNAMED_NAMESPACE USING_NAMESPACE_TEST_MOCKS using namespace testing; -TEST(RingBuffer, initialState) +TEST(RingBufferMock, initialState) { typedef RingBuffer Buffer; Buffer buffer; @@ -17,7 +17,7 @@ TEST(RingBuffer, initialState) EXPECT_EQ(buffer.isEmpty(), true); } -TEST(RingBuffer, uint8) +TEST(RingBufferMock, uint8) { typedef RingBuffer Buffer; Buffer buffer; @@ -48,7 +48,7 @@ TEST(RingBuffer, uint8) EXPECT_EQ(buffer.isEmpty(), true); } -TEST(RingBuffer, uint32) +TEST(RingBufferMock, uint32) { typedef RingBuffer Buffer; Buffer buffer; From 04ae12d9ad76bc4d77d589f0959eae3ab8be03f7 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 11 Oct 2018 10:32:26 +0200 Subject: [PATCH 10/48] chore: Add VSCode config files & editor settings --- .editorconfig | 9 +++++++++ .vscode/arduino.json | 3 +++ .vscode/c_cpp_properties.json | 9 +++++++-- .vscode/settings.json | 8 +++++--- .vscode/tasks.json | 28 ++++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 .editorconfig create mode 100644 .vscode/arduino.json create mode 100644 .vscode/tasks.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..3c44241c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.vscode/arduino.json b/.vscode/arduino.json new file mode 100644 index 00000000..bf33024e --- /dev/null +++ b/.vscode/arduino.json @@ -0,0 +1,3 @@ +{ + "board": "arduino:avr:leonardo" +} \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index b5ed78e8..81b45d30 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,6 +3,8 @@ { "name": "Mac", "includePath": [ + "/Applications/Arduino.app/Contents/Java/tools/**", + "/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/**", "/Applications/Arduino.app/Contents/Java/hardware/tools/avr/include", "/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino", "${workspaceRoot}" @@ -20,6 +22,9 @@ "macFrameworkPath": [ "/System/Library/Frameworks", "/Library/Frameworks" + ], + "forcedInclude": [ + "/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino/Arduino.h" ] }, { @@ -55,5 +60,5 @@ "intelliSenseMode": "msvc-x64" } ], - "version": 3 -} + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index b305399f..73c61a52 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ -// Place your settings in this file to overwrite default and user settings. { - "cmake.experimental.enableTargetDebugging": true -} + "files.associations": { + "cstddef": "cpp", + "ostream": "cpp" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..7992b3de --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "command": "make", + "args": ["all"], + "options": { + "cwd": "${workspaceRoot}/build" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Run Tests", + "command": "${workspaceRoot}/build/test/unit-tests/unit-tests", + "group": { + "kind": "test", + "isDefault": true + }, + "dependsOn": ["Build"] + } + ] +} From 42b98a78cfa12d57253f50f232ad0edfef48b10b Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 11 Oct 2018 10:33:07 +0200 Subject: [PATCH 11/48] refactor: Avoid redundant call --- src/MIDI.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index fa34533e..7df1afb1 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -676,8 +676,10 @@ template bool MidiInterface::parse() { if (mSerial.available() == 0) + { // No data available. return false; + } // Parsing algorithm: // Get a byte from the serial buffer. @@ -726,7 +728,9 @@ bool MidiInterface::parse() // It will be updated upon completion of this message. } - switch (getTypeFromStatusByte(mPendingMessage[0])) + const MidiType pendingType = getTypeFromStatusByte(mPendingMessage[0]); + + switch (pendingType) { // 1 byte messages case Start: @@ -737,7 +741,7 @@ bool MidiInterface::parse() case SystemReset: case TuneRequest: // Handle the message type directly here. - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.type = pendingType; mMessage.channel = 0; mMessage.data1 = 0; mMessage.data2 = 0; @@ -788,7 +792,7 @@ bool MidiInterface::parse() if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) { // Reception complete - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.type = pendingType; mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); mMessage.data1 = mPendingMessage[1]; mMessage.data2 = 0; // Completed new message has 1 data byte From db0f65a46e32e63a7e62ada417d2245b73b1e4cb Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 11 Oct 2018 11:37:40 +0200 Subject: [PATCH 12/48] fix: Unnamed namespace macro is not available here --- src/midi_RingBuffer.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/midi_RingBuffer.hpp b/src/midi_RingBuffer.hpp index 2dbdbbeb..e1f48e76 100644 --- a/src/midi_RingBuffer.hpp +++ b/src/midi_RingBuffer.hpp @@ -27,15 +27,15 @@ #pragma once -BEGIN_UNNAMED_NAMESPACE +namespace { -template -struct isPowerOfTwo -{ - static const bool value = N && !(N & (N - 1)); -}; + template + struct isPowerOfTwo + { + static const bool value = N && !(N & (N - 1)); + }; -END_UNNAMED_NAMESPACE +} // -- From b9397337d209d6b570720529484bb4d1864c5fea Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 11 Oct 2018 11:45:46 +0200 Subject: [PATCH 13/48] doc: Update links [skip ci] --- README.md | 60 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index e42d171f..3b8608ea 100644 --- a/README.md +++ b/README.md @@ -8,56 +8,58 @@ This library enables MIDI I/O communications on the Arduino serial ports. ### Features -* 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). -* Software Thru, with message filtering. -* [Callbacks](http://playground.arduino.cc/Main/MIDILibraryCallbacks) to handle input messages more easily. -* Last received message is saved until a new one arrives. -* Configurable: [overridable template-based settings](https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-custom-Settings). -* Create more than one MIDI port for mergers/splitters applications. -* Use any serial port, hardware or software. + +- 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). +- Software Thru, with message filtering. +- [Callbacks](https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks) to handle input messages more easily. +- Last received message is saved until a new one arrives. +- Configurable: [overridable template-based settings](https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-custom-Settings). +- Create more than one MIDI port for mergers/splitters applications. +- Use any serial port, hardware or software. ### Getting Started 1. Use Arduino's 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.jpg) 2. Start coding: - ```c++ - #include - // Created and binds the MIDI interface to the default hardware Serial port - MIDI_CREATE_DEFAULT_INSTANCE(); + ```c++ + #include + + // Created and binds the MIDI interface to the default hardware Serial port + MIDI_CREATE_DEFAULT_INSTANCE(); - void setup() - { - MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages - } + void setup() + { + MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages + } - void loop() - { - // Send note 42 with velocity 127 on channel 1 - MIDI.sendNoteOn(42, 127, 1); + void loop() + { + // Send note 42 with velocity 127 on channel 1 + MIDI.sendNoteOn(42, 127, 1); - // Read incoming messages - MIDI.read(); - } + // Read incoming messages + MIDI.read(); + } ``` 3. Read the [documentation](#documentation) or watch the awesome video tutorials from [Notes & Volts](https://www.youtube.com/playlist?list=PL4_gPbvyebyH2xfPXePHtx8gK5zPBrVkg). ## Documentation -- [Doxygen Extended Documentation](http://fortyseveneffects.github.io/arduino_midi_library/). -- [GitHub wiki](https://github.com/FortySevenEffects/arduino_midi_library/wiki). +- [Doxygen Extended Documentation](https://fortyseveneffects.github.io/arduino_midi_library/). +- [GitHub wiki](https://github.com/FortySevenEffects/arduino_midi_library/wiki). ## 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). -You can also get informations about bug fixes and updates on my twitter account: [@fortysevenfx](http://twitter.com/fortysevenfx). +You can also contact me on Twitter: [@fortysevenfx](https://twitter.com/fortysevenfx). ## License -MIT © 2016 [Francois Best](http://fortyseveneffects.com) +MIT © 2009 - present [Francois Best](https://francoisbest.com) From 9a0907ea7fe20427232c0024cf6a88a400812d4a Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 3 Nov 2018 18:56:11 +0100 Subject: [PATCH 14/48] chore: Point MIDI-USB submodule to own fork for unit testing --- .gitmodules | 2 +- external/midi-usb | 2 +- test/unit-tests/CMakeLists.txt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index c1c2a776..d94196e9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/google/googletest.git [submodule "external/midi-usb"] path = external/midi-usb - url = https://github.com/arduino-libraries/MIDIUSB.git + url = https://github.com/FortySevenEffects/MIDIUSB.git diff --git a/external/midi-usb b/external/midi-usb index 1e2508d5..8cbf89bd 160000 --- a/external/midi-usb +++ b/external/midi-usb @@ -1 +1 @@ -Subproject commit 1e2508d529e9626efb1b006b3d80d6c20a191e38 +Subproject commit 8cbf89bdb94ab65db35c86038098c3ee5b22298d diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index a57b161a..06dc4f97 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -6,6 +6,7 @@ include_directories( ${unit-tests_SOURCE_DIR} ${gtest_SOURCE_DIR}/include ${gmock_SOURCE_DIR}/include + "../../external/midi-usb/src" ) add_executable(unit-tests From a34087c92c8c956a3ab2f9036015ee3551fcbd7b Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 3 Nov 2018 18:56:42 +0100 Subject: [PATCH 15/48] feat: Add peek and pop methods --- src/midi_RingBuffer.h | 2 ++ src/midi_RingBuffer.hpp | 16 ++++++++++ .../tests/unit-tests_RingBuffer.cpp | 29 +++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/midi_RingBuffer.h b/src/midi_RingBuffer.h index 3627d5d6..348ad9e2 100644 --- a/src/midi_RingBuffer.h +++ b/src/midi_RingBuffer.h @@ -48,9 +48,11 @@ class RingBuffer public: void write(DataType inData); void write(const DataType* inData, int inSize); + void pop(int inNumberOfItems = 1); void clear(); public: + DataType peek(int inOffset = 0) const; DataType read(); void read(DataType* outData, int inSize); diff --git a/src/midi_RingBuffer.hpp b/src/midi_RingBuffer.hpp index e1f48e76..f3c53eba 100644 --- a/src/midi_RingBuffer.hpp +++ b/src/midi_RingBuffer.hpp @@ -93,6 +93,15 @@ void RingBuffer::write(const DataType* inData, int inSize) } } +template +void RingBuffer::pop(int inNumberOfItems) +{ + for (int i = 0; i < inNumberOfItems; ++i) + { + read(); + } +} + template void RingBuffer::clear() { @@ -104,6 +113,13 @@ void RingBuffer::clear() // ----------------------------------------------------------------------------- +template +DataType RingBuffer::peek(int inOffset) const +{ + const int head = (mReadHead + inOffset) & sMask; + return mData[head]; +} + template DataType RingBuffer::read() { diff --git a/test/unit-tests/tests/unit-tests_RingBuffer.cpp b/test/unit-tests/tests/unit-tests_RingBuffer.cpp index d33352d8..083deb76 100644 --- a/test/unit-tests/tests/unit-tests_RingBuffer.cpp +++ b/test/unit-tests/tests/unit-tests_RingBuffer.cpp @@ -170,5 +170,34 @@ TEST(RingBuffer, readArrayOverflow) EXPECT_EQ(buffer.getLength(), 0); } +TEST(RingBuffer, peek) +{ + Buffer buffer; + const uint8_t input[4] = { + 1, 2, 3, 4 + }; + + buffer.write(input, 4); + EXPECT_EQ(1, buffer.peek()); + EXPECT_EQ(2, buffer.peek(1)); + EXPECT_EQ(3, buffer.peek(2)); + EXPECT_EQ(4, buffer.peek(3)); + EXPECT_EQ(4, buffer.getLength()) << "Peek should not change buffer length"; +} + +TEST(RingBuffer, pop) +{ + Buffer buffer; + const uint8_t input[4] = { + 1, 2, 3, 4 + }; + + buffer.write(input, 4); + buffer.pop(); + EXPECT_EQ(3, buffer.getLength()); + buffer.pop(2); + EXPECT_EQ(1, buffer.getLength()); +} + END_UNNAMED_NAMESPACE From 3f15b733e47e6d0faa9e1190a9559dcca00fa9ac Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 3 Nov 2018 18:58:45 +0100 Subject: [PATCH 16/48] feat: Get CIN from status byte Removed unused implementation of UsbMidiEventPacket --- src/midi_UsbDefs.h | 89 ++++++++----------- test/unit-tests/tests/unit-tests_MidiUsb.cpp | 75 ---------------- .../tests/unit-tests_MidiUsbDefs.cpp | 60 +++++++++++++ 3 files changed, 99 insertions(+), 125 deletions(-) delete mode 100644 test/unit-tests/tests/unit-tests_MidiUsb.cpp create mode 100644 test/unit-tests/tests/unit-tests_MidiUsbDefs.cpp diff --git a/src/midi_UsbDefs.h b/src/midi_UsbDefs.h index 2170d024..b117bc4f 100644 --- a/src/midi_UsbDefs.h +++ b/src/midi_UsbDefs.h @@ -60,6 +60,45 @@ struct CodeIndexNumbers singleByte = 0x0F, }; + static inline byte fromStatus(StatusByte inStatus) + { + const byte statusWithoutChannel = inStatus & 0xf0; + if (statusWithoutChannel >= midi::NoteOff && + statusWithoutChannel <= midi::PitchBend) + { + // Channel Voice Messages + return inStatus >> 4; + } + switch (inStatus) + { + // System Real Time Messages + case midi::Clock: + case midi::Start: + case midi::Continue: + case midi::Stop: + case midi::ActiveSensing: + case midi::SystemReset: + return CodeIndexNumbers::singleByte; + + case midi::SystemExclusive: + return CodeIndexNumbers::sysExStart; + + // System Common Messages + case midi::TimeCodeQuarterFrame: + return CodeIndexNumbers::systemCommon2Bytes; + case midi::SongPosition: + return CodeIndexNumbers::systemCommon3Bytes; + case midi::SongSelect: + return CodeIndexNumbers::systemCommon2Bytes; + case midi::TuneRequest: + return CodeIndexNumbers::systemCommon1Byte; + + default: + return CodeIndexNumbers::reserved; + } + + } + static inline byte getSize(byte inCodeIndexNumber) { switch (inCodeIndexNumber) @@ -91,54 +130,4 @@ struct CodeIndexNumbers } }; -// ----------------------------------------------------------------------------- - -struct UsbMidiEventPacket -{ -public: - inline UsbMidiEventPacket() - { - memset(mData, 0, 4 * sizeof(byte)); - } - -public: - inline void setHeader(byte inCableNumber, byte inCodeIndexNumber) - { - const byte msb = (0x0f & inCableNumber) << 4; - const byte lsb = (0x0f & inCodeIndexNumber); - mData[0] = msb | lsb; - } - inline void setMidiData(const byte* inData) - { - mData[1] = *inData++; - mData[2] = *inData++; - mData[3] = *inData; - } - inline byte getCableNumber() const - { - return mData[0] >> 4; - } - inline byte getCodeIndexNumber() const - { - return mData[0] & 0x0f; - } - inline const byte* getMidiData() const - { - return mData + 1; - } - inline byte* getMidiData() - { - return mData + 1; - } - inline UsbMidiEventPacket& operator=(const byte* inData) - { - mData[0] = *inData++; - setMidiData(inData); - return *this; - } - -public: - byte mData[4]; -}; - END_MIDI_NAMESPACE diff --git a/test/unit-tests/tests/unit-tests_MidiUsb.cpp b/test/unit-tests/tests/unit-tests_MidiUsb.cpp deleted file mode 100644 index 1e872337..00000000 --- a/test/unit-tests/tests/unit-tests_MidiUsb.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "unit-tests.h" -#include - -BEGIN_MIDI_NAMESPACE - -END_MIDI_NAMESPACE - -// ----------------------------------------------------------------------------- - -BEGIN_UNNAMED_NAMESPACE - -TEST(MidiUsb, codeIndexNumberSizes) -{ - typedef midi::CodeIndexNumbers CIN; - EXPECT_EQ(CIN::getSize(CIN::reserved), 0); - EXPECT_EQ(CIN::getSize(CIN::misc), 0); - EXPECT_EQ(CIN::getSize(CIN::cableEvent), 0); - EXPECT_EQ(CIN::getSize(CIN::systemCommon2Bytes), 2); - EXPECT_EQ(CIN::getSize(CIN::systemCommon3Bytes), 3); - EXPECT_EQ(CIN::getSize(CIN::sysExStart), 3); - EXPECT_EQ(CIN::getSize(CIN::sysExContinue), 3); - EXPECT_EQ(CIN::getSize(CIN::systemCommon1Byte), 1); - EXPECT_EQ(CIN::getSize(CIN::sysExEnds1Byte), 1); - EXPECT_EQ(CIN::getSize(CIN::sysExEnds2Bytes), 2); - EXPECT_EQ(CIN::getSize(CIN::sysExEnds3Bytes), 3); - EXPECT_EQ(CIN::getSize(CIN::noteOff), 3); - EXPECT_EQ(CIN::getSize(CIN::noteOn), 3); - EXPECT_EQ(CIN::getSize(CIN::polyPressure), 3); - EXPECT_EQ(CIN::getSize(CIN::controlChange), 3); - EXPECT_EQ(CIN::getSize(CIN::programChange), 2); - EXPECT_EQ(CIN::getSize(CIN::channelPressure), 2); - EXPECT_EQ(CIN::getSize(CIN::pitchBend), 3); - EXPECT_EQ(CIN::getSize(CIN::singleByte), 1); -} - -TEST(MidiUsb, UsbMidiEventPacket) -{ - midi::UsbMidiEventPacket packet; - EXPECT_EQ(packet.mData[0], 0); - EXPECT_EQ(packet.mData[1], 0); - EXPECT_EQ(packet.mData[2], 0); - EXPECT_EQ(packet.mData[3], 0); - EXPECT_EQ(packet.getCableNumber(), 0); - EXPECT_EQ(packet.getCodeIndexNumber(), 0); - - packet.setHeader(12, 7); - EXPECT_EQ(packet.mData[0], 0xc7); - EXPECT_EQ(packet.getCableNumber(), 12); - EXPECT_EQ(packet.getCodeIndexNumber(), 7); - - const byte midiData[3] = { 12, 42, 47 }; - packet.setMidiData(midiData); - EXPECT_EQ(packet.mData[0], 0xc7); - EXPECT_EQ(packet.mData[1], 12); - EXPECT_EQ(packet.mData[2], 42); - EXPECT_EQ(packet.mData[3], 47); - - const byte fullData[4] = { 12, 34, 56, 78 }; - packet = fullData; - EXPECT_EQ(packet.mData[0], 12); - EXPECT_EQ(packet.mData[1], 34); - EXPECT_EQ(packet.mData[2], 56); - EXPECT_EQ(packet.mData[3], 78); - - const byte* midiDataConst = packet.getMidiData(); - EXPECT_EQ(midiDataConst[0], 34); - EXPECT_EQ(midiDataConst[1], 56); - EXPECT_EQ(midiDataConst[2], 78); - byte* midiDataMutable = packet.getMidiData(); - EXPECT_EQ(midiDataMutable[0], 34); - EXPECT_EQ(midiDataMutable[1], 56); - EXPECT_EQ(midiDataMutable[2], 78); -} - -END_UNNAMED_NAMESPACE diff --git a/test/unit-tests/tests/unit-tests_MidiUsbDefs.cpp b/test/unit-tests/tests/unit-tests_MidiUsbDefs.cpp new file mode 100644 index 00000000..c5059eff --- /dev/null +++ b/test/unit-tests/tests/unit-tests_MidiUsbDefs.cpp @@ -0,0 +1,60 @@ +#include "unit-tests.h" +#include + +BEGIN_MIDI_NAMESPACE + +END_MIDI_NAMESPACE + +// ----------------------------------------------------------------------------- + +BEGIN_UNNAMED_NAMESPACE + +TEST(MidiUsbDefs, codeIndexNumberFromStatus) +{ + typedef midi::CodeIndexNumbers CIN; + EXPECT_EQ(CIN::fromStatus(midi::InvalidType), CIN::reserved); + EXPECT_EQ(CIN::fromStatus(midi::NoteOff), CIN::noteOff); + EXPECT_EQ(CIN::fromStatus(midi::NoteOn), CIN::noteOn); + EXPECT_EQ(CIN::fromStatus(midi::AfterTouchPoly), CIN::polyPressure); + EXPECT_EQ(CIN::fromStatus(midi::ControlChange), CIN::controlChange); + EXPECT_EQ(CIN::fromStatus(midi::ProgramChange), CIN::programChange); + EXPECT_EQ(CIN::fromStatus(midi::AfterTouchChannel), CIN::channelPressure); + EXPECT_EQ(CIN::fromStatus(midi::PitchBend), CIN::pitchBend); + EXPECT_EQ(CIN::fromStatus(midi::Clock), CIN::singleByte); + EXPECT_EQ(CIN::fromStatus(midi::Start), CIN::singleByte); + EXPECT_EQ(CIN::fromStatus(midi::Continue), CIN::singleByte); + EXPECT_EQ(CIN::fromStatus(midi::Stop), CIN::singleByte); + EXPECT_EQ(CIN::fromStatus(midi::ActiveSensing), CIN::singleByte); + EXPECT_EQ(CIN::fromStatus(midi::SystemReset), CIN::singleByte); + EXPECT_EQ(CIN::fromStatus(midi::SystemExclusive), CIN::sysExStart); + EXPECT_EQ(CIN::fromStatus(midi::TuneRequest), CIN::systemCommon1Byte); + EXPECT_EQ(CIN::fromStatus(midi::TimeCodeQuarterFrame), CIN::systemCommon2Bytes); + EXPECT_EQ(CIN::fromStatus(midi::SongSelect), CIN::systemCommon2Bytes); + EXPECT_EQ(CIN::fromStatus(midi::SongPosition), CIN::systemCommon3Bytes); +} + +TEST(MidiUsbDefs, codeIndexNumberSizes) +{ + typedef midi::CodeIndexNumbers CIN; + EXPECT_EQ(CIN::getSize(CIN::reserved), 0); + EXPECT_EQ(CIN::getSize(CIN::misc), 0); + EXPECT_EQ(CIN::getSize(CIN::cableEvent), 0); + EXPECT_EQ(CIN::getSize(CIN::systemCommon2Bytes), 2); + EXPECT_EQ(CIN::getSize(CIN::systemCommon3Bytes), 3); + EXPECT_EQ(CIN::getSize(CIN::sysExStart), 3); + EXPECT_EQ(CIN::getSize(CIN::sysExContinue), 3); + EXPECT_EQ(CIN::getSize(CIN::systemCommon1Byte), 1); + EXPECT_EQ(CIN::getSize(CIN::sysExEnds1Byte), 1); + EXPECT_EQ(CIN::getSize(CIN::sysExEnds2Bytes), 2); + EXPECT_EQ(CIN::getSize(CIN::sysExEnds3Bytes), 3); + EXPECT_EQ(CIN::getSize(CIN::noteOff), 3); + EXPECT_EQ(CIN::getSize(CIN::noteOn), 3); + EXPECT_EQ(CIN::getSize(CIN::polyPressure), 3); + EXPECT_EQ(CIN::getSize(CIN::controlChange), 3); + EXPECT_EQ(CIN::getSize(CIN::programChange), 2); + EXPECT_EQ(CIN::getSize(CIN::channelPressure), 2); + EXPECT_EQ(CIN::getSize(CIN::pitchBend), 3); + EXPECT_EQ(CIN::getSize(CIN::singleByte), 1); +} + +END_UNNAMED_NAMESPACE From 5b96487362f8eec20a6e1c1551c3c14298541e44 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 3 Nov 2018 18:59:20 +0100 Subject: [PATCH 17/48] feat: Compose TX USB MIDI packets from serial buffer --- .vscode/settings.json | 9 +- src/midi_UsbPacketInterface.h | 44 +++ src/midi_UsbPacketInterface.hpp | 65 ++++ test/unit-tests/CMakeLists.txt | 3 +- .../unit-tests_MidiUsbPacketInterface.cpp | 314 ++++++++++++++++++ 5 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 src/midi_UsbPacketInterface.h create mode 100644 src/midi_UsbPacketInterface.hpp create mode 100644 test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 73c61a52..007ba052 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,13 @@ { "files.associations": { "cstddef": "cpp", - "ostream": "cpp" + "ostream": "cpp", + "__locale": "cpp", + "functional": "cpp", + "iterator": "cpp", + "string": "cpp", + "string_view": "cpp", + "vector": "cpp", + "istream": "cpp" } } \ No newline at end of file diff --git a/src/midi_UsbPacketInterface.h b/src/midi_UsbPacketInterface.h new file mode 100644 index 00000000..33d57215 --- /dev/null +++ b/src/midi_UsbPacketInterface.h @@ -0,0 +1,44 @@ +/*! + * @file midi_UsbPacketInterface.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Transport layer for USB MIDI + * @author Francois Best + * @date 2018-11-03 + * @license MIT - Copyright (c) 2018 Francois Best + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "midi_Defs.h" +#include "midi_UsbDefs.h" +#include + +BEGIN_MIDI_NAMESPACE + +template +bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket); + +template +bool parseRxPacket(const midiEventPacket_t& inPacket, Buffer& outBuffer); + +END_MIDI_NAMESPACE + +#include "midi_UsbPacketInterface.hpp" diff --git a/src/midi_UsbPacketInterface.hpp b/src/midi_UsbPacketInterface.hpp new file mode 100644 index 00000000..6d575183 --- /dev/null +++ b/src/midi_UsbPacketInterface.hpp @@ -0,0 +1,65 @@ +/*! + * @file midi_UsbPacketInterface.hpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Transport layer for USB MIDI + * @author Francois Best + * @date 2018-11-03 + * @license MIT - Copyright (c) 2018 Francois Best + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +BEGIN_MIDI_NAMESPACE + +template +bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket) +{ + if (inBuffer.isEmpty()) { + return false; + } + int bufferLength = inBuffer.getLength(); + const byte status = inBuffer.peek(); + const byte cin = midi::CodeIndexNumbers::fromStatus(status); + const byte messageLength = midi::CodeIndexNumbers::getSize(cin); + + if (bufferLength < messageLength) { + return false; // Not enough data in the buffer to compose a full packet. + } + + outPacket.header = cin; + outPacket.byte1 = status; + outPacket.byte2 = messageLength >= 2 ? inBuffer.peek(1) : 0x00; + outPacket.byte3 = messageLength >= 3 ? inBuffer.peek(2) : 0x00; + + inBuffer.pop(messageLength); + return true; + + // todo: handle interleaved RealTime messages + // todo: handle SysEx +} + +template +bool parseRxPacket(const midiEventPacket_t& /*inPacket*/, Buffer& /*outBuffer*/) +{ + return false; +} + +END_MIDI_NAMESPACE diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index 06dc4f97..59bfb7be 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -25,7 +25,8 @@ add_executable(unit-tests tests/unit-tests_MidiInputCallbacks.cpp tests/unit-tests_MidiOutput.cpp tests/unit-tests_MidiThru.cpp - tests/unit-tests_MidiUsb.cpp + tests/unit-tests_MidiUsbDefs.cpp + tests/unit-tests_MidiUsbPacketInterface.cpp ) target_link_libraries(unit-tests diff --git a/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp b/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp new file mode 100644 index 00000000..3ca614e9 --- /dev/null +++ b/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp @@ -0,0 +1,314 @@ +#include "unit-tests.h" +#include +#include +#include + +BEGIN_MIDI_NAMESPACE + +END_MIDI_NAMESPACE + +// ----------------------------------------------------------------------------- + +BEGIN_UNNAMED_NAMESPACE + +using Buffer = midi::RingBuffer; + +TEST(MidiUsbPacketInterfaceTx, EmptyBufferShouldNotComposeAPacket) +{ + midiEventPacket_t packet; + Buffer buffer; + + const bool result = midi::composeTxPacket(buffer, packet); + EXPECT_FALSE(result) << "Empty buffer should not compose a packet"; +} + +TEST(MidiUsbPacketInterfaceTx, IncompleteDataShouldNotComposeAPacket) +{ + midiEventPacket_t packet; + Buffer buffer; + bool result = false; + + buffer.write(0x81); + result = midi::composeTxPacket(buffer, packet); + EXPECT_FALSE(result) << "Insufficient data should not compose a packet"; + EXPECT_EQ(1, buffer.getLength()) << "Partial data should not be read out of the buffer"; + + buffer.write(0x12); + buffer.write(0x42); + result = midi::composeTxPacket(buffer, packet); + EXPECT_TRUE(result) << "Complete data should compose a packet"; + EXPECT_EQ(0, buffer.getLength()) << "Complete packet data should be read out of the buffer"; + EXPECT_EQ(midi::CodeIndexNumbers::noteOff, packet.header); + EXPECT_EQ(0x81, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x42, packet.byte3); +} + +// Channel Voice Messages -- + +TEST(MidiUsbPacketInterfaceTx, NoteOff) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(0x80); + buffer.write(0x12); + buffer.write(0x42); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::noteOff, packet.header); + EXPECT_EQ(0x80, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x42, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, NoteOn) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(0x91); + buffer.write(0x12); + buffer.write(0x42); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::noteOn, packet.header); + EXPECT_EQ(0x91, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x42, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, PolyPressure) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(0xA2); + buffer.write(0x12); + buffer.write(0x42); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::polyPressure, packet.header); + EXPECT_EQ(0xA2, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x42, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, ControlChange) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(0xB3); + buffer.write(0x12); + buffer.write(0x42); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::controlChange, packet.header); + EXPECT_EQ(0xB3, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x42, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, ProgramChange) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(0xC4); + buffer.write(0x12); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::programChange, packet.header); + EXPECT_EQ(0xC4, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, ChannelPressure) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(0xD5); + buffer.write(0x12); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::channelPressure, packet.header); + EXPECT_EQ(0xD5, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, PitchBend) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(0xE6); + buffer.write(0x12); + buffer.write(0x42); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::pitchBend, packet.header); + EXPECT_EQ(0xE6, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x42, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +// System Real Time Messages -- + +TEST(MidiUsbPacketInterfaceTx, Clock) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::Clock); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(midi::Clock, packet.byte1); + EXPECT_EQ(0x00, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, Start) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::Start); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(midi::Start, packet.byte1); + EXPECT_EQ(0x00, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, Stop) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::Stop); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(midi::Stop, packet.byte1); + EXPECT_EQ(0x00, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, Continue) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::Continue); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(midi::Continue, packet.byte1); + EXPECT_EQ(0x00, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, ActiveSensing) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::ActiveSensing); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(midi::ActiveSensing, packet.byte1); + EXPECT_EQ(0x00, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SystemReset) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::SystemReset); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(midi::SystemReset, packet.byte1); + EXPECT_EQ(0x00, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +// System Common Messages -- + +TEST(MidiUsbPacketInterfaceTx, TuneRequest) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::TuneRequest); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::systemCommon1Byte, packet.header); + EXPECT_EQ(midi::TuneRequest, packet.byte1); + EXPECT_EQ(0x00, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SongSelect) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::SongSelect); + buffer.write(0x12); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::systemCommon2Bytes, packet.header); + EXPECT_EQ(midi::SongSelect, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SongPosition) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::SongPosition); + buffer.write(0x12); + buffer.write(0x42); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::systemCommon3Bytes, packet.header); + EXPECT_EQ(midi::SongPosition, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x42, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, TimeCodeQuarterFrame) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(midi::TimeCodeQuarterFrame); + buffer.write(0x12); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::systemCommon2Bytes, packet.header); + EXPECT_EQ(midi::TimeCodeQuarterFrame, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +// ----------------------------------------------------------------------------- + +TEST(MidiUsbPacketInterfaceRx, PacketParsing) +{ + +} + +END_UNNAMED_NAMESPACE From 1ccd7c3ce8785474e6a243b33d24bd913584d1e3 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 3 Nov 2018 21:47:37 +0100 Subject: [PATCH 18/48] feat: Add SysEx support for USB MIDI --- src/midi_UsbDefs.h | 5 +- src/midi_UsbPacketInterface.hpp | 63 ++++++++- .../unit-tests_MidiUsbPacketInterface.cpp | 129 ++++++++++++++++++ 3 files changed, 195 insertions(+), 2 deletions(-) diff --git a/src/midi_UsbDefs.h b/src/midi_UsbDefs.h index b117bc4f..0cc110ed 100644 --- a/src/midi_UsbDefs.h +++ b/src/midi_UsbDefs.h @@ -80,8 +80,11 @@ struct CodeIndexNumbers case midi::SystemReset: return CodeIndexNumbers::singleByte; + // System Exclusive case midi::SystemExclusive: return CodeIndexNumbers::sysExStart; + case 0xf7: + return CodeIndexNumbers::sysExEnds1Byte; // System Common Messages case midi::TimeCodeQuarterFrame: @@ -119,7 +122,7 @@ struct CodeIndexNumbers case sysExEnds2Bytes: return 2; - case systemCommon1Byte: + case systemCommon1Byte: // also sysExEnds1Byte case singleByte: return 1; diff --git a/src/midi_UsbPacketInterface.hpp b/src/midi_UsbPacketInterface.hpp index 6d575183..609ba9f2 100644 --- a/src/midi_UsbPacketInterface.hpp +++ b/src/midi_UsbPacketInterface.hpp @@ -40,6 +40,68 @@ bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket) const byte cin = midi::CodeIndexNumbers::fromStatus(status); const byte messageLength = midi::CodeIndexNumbers::getSize(cin); + if (status == 0xf0) + { + // Start of SysEx, check if it can end in one go. + if (bufferLength == 2 && inBuffer.peek(1) == 0xf7) + { + outPacket.header = midi::CodeIndexNumbers::sysExEnds2Bytes; + outPacket.byte1 = status; + outPacket.byte2 = 0xf7; + outPacket.byte3 = 0x00; + inBuffer.pop(2); + return true; + } + if (bufferLength >= 3 && inBuffer.peek(2) == 0xf7) + { + outPacket.header = midi::CodeIndexNumbers::sysExEnds3Bytes; + outPacket.byte1 = status; + outPacket.byte2 = inBuffer.peek(1); + outPacket.byte3 = 0xf7; + inBuffer.pop(3); + return true; + } + } + + if ((status & 0x80) == 0x00) + { + // First byte is data, consider it's part of a running SysEx message. + // We look for the SysEx end byte in the next 2 bytes + // At this point, bufferLength should be 2 or more to continue. + if (bufferLength == 1) + { + return false; // Not enough data + } + if (bufferLength == 2) + { + const bool isSysExEnd = inBuffer.peek(1) == 0xf7; + if (!isSysExEnd) + { + return false; // Not enough data (eg: 0x12 0x42) + } + // eg: 0x42 0xf7 + outPacket.header = midi::CodeIndexNumbers::sysExEnds2Bytes; + outPacket.byte1 = status; + outPacket.byte2 = inBuffer.peek(1); + outPacket.byte3 = 0x00; + inBuffer.pop(2); + return true; + } + else + { + // bufferLength > 2 + const byte byte3 = inBuffer.peek(2); + outPacket.header = byte3 == 0xf7 + ? midi::CodeIndexNumbers::sysExEnds3Bytes + : midi::CodeIndexNumbers::sysExContinue; + outPacket.byte1 = status; + outPacket.byte2 = inBuffer.peek(1); + outPacket.byte3 = byte3; + inBuffer.pop(3); + return true; + } + } + if (bufferLength < messageLength) { return false; // Not enough data in the buffer to compose a full packet. } @@ -53,7 +115,6 @@ bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket) return true; // todo: handle interleaved RealTime messages - // todo: handle SysEx } template diff --git a/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp b/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp index 3ca614e9..ee90d1e5 100644 --- a/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp +++ b/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp @@ -304,6 +304,135 @@ TEST(MidiUsbPacketInterfaceTx, TimeCodeQuarterFrame) EXPECT_EQ(0, buffer.getLength()); } +// System Exclusive -- + +TEST(MidiUsbPacketInterfaceTx, SysExNotEnoughData) +{ + midiEventPacket_t packet; + Buffer buffer; + + buffer.write(0x12); + buffer.write(0x42); + EXPECT_FALSE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(2, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SysExSinglePacket) +{ + midiEventPacket_t packet; + Buffer buffer; + + // Two-byte SysEx (utterly useless) + buffer.write(0xf0); + buffer.write(0xf7); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds2Bytes, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0xf7, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); + + // Single-data byte SysEx (non-spec conformant ?) + buffer.write(0xf0); + buffer.write(0x12); + buffer.write(0xf7); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds3Bytes, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0x12, packet.byte2); + EXPECT_EQ(0xf7, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SysExTwoPackets) +{ + midiEventPacket_t packet; + Buffer buffer; + + const byte deviceIdentityRequest[6] = { + 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7 + }; + + buffer.write(deviceIdentityRequest, 6); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExStart, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0x7e, packet.byte2); + EXPECT_EQ(0x7f, packet.byte3); + EXPECT_EQ(3, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds3Bytes, packet.header); + EXPECT_EQ(0x06, packet.byte1); + EXPECT_EQ(0x01, packet.byte2); + EXPECT_EQ(0xf7, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SysExMultiplePacketsEndingWith1Byte) +{ + midiEventPacket_t packet; + Buffer buffer; + + const byte message[7] = { + 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0xf7 + }; + + buffer.write(message, 7); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExStart, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0x01, packet.byte2); + EXPECT_EQ(0x02, packet.byte3); + EXPECT_EQ(4, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExContinue, packet.header); + EXPECT_EQ(0x03, packet.byte1); + EXPECT_EQ(0x04, packet.byte2); + EXPECT_EQ(0x05, packet.byte3); + EXPECT_EQ(1, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds1Byte, packet.header); + EXPECT_EQ(0xf7, packet.byte1); + EXPECT_EQ(0x00, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + +TEST(MidiUsbPacketInterfaceTx, SysExMultiplePacketsEndingWith2Bytes) +{ + midiEventPacket_t packet; + Buffer buffer; + + const byte message[8] = { + 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xf7 + }; + + buffer.write(message, 8); + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExStart, packet.header); + EXPECT_EQ(0xf0, packet.byte1); + EXPECT_EQ(0x01, packet.byte2); + EXPECT_EQ(0x02, packet.byte3); + EXPECT_EQ(5, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExContinue, packet.header); + EXPECT_EQ(0x03, packet.byte1); + EXPECT_EQ(0x04, packet.byte2); + EXPECT_EQ(0x05, packet.byte3); + EXPECT_EQ(2, buffer.getLength()); + + EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); + EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds2Bytes, packet.header); + EXPECT_EQ(0x06, packet.byte1); + EXPECT_EQ(0xf7, packet.byte2); + EXPECT_EQ(0x00, packet.byte3); + EXPECT_EQ(0, buffer.getLength()); +} + // ----------------------------------------------------------------------------- TEST(MidiUsbPacketInterfaceRx, PacketParsing) From 5ccf0159a2912214895bff5a56e4065c81331aae Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sun, 4 Nov 2018 10:07:21 +0100 Subject: [PATCH 19/48] feat: Serialise USB MIDI packets into the serial RX buffer --- src/midi_UsbDefs.h | 3 +- src/midi_UsbPacketInterface.h | 2 +- src/midi_UsbPacketInterface.hpp | 24 +- .../unit-tests_MidiUsbPacketInterface.cpp | 460 ++++++++++++++++-- 4 files changed, 454 insertions(+), 35 deletions(-) diff --git a/src/midi_UsbDefs.h b/src/midi_UsbDefs.h index 0cc110ed..54defb59 100644 --- a/src/midi_UsbDefs.h +++ b/src/midi_UsbDefs.h @@ -127,9 +127,8 @@ struct CodeIndexNumbers return 1; default: - break; + return 0; // Can be any length (1, 2 or 3). } - return 0; // Can be any length (1, 2 or 3). } }; diff --git a/src/midi_UsbPacketInterface.h b/src/midi_UsbPacketInterface.h index 33d57215..ee27901e 100644 --- a/src/midi_UsbPacketInterface.h +++ b/src/midi_UsbPacketInterface.h @@ -37,7 +37,7 @@ template bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket); template -bool parseRxPacket(const midiEventPacket_t& inPacket, Buffer& outBuffer); +void serialiseRxPacket(const midiEventPacket_t& inPacket, Buffer& outBuffer); END_MIDI_NAMESPACE diff --git a/src/midi_UsbPacketInterface.hpp b/src/midi_UsbPacketInterface.hpp index 609ba9f2..b7782e3c 100644 --- a/src/midi_UsbPacketInterface.hpp +++ b/src/midi_UsbPacketInterface.hpp @@ -118,9 +118,29 @@ bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket) } template -bool parseRxPacket(const midiEventPacket_t& /*inPacket*/, Buffer& /*outBuffer*/) +void serialiseRxPacket(const midiEventPacket_t& inPacket, Buffer& outBuffer) { - return false; + const byte cin = inPacket.header & 0x0f; + const byte messageLength = midi::CodeIndexNumbers::getSize(cin); + switch (messageLength) + { + case 1: + outBuffer.write(inPacket.byte1); + return; + case 2: + outBuffer.write(inPacket.byte1); + outBuffer.write(inPacket.byte2); + return; + case 3: + outBuffer.write(inPacket.byte1); + outBuffer.write(inPacket.byte2); + outBuffer.write(inPacket.byte3); + return; + case 0: + default: + // Invalid or ignored messages, don't serialise. + return; + } } END_MIDI_NAMESPACE diff --git a/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp b/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp index ee90d1e5..e66cb659 100644 --- a/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp +++ b/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp @@ -12,6 +12,7 @@ END_MIDI_NAMESPACE BEGIN_UNNAMED_NAMESPACE using Buffer = midi::RingBuffer; +using CIN = midi::CodeIndexNumbers; TEST(MidiUsbPacketInterfaceTx, EmptyBufferShouldNotComposeAPacket) { @@ -38,7 +39,7 @@ TEST(MidiUsbPacketInterfaceTx, IncompleteDataShouldNotComposeAPacket) result = midi::composeTxPacket(buffer, packet); EXPECT_TRUE(result) << "Complete data should compose a packet"; EXPECT_EQ(0, buffer.getLength()) << "Complete packet data should be read out of the buffer"; - EXPECT_EQ(midi::CodeIndexNumbers::noteOff, packet.header); + EXPECT_EQ(CIN::noteOff, packet.header); EXPECT_EQ(0x81, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x42, packet.byte3); @@ -55,7 +56,7 @@ TEST(MidiUsbPacketInterfaceTx, NoteOff) buffer.write(0x12); buffer.write(0x42); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::noteOff, packet.header); + EXPECT_EQ(CIN::noteOff, packet.header); EXPECT_EQ(0x80, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x42, packet.byte3); @@ -71,7 +72,7 @@ TEST(MidiUsbPacketInterfaceTx, NoteOn) buffer.write(0x12); buffer.write(0x42); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::noteOn, packet.header); + EXPECT_EQ(CIN::noteOn, packet.header); EXPECT_EQ(0x91, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x42, packet.byte3); @@ -87,7 +88,7 @@ TEST(MidiUsbPacketInterfaceTx, PolyPressure) buffer.write(0x12); buffer.write(0x42); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::polyPressure, packet.header); + EXPECT_EQ(CIN::polyPressure, packet.header); EXPECT_EQ(0xA2, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x42, packet.byte3); @@ -103,7 +104,7 @@ TEST(MidiUsbPacketInterfaceTx, ControlChange) buffer.write(0x12); buffer.write(0x42); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::controlChange, packet.header); + EXPECT_EQ(CIN::controlChange, packet.header); EXPECT_EQ(0xB3, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x42, packet.byte3); @@ -118,7 +119,7 @@ TEST(MidiUsbPacketInterfaceTx, ProgramChange) buffer.write(0xC4); buffer.write(0x12); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::programChange, packet.header); + EXPECT_EQ(CIN::programChange, packet.header); EXPECT_EQ(0xC4, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -133,7 +134,7 @@ TEST(MidiUsbPacketInterfaceTx, ChannelPressure) buffer.write(0xD5); buffer.write(0x12); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::channelPressure, packet.header); + EXPECT_EQ(CIN::channelPressure, packet.header); EXPECT_EQ(0xD5, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -149,7 +150,7 @@ TEST(MidiUsbPacketInterfaceTx, PitchBend) buffer.write(0x12); buffer.write(0x42); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::pitchBend, packet.header); + EXPECT_EQ(CIN::pitchBend, packet.header); EXPECT_EQ(0xE6, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x42, packet.byte3); @@ -165,7 +166,7 @@ TEST(MidiUsbPacketInterfaceTx, Clock) buffer.write(midi::Clock); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(CIN::singleByte, packet.header); EXPECT_EQ(midi::Clock, packet.byte1); EXPECT_EQ(0x00, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -179,7 +180,7 @@ TEST(MidiUsbPacketInterfaceTx, Start) buffer.write(midi::Start); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(CIN::singleByte, packet.header); EXPECT_EQ(midi::Start, packet.byte1); EXPECT_EQ(0x00, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -193,7 +194,7 @@ TEST(MidiUsbPacketInterfaceTx, Stop) buffer.write(midi::Stop); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(CIN::singleByte, packet.header); EXPECT_EQ(midi::Stop, packet.byte1); EXPECT_EQ(0x00, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -207,7 +208,7 @@ TEST(MidiUsbPacketInterfaceTx, Continue) buffer.write(midi::Continue); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(CIN::singleByte, packet.header); EXPECT_EQ(midi::Continue, packet.byte1); EXPECT_EQ(0x00, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -221,7 +222,7 @@ TEST(MidiUsbPacketInterfaceTx, ActiveSensing) buffer.write(midi::ActiveSensing); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(CIN::singleByte, packet.header); EXPECT_EQ(midi::ActiveSensing, packet.byte1); EXPECT_EQ(0x00, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -235,7 +236,7 @@ TEST(MidiUsbPacketInterfaceTx, SystemReset) buffer.write(midi::SystemReset); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::singleByte, packet.header); + EXPECT_EQ(CIN::singleByte, packet.header); EXPECT_EQ(midi::SystemReset, packet.byte1); EXPECT_EQ(0x00, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -251,7 +252,7 @@ TEST(MidiUsbPacketInterfaceTx, TuneRequest) buffer.write(midi::TuneRequest); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::systemCommon1Byte, packet.header); + EXPECT_EQ(CIN::systemCommon1Byte, packet.header); EXPECT_EQ(midi::TuneRequest, packet.byte1); EXPECT_EQ(0x00, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -266,7 +267,7 @@ TEST(MidiUsbPacketInterfaceTx, SongSelect) buffer.write(midi::SongSelect); buffer.write(0x12); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::systemCommon2Bytes, packet.header); + EXPECT_EQ(CIN::systemCommon2Bytes, packet.header); EXPECT_EQ(midi::SongSelect, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -282,7 +283,7 @@ TEST(MidiUsbPacketInterfaceTx, SongPosition) buffer.write(0x12); buffer.write(0x42); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::systemCommon3Bytes, packet.header); + EXPECT_EQ(CIN::systemCommon3Bytes, packet.header); EXPECT_EQ(midi::SongPosition, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x42, packet.byte3); @@ -297,7 +298,7 @@ TEST(MidiUsbPacketInterfaceTx, TimeCodeQuarterFrame) buffer.write(midi::TimeCodeQuarterFrame); buffer.write(0x12); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::systemCommon2Bytes, packet.header); + EXPECT_EQ(CIN::systemCommon2Bytes, packet.header); EXPECT_EQ(midi::TimeCodeQuarterFrame, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -326,7 +327,7 @@ TEST(MidiUsbPacketInterfaceTx, SysExSinglePacket) buffer.write(0xf0); buffer.write(0xf7); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds2Bytes, packet.header); + EXPECT_EQ(CIN::sysExEnds2Bytes, packet.header); EXPECT_EQ(0xf0, packet.byte1); EXPECT_EQ(0xf7, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -337,7 +338,7 @@ TEST(MidiUsbPacketInterfaceTx, SysExSinglePacket) buffer.write(0x12); buffer.write(0xf7); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds3Bytes, packet.header); + EXPECT_EQ(CIN::sysExEnds3Bytes, packet.header); EXPECT_EQ(0xf0, packet.byte1); EXPECT_EQ(0x12, packet.byte2); EXPECT_EQ(0xf7, packet.byte3); @@ -355,14 +356,14 @@ TEST(MidiUsbPacketInterfaceTx, SysExTwoPackets) buffer.write(deviceIdentityRequest, 6); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExStart, packet.header); + EXPECT_EQ(CIN::sysExStart, packet.header); EXPECT_EQ(0xf0, packet.byte1); EXPECT_EQ(0x7e, packet.byte2); EXPECT_EQ(0x7f, packet.byte3); EXPECT_EQ(3, buffer.getLength()); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds3Bytes, packet.header); + EXPECT_EQ(CIN::sysExEnds3Bytes, packet.header); EXPECT_EQ(0x06, packet.byte1); EXPECT_EQ(0x01, packet.byte2); EXPECT_EQ(0xf7, packet.byte3); @@ -380,21 +381,21 @@ TEST(MidiUsbPacketInterfaceTx, SysExMultiplePacketsEndingWith1Byte) buffer.write(message, 7); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExStart, packet.header); + EXPECT_EQ(CIN::sysExStart, packet.header); EXPECT_EQ(0xf0, packet.byte1); EXPECT_EQ(0x01, packet.byte2); EXPECT_EQ(0x02, packet.byte3); EXPECT_EQ(4, buffer.getLength()); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExContinue, packet.header); + EXPECT_EQ(CIN::sysExContinue, packet.header); EXPECT_EQ(0x03, packet.byte1); EXPECT_EQ(0x04, packet.byte2); EXPECT_EQ(0x05, packet.byte3); EXPECT_EQ(1, buffer.getLength()); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds1Byte, packet.header); + EXPECT_EQ(CIN::sysExEnds1Byte, packet.header); EXPECT_EQ(0xf7, packet.byte1); EXPECT_EQ(0x00, packet.byte2); EXPECT_EQ(0x00, packet.byte3); @@ -412,32 +413,431 @@ TEST(MidiUsbPacketInterfaceTx, SysExMultiplePacketsEndingWith2Bytes) buffer.write(message, 8); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExStart, packet.header); + EXPECT_EQ(CIN::sysExStart, packet.header); EXPECT_EQ(0xf0, packet.byte1); EXPECT_EQ(0x01, packet.byte2); EXPECT_EQ(0x02, packet.byte3); EXPECT_EQ(5, buffer.getLength()); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExContinue, packet.header); + EXPECT_EQ(CIN::sysExContinue, packet.header); EXPECT_EQ(0x03, packet.byte1); EXPECT_EQ(0x04, packet.byte2); EXPECT_EQ(0x05, packet.byte3); EXPECT_EQ(2, buffer.getLength()); EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(midi::CodeIndexNumbers::sysExEnds2Bytes, packet.header); + EXPECT_EQ(CIN::sysExEnds2Bytes, packet.header); EXPECT_EQ(0x06, packet.byte1); EXPECT_EQ(0xf7, packet.byte2); EXPECT_EQ(0x00, packet.byte3); EXPECT_EQ(0, buffer.getLength()); } -// ----------------------------------------------------------------------------- +// ============================================================================= + +TEST(MidiUsbPacketInterfaceRx, InvalidPacketIsNotSerialised) +{ + Buffer buffer; + midiEventPacket_t packet; + packet.header = CIN::reserved; + packet.byte1 = 0x01; + packet.byte2 = 0x02; + packet.byte3 = 0x03; + midi::serialiseRxPacket(packet, buffer); + EXPECT_TRUE(buffer.isEmpty()); +} + +TEST(MidiUsbPacketInterfaceRx, CableEventsAreNotSerialised) +{ + Buffer buffer; + midiEventPacket_t packet; + packet.header = CIN::cableEvent; + packet.byte1 = 0x01; + packet.byte2 = 0x02; + packet.byte3 = 0x03; + midi::serialiseRxPacket(packet, buffer); + EXPECT_TRUE(buffer.isEmpty()); +} + +// Channel Voice Messages -- + +TEST(MidiUsbPacketInterfaceRx, NoteOff) +{ + Buffer buffer; + midiEventPacket_t packet; + packet.header = CIN::noteOff; + packet.byte1 = 0x80; + packet.byte2 = 0x12; + packet.byte3 = 0x42; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0x80, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); + EXPECT_EQ(0x42, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, NoteOn) +{ + Buffer buffer; + midiEventPacket_t packet; + packet.header = CIN::noteOn; + packet.byte1 = 0x91; + packet.byte2 = 0x12; + packet.byte3 = 0x42; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0x91, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); + EXPECT_EQ(0x42, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, PolyPressure) +{ + Buffer buffer; + midiEventPacket_t packet; + packet.header = CIN::polyPressure; + packet.byte1 = 0xA2; + packet.byte2 = 0x12; + packet.byte3 = 0x42; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0xA2, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); + EXPECT_EQ(0x42, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, ControlChange) +{ + Buffer buffer; + midiEventPacket_t packet; + packet.header = CIN::controlChange; + packet.byte1 = 0xB3; + packet.byte2 = 0x12; + packet.byte3 = 0x42; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0xB3, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); + EXPECT_EQ(0x42, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, ProgramChange) +{ + Buffer buffer; + midiEventPacket_t packet; + packet.header = CIN::programChange; + packet.byte1 = 0xC4; + packet.byte2 = 0x12; + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(2, buffer.getLength()); + EXPECT_EQ(0xC4, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, ChannelPressure) +{ + Buffer buffer; + midiEventPacket_t packet; + packet.header = CIN::channelPressure; + packet.byte1 = 0xD5; + packet.byte2 = 0x12; + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(2, buffer.getLength()); + EXPECT_EQ(0xD5, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, PitchBend) +{ + Buffer buffer; + midiEventPacket_t packet; + packet.header = CIN::pitchBend; + packet.byte1 = 0xE6; + packet.byte2 = 0x12; + packet.byte3 = 0x42; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0xE6, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); + EXPECT_EQ(0x42, buffer.read()); +} + +// System Real Time Messages -- + +TEST(MidiUsbPacketInterfaceRx, Clock) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::singleByte; + packet.byte1 = midi::Clock; + packet.byte2 = 0x12; // Should be ignored + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(1, buffer.getLength()); + EXPECT_EQ(midi::Clock, buffer.read()); +} -TEST(MidiUsbPacketInterfaceRx, PacketParsing) +TEST(MidiUsbPacketInterfaceRx, Start) { + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::singleByte; + packet.byte1 = midi::Start; + packet.byte2 = 0x12; // Should be ignored + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(1, buffer.getLength()); + EXPECT_EQ(midi::Start, buffer.read()); +} +TEST(MidiUsbPacketInterfaceRx, Stop) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::singleByte; + packet.byte1 = midi::Stop; + packet.byte2 = 0x12; // Should be ignored + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(1, buffer.getLength()); + EXPECT_EQ(midi::Stop, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, Continue) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::singleByte; + packet.byte1 = midi::Continue; + packet.byte2 = 0x12; // Should be ignored + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(1, buffer.getLength()); + EXPECT_EQ(midi::Continue, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, ActiveSensing) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::singleByte; + packet.byte1 = midi::ActiveSensing; + packet.byte2 = 0x12; // Should be ignored + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(1, buffer.getLength()); + EXPECT_EQ(midi::ActiveSensing, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, SystemReset) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::singleByte; + packet.byte1 = midi::SystemReset; + packet.byte2 = 0x12; // Should be ignored + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(1, buffer.getLength()); + EXPECT_EQ(midi::SystemReset, buffer.read()); +} + +// System Common Messages -- + +TEST(MidiUsbPacketInterfaceRx, TuneRequest) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::systemCommon1Byte; + packet.byte1 = midi::TuneRequest; + packet.byte2 = 0x12; // Should be ignored + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(1, buffer.getLength()); + EXPECT_EQ(midi::TuneRequest, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, SongSelect) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::systemCommon2Bytes; + packet.byte1 = midi::SongSelect; + packet.byte2 = 0x12; + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(2, buffer.getLength()); + EXPECT_EQ(midi::SongSelect, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, SongPosition) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::systemCommon2Bytes; + packet.byte1 = midi::SongPosition; + packet.byte2 = 0x12; + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(2, buffer.getLength()); + EXPECT_EQ(midi::SongPosition, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, TimeCodeQuarterFrame) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::systemCommon2Bytes; + packet.byte1 = midi::TimeCodeQuarterFrame; + packet.byte2 = 0x12; + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(2, buffer.getLength()); + EXPECT_EQ(midi::TimeCodeQuarterFrame, buffer.read()); + EXPECT_EQ(0x12, buffer.read()); +} + +// System Exclusive -- + +TEST(MidiUsbPacketInterfaceRx, SysExSinglePacket2Bytes) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::sysExEnds2Bytes; + packet.byte1 = 0xf0; + packet.byte2 = 0xf7; + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(2, buffer.getLength()); + EXPECT_EQ(0xf0, buffer.read()); + EXPECT_EQ(0xf7, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, SysExSinglePacket3Bytes) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::sysExEnds3Bytes; + packet.byte1 = 0xf0; + packet.byte2 = 0x42; + packet.byte3 = 0xf7; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0xf0, buffer.read()); + EXPECT_EQ(0x42, buffer.read()); + EXPECT_EQ(0xf7, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, SysExTwoPackets) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::sysExStart; + packet.byte1 = 0xf0; + packet.byte2 = 0x01; + packet.byte3 = 0x02; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0xf0, buffer.read()); + EXPECT_EQ(0x01, buffer.read()); + EXPECT_EQ(0x02, buffer.read()); + + packet.header = CIN::sysExEnds3Bytes; + packet.byte1 = 0x03; + packet.byte2 = 0x04; + packet.byte3 = 0xf7; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0x03, buffer.read()); + EXPECT_EQ(0x04, buffer.read()); + EXPECT_EQ(0xf7, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, SysExMultiplePacketsEndingWith1Byte) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::sysExStart; + packet.byte1 = 0xf0; + packet.byte2 = 0x01; + packet.byte3 = 0x02; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0xf0, buffer.read()); + EXPECT_EQ(0x01, buffer.read()); + EXPECT_EQ(0x02, buffer.read()); + + packet.header = CIN::sysExContinue; + packet.byte1 = 0x03; + packet.byte2 = 0x04; + packet.byte3 = 0x05; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0x03, buffer.read()); + EXPECT_EQ(0x04, buffer.read()); + EXPECT_EQ(0x05, buffer.read()); + + packet.header = CIN::sysExEnds1Byte; + packet.byte1 = 0xf7; + packet.byte2 = 0x12; // Should be ignored + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(1, buffer.getLength()); + EXPECT_EQ(0xf7, buffer.read()); +} + +TEST(MidiUsbPacketInterfaceRx, SysExMultiplePacketsEndingWith2Bytes) +{ + midiEventPacket_t packet; + Buffer buffer; + + packet.header = CIN::sysExStart; + packet.byte1 = 0xf0; + packet.byte2 = 0x01; + packet.byte3 = 0x02; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0xf0, buffer.read()); + EXPECT_EQ(0x01, buffer.read()); + EXPECT_EQ(0x02, buffer.read()); + + packet.header = CIN::sysExContinue; + packet.byte1 = 0x03; + packet.byte2 = 0x04; + packet.byte3 = 0x05; + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(3, buffer.getLength()); + EXPECT_EQ(0x03, buffer.read()); + EXPECT_EQ(0x04, buffer.read()); + EXPECT_EQ(0x05, buffer.read()); + + packet.header = CIN::sysExEnds2Bytes; + packet.byte1 = 0x06; + packet.byte2 = 0xf7; // Should be ignored + packet.byte3 = 0x42; // Should be ignored + midi::serialiseRxPacket(packet, buffer); + EXPECT_EQ(2, buffer.getLength()); + EXPECT_EQ(0x06, buffer.read()); + EXPECT_EQ(0xf7, buffer.read()); } END_UNNAMED_NAMESPACE From 67a13be6c622694f504d48d15f303656f08b6eda Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sun, 4 Nov 2018 14:17:07 +0100 Subject: [PATCH 20/48] fix: Use USB packet interface in the transport layer --- src/midi_UsbTransport.h | 14 +----- src/midi_UsbTransport.hpp | 100 +++++--------------------------------- 2 files changed, 13 insertions(+), 101 deletions(-) diff --git a/src/midi_UsbTransport.h b/src/midi_UsbTransport.h index a1556f5d..eb869e94 100644 --- a/src/midi_UsbTransport.h +++ b/src/midi_UsbTransport.h @@ -29,6 +29,7 @@ #include "midi_Defs.h" #include "midi_RingBuffer.h" +#include "midi_UsbPacketInterface.h" #include BEGIN_MIDI_NAMESPACE @@ -47,24 +48,13 @@ class UsbTransport inline void write(byte inData); private: - inline bool pollUsbMidi(); + inline void pollUsbMidi(); inline void recomposeAndSendTxPackets(); - inline void resetTx(); - static inline byte encodePacketHeader(StatusByte inStatusByte); - static inline int getPacketLength(const midiEventPacket_t& inPacket); private: typedef RingBuffer Buffer; Buffer mTxBuffer; Buffer mRxBuffer; - - union TxPacket - { - byte mDataArray[4]; - midiEventPacket_t mPacket; - }; - TxPacket mCurrentTxPacket; - int mCurrentTxPacketByteIndex; }; END_MIDI_NAMESPACE diff --git a/src/midi_UsbTransport.hpp b/src/midi_UsbTransport.hpp index 2a02c413..51594339 100644 --- a/src/midi_UsbTransport.hpp +++ b/src/midi_UsbTransport.hpp @@ -73,108 +73,30 @@ inline void UsbTransport::write(byte inData) // ----------------------------------------------------------------------------- template -inline bool UsbTransport::pollUsbMidi() +inline void UsbTransport::pollUsbMidi() { - bool received = false; midiEventPacket_t packet = MidiUSB.read(); while (packet.header != 0) { - received = true; - - switch (packet.header << 4) - { - // 3 bytes messages - case midi::NoteOff: - case midi::NoteOn: - case midi::AfterTouchPoly: - case midi::ControlChange: - case midi::PitchBend: - mRxBuffer.write(packet.byte1); - mRxBuffer.write(packet.byte2); - mRxBuffer.write(packet.byte3); - break; - - // 2 bytes messages - case midi::ProgramChange: - case midi::AfterTouchChannel: - mRxBuffer.write(packet.byte1); - mRxBuffer.write(packet.byte2); - break; - - // 1 byte message - case midi::TuneRequest: - case midi::Clock: - case midi::Start: - case midi::Continue: - case midi::Stop: - case midi::ActiveSensing: - case midi::SystemReset: - mRxBuffer.write(packet.byte1); - break; - - // Special cases - // case midi::SystemExclusive: - // case midi::TimeCodeQuarterFrame: - // case midi::SongPosition: - // case midi::SongSelect: - // break; - - default: - break; - } - + serialiseRxPacket(packet, mRxBuffer); packet = MidiUSB.read(); } - return received; } template inline void UsbTransport::recomposeAndSendTxPackets() { - while (!mTxBuffer.isEmpty()) + midiEventPacket_t packet; + bool sent = false; + while (composeTxPacket(mTxBuffer, packet)) { - const byte data = mTxBuffer.read(); - if (mCurrentTxPacketByteIndex == 0) - { - mCurrentTxPacket.mPacket.header = encodePacketHeader(data); - } - else - { - mCurrentTxPacket.mDataArray[mCurrentTxPacketByteIndex] = data; - } - mCurrentTxPacketByteIndex++; - - const int packetLength = getPacketLength(mCurrentTxPacket.mPacket); - - if (mCurrentTxPacketByteIndex == packetLength) - { - MidiUSB.write(mCurrentTxPacket.mDataArray, packetLength); - resetTx(); - } + MidiUSB.sendMIDI(packet); + sent = true; + } + if (sent) + { + MidiUSB.flush(); } -} - -template -inline void UsbTransport::resetTx() -{ - mCurrentTxPacket.mPacket.header = 0; - mCurrentTxPacket.mPacket.byte1 = 0; - mCurrentTxPacket.mPacket.byte2 = 0; - mCurrentTxPacket.mPacket.byte3 = 0; - mCurrentTxPacketByteIndex = 0; -} - -template -inline byte UsbTransport::encodePacketHeader(StatusByte inStatusByte) -{ - // todo: fix me for other types than channel - return inStatusByte >> 4; -} - -template -inline int UsbTransport::getPacketLength(const midiEventPacket_t& inPacket) -{ - return 3; } END_MIDI_NAMESPACE From 0e9e505543b9e49c7a3e7d366905434933d96a38 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sun, 4 Nov 2018 14:17:30 +0100 Subject: [PATCH 21/48] doc: Note the importance of Running Status being disabled for USB --- doc/usb-midi.md | 16 ++++++++++++++++ src/midi_Settings.h | 1 + 2 files changed, 17 insertions(+) create mode 100644 doc/usb-midi.md diff --git a/doc/usb-midi.md b/doc/usb-midi.md new file mode 100644 index 00000000..0c19f681 --- /dev/null +++ b/doc/usb-midi.md @@ -0,0 +1,16 @@ +# Using the library for MIDI through USB + +Some boards have "native" USB functionality, which means they can directly +connect to a computer and appear as a USB MIDI device. + +Here are the officially supported boards: + +- Arduino Leonardo +- Arduino Due + +## Pre-requisites + +To use USB MIDI to send messages to the computer, you must disable Running +Status in the settings. This is because the USB transport is stateless and has +no knowledge of the last message's running status. +The setting is off by default, so if you use custom settings, keep that in mind. diff --git a/src/midi_Settings.h b/src/midi_Settings.h index 1951944b..493a3b31 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -50,6 +50,7 @@ struct DefaultSettings { /*! Running status enables short messages when sending multiple values of the same type and channel.\n + Must be disabled to send USB MIDI messages to a computer Warning: does not work with some hardware, enable with caution. */ static const bool UseRunningStatus = false; From 347b67a87789e7e6ec77c838c9c44e09aaa68f11 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 6 Nov 2018 12:47:26 +0100 Subject: [PATCH 22/48] fix: Point back to original MIDIUSB repo Now that arduino-libraries/MIDIUSB#47 has been merged. --- .gitmodules | 2 +- external/midi-usb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index d94196e9..c1c2a776 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/google/googletest.git [submodule "external/midi-usb"] path = external/midi-usb - url = https://github.com/FortySevenEffects/MIDIUSB.git + url = https://github.com/arduino-libraries/MIDIUSB.git diff --git a/external/midi-usb b/external/midi-usb index 8cbf89bd..1f81cc9f 160000 --- a/external/midi-usb +++ b/external/midi-usb @@ -1 +1 @@ -Subproject commit 8cbf89bdb94ab65db35c86038098c3ee5b22298d +Subproject commit 1f81cc9f52184bbc271bfcee63b5a0efb03c8cf7 From 5835806832a7a3da8fdef6268b97c0efcc48a8c6 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 6 Nov 2018 16:55:08 +0100 Subject: [PATCH 23/48] fix: Constify --- src/midi_UsbPacketInterface.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/midi_UsbPacketInterface.hpp b/src/midi_UsbPacketInterface.hpp index b7782e3c..26cb22a5 100644 --- a/src/midi_UsbPacketInterface.hpp +++ b/src/midi_UsbPacketInterface.hpp @@ -35,7 +35,7 @@ bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket) if (inBuffer.isEmpty()) { return false; } - int bufferLength = inBuffer.getLength(); + const int bufferLength = inBuffer.getLength(); const byte status = inBuffer.peek(); const byte cin = midi::CodeIndexNumbers::fromStatus(status); const byte messageLength = midi::CodeIndexNumbers::getSize(cin); From 39d13e8ac47cabafe880e2bfee3c8684158139b8 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 6 Nov 2018 16:55:51 +0100 Subject: [PATCH 24/48] feat: Add MIDI USB test sketches --- .../MidiUSB-CompleteAPI.ino | 181 ++++++++++++++++++ examples/MidiUSB-Thru/MidiUSB-Thru.ino | 135 +++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 examples/MidiUSB-CompleteAPI/MidiUSB-CompleteAPI.ino create mode 100644 examples/MidiUSB-Thru/MidiUSB-Thru.ino diff --git a/examples/MidiUSB-CompleteAPI/MidiUSB-CompleteAPI.ino b/examples/MidiUSB-CompleteAPI/MidiUSB-CompleteAPI.ino new file mode 100644 index 00000000..4b6690ea --- /dev/null +++ b/examples/MidiUSB-CompleteAPI/MidiUSB-CompleteAPI.ino @@ -0,0 +1,181 @@ +#include + +#if defined(USBCON) +#include + +static const unsigned sUsbTransportBufferSize = 16; +typedef midi::UsbTransport UsbTransport; + +UsbTransport sUsbTransport; + +MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); + +#else // No USB available, fallback to Serial +MIDI_CREATE_DEFAULT_INSTANCE(); +#endif + +// -- + +void setupRxBenchmark() +{ + MIDI.setHandleNoteOff(handleNoteOff); + MIDI.setHandleNoteOn(handleNoteOn); + MIDI.setHandleControlChange(handleControlChange); + MIDI.setHandleProgramChange(handleProgramChange); + MIDI.setHandleAfterTouchChannel(handleAfterTouchChannel); + MIDI.setHandleAfterTouchPoly(handleAfterTouchPoly); + MIDI.setHandlePitchBend(handlePitchBend); + MIDI.setHandleStart(handleStart); + MIDI.setHandleStop(handleStop); + MIDI.setHandleContinue(handleContinue); + MIDI.setHandleClock(handleClock); + MIDI.setHandleSystemExclusive(handleSysEx); +} + +void startTxBenchmark() +{ + for (int i = 0; i < 128; ++i) + { + MIDI.sendNoteOn(i, 127, 1); + MIDI.sendNoteOff(i, 127, 1); + } + for (int i = 0; i < 128; ++i) + { + MIDI.sendControlChange(i, 127, 1); + MIDI.sendControlChange(i, 0, 1); + } + for (int i = 0; i < 128; ++i) + { + MIDI.sendProgramChange(i, 1); + } + MIDI.sendRealTime(midi::Start); + MIDI.sendRealTime(midi::Stop); + MIDI.sendRealTime(midi::Continue); + MIDI.sendRealTime(midi::Clock); + + const byte length = 128; + const byte data[128] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + }; + MIDI.sendSysEx(length, data, false); +} + +// -- + +void printChannel(byte inChannel) +{ + Serial.print("["); + Serial.print(inChannel); + Serial.print("] "); +} + +void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) +{ + printChannel(inChannel); + Serial.print("Note Off\t"); + Serial.print(inNote); + Serial.print(' '); + Serial.println(inVelocity); +} +void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) +{ + printChannel(inChannel); + Serial.print("Note On\t"); + Serial.print(inNote); + Serial.print(' '); + Serial.println(inVelocity); +} +void handleControlChange(byte inChannel, byte inControl, byte inValue) +{ + printChannel(inChannel); + Serial.print("Control Change\t"); + Serial.print(inControl); + Serial.print(' '); + Serial.println(inValue); +} +void handleProgramChange(byte inChannel, byte inProgram) +{ + printChannel(inChannel); + Serial.print("Program Change\t"); + Serial.println(inProgram); +} +void handleAfterTouchChannel(byte inChannel, byte inPressure) +{ + printChannel(inChannel); + Serial.print("AT Channel\t"); + Serial.println(inPressure); +} +void handleAfterTouchPoly(byte inChannel, byte inNote, byte inPressure) +{ + printChannel(inChannel); + Serial.print("AT Poly\t"); + Serial.print(inNote); + Serial.print(' '); + Serial.println(inPressure); +} +void handlePitchBend(byte inChannel, int inBend) +{ + printChannel(inChannel); + Serial.print("Pitch Bend\t"); + Serial.println(inBend); +} +void handleStart() +{ + Serial.println("Start"); +} +void handleStop() +{ + Serial.println("Stop"); +} +void handleContinue() +{ + Serial.println("Continue"); +} +void handleClock() +{ + Serial.println("Clock"); +} +void handleSysEx(byte* inArray, unsigned inSize) +{ + Serial.print("SysEx\t[ "); + for (unsigned i = 0; i < inSize; ++i) + { + Serial.print(inArray[i], HEX); + Serial.print(' '); + } + Serial.println(']'); +} + +// -- + +void setup() +{ + Serial.begin(115200); + while(!Serial); // Wait for the Serial Monitor to open + + MIDI.begin(); + MIDI.turnThruOff(); + + setupRxBenchmark(); + startTxBenchmark(); +} + +void loop() +{ + MIDI.read(); +} diff --git a/examples/MidiUSB-Thru/MidiUSB-Thru.ino b/examples/MidiUSB-Thru/MidiUSB-Thru.ino new file mode 100644 index 00000000..13154bb1 --- /dev/null +++ b/examples/MidiUSB-Thru/MidiUSB-Thru.ino @@ -0,0 +1,135 @@ +#include + +#if defined(USBCON) +#include + +static const unsigned sUsbTransportBufferSize = 16; +typedef midi::UsbTransport UsbTransport; + +UsbTransport sUsbTransport; + +MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); + +#else // No USB available, fallback to Serial +MIDI_CREATE_DEFAULT_INSTANCE(); +#endif + +// -- + +void attachCallbacks() +{ + MIDI.setHandleNoteOff(handleNoteOff); + MIDI.setHandleNoteOn(handleNoteOn); + MIDI.setHandleControlChange(handleControlChange); + MIDI.setHandleProgramChange(handleProgramChange); + MIDI.setHandleAfterTouchChannel(handleAfterTouchChannel); + MIDI.setHandleAfterTouchPoly(handleAfterTouchPoly); + MIDI.setHandlePitchBend(handlePitchBend); + MIDI.setHandleStart(handleStart); + MIDI.setHandleStop(handleStop); + MIDI.setHandleContinue(handleContinue); + MIDI.setHandleClock(handleClock); + MIDI.setHandleSystemExclusive(handleSysEx); +} + +// -- + +void printChannel(byte inChannel) +{ + Serial.print("["); + Serial.print(inChannel); + Serial.print("] "); +} + +void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) +{ + printChannel(inChannel); + Serial.print("Note Off\t"); + Serial.print(inNote); + Serial.print(' '); + Serial.println(inVelocity); +} +void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) +{ + printChannel(inChannel); + Serial.print("Note On\t"); + Serial.print(inNote); + Serial.print(' '); + Serial.println(inVelocity); +} +void handleControlChange(byte inChannel, byte inControl, byte inValue) +{ + printChannel(inChannel); + Serial.print("Control Change\t"); + Serial.print(inControl); + Serial.print(' '); + Serial.println(inValue); +} +void handleProgramChange(byte inChannel, byte inProgram) +{ + printChannel(inChannel); + Serial.print("Program Change\t"); + Serial.println(inProgram); +} +void handleAfterTouchChannel(byte inChannel, byte inPressure) +{ + printChannel(inChannel); + Serial.print("AT Channel\t"); + Serial.println(inPressure); +} +void handleAfterTouchPoly(byte inChannel, byte inNote, byte inPressure) +{ + printChannel(inChannel); + Serial.print("AT Poly\t"); + Serial.print(inNote); + Serial.print(' '); + Serial.println(inPressure); +} +void handlePitchBend(byte inChannel, int inBend) +{ + printChannel(inChannel); + Serial.print("Pitch Bend\t"); + Serial.println(inBend); +} +void handleStart() +{ + Serial.println("Start"); +} +void handleStop() +{ + Serial.println("Stop"); +} +void handleContinue() +{ + Serial.println("Continue"); +} +void handleClock() +{ + Serial.println("Clock"); +} +void handleSysEx(byte* inArray, unsigned inSize) +{ + Serial.print("SysEx\t[ "); + for (unsigned i = 0; i < inSize; ++i) + { + Serial.print(inArray[i], HEX); + Serial.print(' '); + } + Serial.println(']'); +} + +// -- + +void setup() +{ + MIDI.begin(MIDI_CHANNEL_OMNI); + + // attachCallbacks(); + // Serial.begin(115200); + // while(!Serial); // Wait for the Serial Monitor to open +} + +void loop() +{ + MIDI.read(); +} From a9e0db5b73baba37dadfecad592bb54f5d6c284d Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 6 Nov 2018 16:58:05 +0100 Subject: [PATCH 25/48] fix: Test examples in CI --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index bf3e8bfe..945ce9e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,12 @@ os: - linux python: - - "2.7" + - '2.7' # Cache PlatformIO packages using Travis CI container-based infrastructure cache: directories: - - "~/.platformio" + - '~/.platformio' env: global: @@ -27,6 +27,8 @@ env: - PLATFORMIO_CI_SRC=examples/DualMerger - PLATFORMIO_CI_SRC=examples/Input - PLATFORMIO_CI_SRC=examples/MidiUSB REQUIRES_USB=1 + - PLATFORMIO_CI_SRC=examples/MidiUSB-CompleteAPI REQUIRES_USB=1 + - PLATFORMIO_CI_SRC=examples/MidiUSB-Thru REQUIRES_USB=1 - PLATFORMIO_CI_SRC=examples/RPN_NRPN - PLATFORMIO_CI_SRC=examples/SimpleSynth From faa7bade0b96773dda332052d96cc7025e757f47 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 7 Nov 2018 12:08:12 +0100 Subject: [PATCH 26/48] cleanup: Dedupe RingBuffer implementation in mocks --- test/mocks/CMakeLists.txt | 5 +- test/mocks/test-mocks_Defs.h | 10 -- test/mocks/test-mocks_SerialMock.h | 38 +------ test/mocks/test-mocks_SerialMock.hpp | 101 +----------------- test/unit-tests/CMakeLists.txt | 10 +- .../tests/unit-tests_SerialMock.cpp | 64 ----------- 6 files changed, 15 insertions(+), 213 deletions(-) delete mode 100644 test/mocks/test-mocks_Defs.h delete mode 100644 test/unit-tests/tests/unit-tests_SerialMock.cpp diff --git a/test/mocks/CMakeLists.txt b/test/mocks/CMakeLists.txt index af82728a..5322dfb3 100644 --- a/test/mocks/CMakeLists.txt +++ b/test/mocks/CMakeLists.txt @@ -4,8 +4,11 @@ add_library(test-mocks STATIC test-mocks.cpp test-mocks.h test-mocks_Namespace.h - test-mocks_Defs.h test-mocks_SerialMock.cpp test-mocks_SerialMock.hpp test-mocks_SerialMock.h ) + +target_link_libraries(test-mocks + midi +) diff --git a/test/mocks/test-mocks_Defs.h b/test/mocks/test-mocks_Defs.h deleted file mode 100644 index 9ed453a3..00000000 --- a/test/mocks/test-mocks_Defs.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "test-mocks.h" -#include - -BEGIN_TEST_MOCKS_NAMESPACE - -typedef uint8_t uint8; - -END_TEST_MOCKS_NAMESPACE diff --git a/test/mocks/test-mocks_SerialMock.h b/test/mocks/test-mocks_SerialMock.h index 028205d6..f3ab833d 100644 --- a/test/mocks/test-mocks_SerialMock.h +++ b/test/mocks/test-mocks_SerialMock.h @@ -1,39 +1,11 @@ #pragma once #include "test-mocks.h" -#include "test-mocks_Defs.h" +#include +#include BEGIN_TEST_MOCKS_NAMESPACE -template -class RingBuffer -{ -public: - RingBuffer(); - ~RingBuffer(); - -public: - int getLength() const; - bool isEmpty() const; - -public: - void write(DataType inData); - void write(const DataType* inData, int inSize); - void clear(); - -public: - DataType peek() const; - DataType read(); - void read(DataType* outData, int inSize); - -private: - DataType mData[Size]; - DataType* mWriteHead; - DataType* mReadHead; -}; - -// ----------------------------------------------------------------------------- - template class SerialMock { @@ -44,14 +16,14 @@ class SerialMock public: // Arduino Serial API void begin(int inBaudrate); int available() const; - void write(uint8 inData); - uint8 read(); + void write(uint8_t inData); + uint8_t read(); public: // Test Helpers API void moveTxToRx(); // Simulate loopback public: - typedef RingBuffer Buffer; + typedef midi::RingBuffer Buffer; Buffer mTxBuffer; Buffer mRxBuffer; int mBaudrate; diff --git a/test/mocks/test-mocks_SerialMock.hpp b/test/mocks/test-mocks_SerialMock.hpp index c0181b20..3c06e29a 100644 --- a/test/mocks/test-mocks_SerialMock.hpp +++ b/test/mocks/test-mocks_SerialMock.hpp @@ -2,103 +2,6 @@ BEGIN_TEST_MOCKS_NAMESPACE -template -RingBuffer::RingBuffer() - : mWriteHead(mData) - , mReadHead(mData) -{ - memset(mData, DataType(0), Size * sizeof(DataType)); -} - -template -RingBuffer::~RingBuffer() -{ -} - -// ----------------------------------------------------------------------------- - -template -int RingBuffer::getLength() const -{ - if (mReadHead == mWriteHead) - { - return 0; - } - else if (mWriteHead > mReadHead) - { - return int(mWriteHead - mReadHead); - } - else - { - return int(mWriteHead - mData) + Size - int(mReadHead - mData); - } -} - -template -bool RingBuffer::isEmpty() const -{ - return mReadHead == mWriteHead; -} - -// ----------------------------------------------------------------------------- - -template -void RingBuffer::write(DataType inData) -{ - *mWriteHead++ = inData; - if (mWriteHead >= mData + Size) - { - mWriteHead = mData; - } -} - -template -void RingBuffer::write(const DataType* inData, int inSize) -{ - for (int i = 0; i < inSize; ++i) - { - write(inData[i]); - } -} - -template -void RingBuffer::clear() -{ - memset(mData, DataType(0), Size * sizeof(DataType)); - mReadHead = mData; - mWriteHead = mData; -} - -// ----------------------------------------------------------------------------- - -template -DataType RingBuffer::peek() const -{ - return *mReadHead; -} - -template -DataType RingBuffer::read() -{ - const DataType data = *mReadHead++; - if (mReadHead >= mData + Size) - { - mReadHead = mData; - } - return data; -} - -template -void RingBuffer::read(DataType* outData, int inSize) -{ - for (int i = 0; i < inSize; ++i) - { - outData[i] = read(); - } -} - -// ============================================================================= - template SerialMock::SerialMock() { @@ -126,13 +29,13 @@ int SerialMock::available() const } template -void SerialMock::write(uint8 inData) +void SerialMock::write(uint8_t inData) { mTxBuffer.write(inData); } template -uint8 SerialMock::read() +uint8_t SerialMock::read() { return mRxBuffer.read(); } diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index 59bfb7be..a63cbd4e 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -3,14 +3,13 @@ include(CMakeToolsHelpers OPTIONAL) project(unit-tests) include_directories( - ${unit-tests_SOURCE_DIR} - ${gtest_SOURCE_DIR}/include - ${gmock_SOURCE_DIR}/include - "../../external/midi-usb/src" + "${unit-tests_SOURCE_DIR}" + "${gtest_SOURCE_DIR}/include" + "${gmock_SOURCE_DIR}/include" + "${ROOT_SOURCE_DIR}/external/midi-usb/src" ) add_executable(unit-tests - unit-tests.cpp unit-tests.h unit-tests_Namespace.h @@ -20,7 +19,6 @@ add_executable(unit-tests tests/unit-tests_Settings.h tests/unit-tests_SysExCodec.cpp tests/unit-tests_RingBuffer.cpp - tests/unit-tests_SerialMock.cpp tests/unit-tests_MidiInput.cpp tests/unit-tests_MidiInputCallbacks.cpp tests/unit-tests_MidiOutput.cpp diff --git a/test/unit-tests/tests/unit-tests_SerialMock.cpp b/test/unit-tests/tests/unit-tests_SerialMock.cpp deleted file mode 100644 index ae204fe2..00000000 --- a/test/unit-tests/tests/unit-tests_SerialMock.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "unit-tests.h" -#include - -BEGIN_UNNAMED_NAMESPACE - -USING_NAMESPACE_TEST_MOCKS -using namespace testing; - -TEST(RingBufferMock, initialState) -{ - typedef RingBuffer Buffer; - Buffer buffer; - EXPECT_EQ(buffer.getLength(), 0); - EXPECT_EQ(buffer.isEmpty(), true); - buffer.clear(); - EXPECT_EQ(buffer.getLength(), 0); - EXPECT_EQ(buffer.isEmpty(), true); -} - -TEST(RingBufferMock, uint8) -{ - typedef RingBuffer Buffer; - Buffer buffer; - - buffer.write(42); - EXPECT_EQ(buffer.getLength(), 1); - EXPECT_EQ(buffer.isEmpty(), false); - - const uint8 read = buffer.read(); - EXPECT_EQ(read, 42); - EXPECT_EQ(buffer.getLength(), 0); - EXPECT_EQ(buffer.isEmpty(), true); - - const uint8 data[] = "Hello, World!"; - buffer.write(data, 13); - EXPECT_EQ(buffer.getLength(), 5); // 13 % 8 - EXPECT_EQ(buffer.isEmpty(), false); - - uint8 output[8] = { 0 }; - buffer.read(output, 8); - const uint8 expected[8] = { - 'o', 'r', 'l', 'd', '!', ',', ' ', 'W', - }; - EXPECT_THAT(output, ContainerEq(expected)); - - buffer.clear(); - EXPECT_EQ(buffer.getLength(), 0); - EXPECT_EQ(buffer.isEmpty(), true); -} - -TEST(RingBufferMock, uint32) -{ - typedef RingBuffer Buffer; - Buffer buffer; - buffer.write(42); - EXPECT_EQ(buffer.getLength(), 1); - EXPECT_EQ(buffer.isEmpty(), false); - const uint8 read = buffer.read(); - EXPECT_EQ(read, 42); - EXPECT_EQ(buffer.getLength(), 0); - EXPECT_EQ(buffer.isEmpty(), true); -} - -END_UNNAMED_NAMESPACE From b3ab309781e2ec1b4ae73e8666423392cfa081c8 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 7 Nov 2018 12:09:37 +0100 Subject: [PATCH 27/48] doc: Update Readme Fix typos, add USB support --- README.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 3b8608ea..e75ba6a4 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,11 @@ [![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) -This library enables MIDI I/O communications on the Arduino serial ports. +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). - 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). @@ -16,36 +17,36 @@ This library enables MIDI I/O communications on the Arduino serial ports. - [Callbacks](https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks) to handle input messages more easily. - Last received message is saved until a new one arrives. - Configurable: [overridable template-based settings](https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-custom-Settings). -- Create more than one MIDI port for mergers/splitters applications. +- Create more than one MIDI interface for mergers/splitters applications. - Use any serial port, hardware or software. ### Getting Started -1. Use Arduino's Library Manager to install the library. +1. Use the Arduino Library Manager to install the library. ![Type "MIDI" in the Arduino IDE Library Manager](res/library-manager.jpg) 2. Start coding: - ```c++ - #include +```c++ +#include - // Created and binds the MIDI interface to the default hardware Serial port - MIDI_CREATE_DEFAULT_INSTANCE(); +// Create and bind the MIDI interface to the default hardware Serial port +MIDI_CREATE_DEFAULT_INSTANCE(); - void setup() - { - MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages - } +void setup() +{ + MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages +} - void loop() - { - // Send note 42 with velocity 127 on channel 1 - MIDI.sendNoteOn(42, 127, 1); +void loop() +{ + // Send note 42 with velocity 127 on channel 1 + MIDI.sendNoteOn(42, 127, 1); - // Read incoming messages - MIDI.read(); - } - ``` + // Read incoming messages + MIDI.read(); +} +``` 3. Read the [documentation](#documentation) or watch the awesome video tutorials from [Notes & Volts](https://www.youtube.com/playlist?list=PL4_gPbvyebyH2xfPXePHtx8gK5zPBrVkg). From 96b7fd45378e6426eae8cb7be93ce8d838f2ee2a Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 7 Nov 2018 12:09:47 +0100 Subject: [PATCH 28/48] chore: Config file update --- .vscode/c_cpp_properties.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 81b45d30..74577b84 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -25,7 +25,8 @@ ], "forcedInclude": [ "/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino/Arduino.h" - ] + ], + "configurationProvider": "vector-of-bool.cmake-tools" }, { "name": "Linux", From f067883dd8ab8b0bae0c57fad06b68e54e0d91c3 Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Sun, 18 Aug 2019 06:52:50 +0200 Subject: [PATCH 29/48] changed default size of enums (int) to a smaller type Easily save over 200 bytes for the library by using the smallest type for these MIDI types. --- src/midi_Defs.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/midi_Defs.h b/src/midi_Defs.h index d0f86895..786da708 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -62,7 +62,7 @@ typedef byte FilterMode; // ----------------------------------------------------------------------------- /*! Enumeration of MIDI types */ -enum MidiType +enum MidiType: uint8_t { InvalidType = 0x00, ///< For notifying errors NoteOff = 0x80, ///< Note Off @@ -116,7 +116,7 @@ enum __attribute__ ((deprecated)) MidiFilterMode See the detailed controllers numbers & description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums */ -enum MidiControlChangeNumber +enum MidiControlChangeNumber: uint8_t { // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- BankSelect = 0, @@ -192,7 +192,7 @@ enum MidiControlChangeNumber struct RPN { - enum RegisteredParameterNumbers + enum RegisteredParameterNumbers: uint16_t { PitchBendSensitivity = 0x0000, ChannelFineTuning = 0x0001, From 2c657a38d107bd172edaf7608071b445a48b9ad3 Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Wed, 8 Apr 2020 10:36:10 +0200 Subject: [PATCH 30/48] Added support for Active Sensing and SysEx command segments (v4.4.0) (#138) * stuff to ignore list * Update midi_Defs.h * added support for ActiveSensing From: https://www.midi.org/specifications/item/table-1-summary-of-midi-message Active Sensing. This message is intended to be sent repeatedly to tell the receiver that a connection is alive. Use of this message is optional. When initially received, the receiver will expect to receive another Active Sensing message each 300ms (max), and if it does not then it will assume that the connection has been terminated. At termination, the receiver will turn off all voices and return to normal (non- active sensing) operation. * replaced ActiveSensing with Sender Active Sensing Receiver active Sensing is not implemented (yet) * added support for receiving SysEx command segments Very long SysEx mesaages are cut in to multiple command segments Where a normal SysEx starts with F0 and ends with F7, the first segment will start with F0 and end with F0, the middle section (optional) start with F7 and ends with F7 - the last segments starts with F7 and ends with F0 * lift and shift of the Serial code into a seperate class, allowing for other serializers lift and shift of the Serial code into a seperate class, allowing for other serializers as AppleMIDI, USBMIDI, ipMIDI, BLE-MIDI * added Platform class to abstract millis() * add MidiType to beginTransmission Avoids reparsing of the stream in the transport layer, in order to get the MidiType * active sensing defaults to false, to avoid spamming devices * set ActiveSensing Periodicity in ms Gives more control over the bool value. Typical value is 300 (ms) - an Active Sensing command is send every 300ms; or 0 (zero) to disable sending ActiveSensing * added namespace reference * renamed Encoder -> Transport, added MidiType to beginTransmission in serialMIDI * added callback for Message before the specific callback are called, a generic mMessage callback can be fired to inform the user that 'a' message has been received. * added send(MidiMessage), added (untested) bridge example added send(MidiMessage) for Bridge application (that convert MIDI transport x into MIDI transport y), avoiding parsing entry a stream, setting up all callback - whilst this allows for passing the content, without to much processing/parsing. Had to move mPendingMessageExpectedLenght into MidiMessage to avoid parsing the data again, just to know the size Added Bridge example (untested) * changed based on Franky47's feedback - send: pass the message as a const ref, to avoid copies - add mSerial.begin (with Baudrate - added Settings) - ThruActivated defaults to true - class name serialMIDI => SerialMIDI * return reference to Transport layer * return a *pointer* (not a ref) to the transport layer * mPendingMessageExpectedLength as before mMessage.length is reset to 0 before the callback, so bring back mPendingMessageExpectedLength as before, and set mMessage.length (and not reset it) * DefaultPlatform for Arduino * strong typing Channel in the callbacks * aliasing for callbacks (and default initializers * cleanup setHandleMessage unfortunately, not using aliases yet (too complex) * Fixed "FML" case: fall down here with an overflown SysEx.. Splitting larger incoming messages into smaller SysEx packets - using an RrtpMidi trick: // first: 0xF0 .... 0xF0 // midlle: 0xF7 .... 0xF0 // last: 0xF7 .... 0xF7 see: https://tools.ietf.org/html/rfc4695#section-3.2 (Understanding this is not part of the original MIDI spec, but does allow for receiving very very large SysEx messages on a small footprint (does require some 'handy' parsing)) SysExMaxSize in Settings can be reduced significatly (16bytes works fine - pending use case) * + receiver ActiveSensing, + error Callback 1) Active Sensing: Once an ActiveSensing message is received, the system checks for timeouts: if no message is received within the specified 300ms (see in _Defs), an error is set and the checks for timeout stop. 2) added a callback for errors. 2 errors are defined: parse error and ActiveSensing timeout * cleanup of basic example use LED_BUILTIN * + Manufacturer System Exclusive ID (incomplete) * + noteValues * added SendCommon, consistent with SendRealTime including: sendClock, sendStart, sendStop, sendContinue, sendActiveSensing, sendSystemReset * UseSenderActiveSensing & UseReceiverActiveSensing to enable/disable Active Sensing UseSenderActiveSensing & UseReceiverActiveSensing in Settings are global switches to turn on/off ActiveSensing (and save memory) * use #defined value, not the literal * cleanup * removed deprecated MidiFilterMode (to be removed for v5) * getting ready for v5 * prepare for v5 * updating authors * Removed USB examples * added header * removed typedef when instanciating multiple instances of SerialMIDI, a redefinition error accors because of typing of the serial##Name. Fix: removed the typdef, a bit harder to read, but avoided complex #ifdef * move bridge example to other Transport mechanisms * Update Bench.ino * Create NoteNames.ino demonstrate use of MIDI_NAMESPACE::NoteValues[inNote].strptr * moved #version to MIDI.h #version of the library does not belong in general MIDI definitions (making midi_Defs.h as generic as possible - usable on other platforms) * adding comment section * shorter syntax using iif * moved code to avoid doc clash * added Tick (0xF9) * Simplified NoteValues Need need for extra index (that is the same as the noteName anyway) * Removed Undefined_F9 (Tick) as an invalid token (so it valid now) * removed references to USB in the test scripts * setting the CMake env * removed USB references * struggling with travis * LCOV_EXCL_LINE in sendCommon * order of initialization * reorder variables for initialisation * extending platforms * adding boards (but not the Zero) * fixing cmake * moved RingBuffer to SerialMock * fix cmake removing RingBuffer * Update CMakeLists.txt * using bitwire operators (works on all platforms) * Update midi_Platform.h * removed NoteNames * simplify test (will be rolled back later) * Update unit-tests_MidiInput.cpp * Update unit-tests_MidiInput.cpp * Revert "Update midi_Platform.h" This reverts commit bd2f6476b25179cba5295228560c2a3eb44e679c. * bring in other Platform (remove usb) * added BaudRate * Revert "using bitwire operators (works on all platforms)" This reverts commit 122ebe1dc391b36102bc86a52e452fbd5b234a11. * Revert "moved code to avoid doc clash" This reverts commit ef878344cdce61bb715ba8eb7a573ed989fb5bae. * restored after very funcky commits and rollbacks (Sorry!) * add Transport layer * removed SerialMock test * Update unit-tests_MidiInput.cpp * Update unit-tests_MidiInput.cpp * Update unit-tests_MidiInput.cpp * Update unit-tests_MidiInput.cpp * fixed MidiInterface MidiInterface --- .gitignore | 5 + .travis.yml | 10 +- doc/usb-midi.md | 16 - examples/AltPinSerial/AltPinSerial.ino | 27 + examples/AltPinSerial/altPinSerialMIDI.h | 54 ++ examples/Basic_IO/Basic_IO.ino | 8 +- examples/Bench/Bench.ino | 2 +- examples/DualMerger/DualMerger.ino | 7 +- examples/ErrorCallback/ErrorCallback.ino | 25 + .../MidiUSB-CompleteAPI.ino | 181 ---- examples/MidiUSB-Thru/MidiUSB-Thru.ino | 135 --- examples/MidiUSB/MidiUSB.ino | 45 - external/google-test | 2 +- external/midi-usb | 1 - keywords.txt | 8 + library.json | 2 +- library.properties | 4 +- src/CMakeLists.txt | 6 +- src/MIDI.h | 135 ++- src/MIDI.hpp | 785 +++++++++------- src/midi_Defs.h | 104 +-- src/midi_Message.h | 6 +- ...i_UsbPacketInterface.h => midi_Platform.h} | 33 +- src/midi_RingBuffer.h | 68 -- src/midi_RingBuffer.hpp | 145 --- src/midi_Settings.h | 34 +- src/midi_UsbDefs.h | 135 --- src/midi_UsbPacketInterface.hpp | 146 --- src/midi_UsbTransport.h | 62 -- src/midi_UsbTransport.hpp | 102 --- src/serialMIDI.h | 115 +++ test/mocks/test-mocks_SerialMock.h | 30 +- test/mocks/test-mocks_SerialMock.hpp | 97 ++ test/unit-tests/CMakeLists.txt | 4 - .../unit-tests/tests/unit-tests_MidiInput.cpp | 145 ++- .../tests/unit-tests_MidiInputCallbacks.cpp | 8 +- .../tests/unit-tests_MidiOutput.cpp | 107 ++- test/unit-tests/tests/unit-tests_MidiThru.cpp | 50 +- .../tests/unit-tests_MidiUsbDefs.cpp | 60 -- .../unit-tests_MidiUsbPacketInterface.cpp | 843 ------------------ .../tests/unit-tests_RingBuffer.cpp | 203 ----- test/unit-tests/tests/unit-tests_Settings.cpp | 2 - 42 files changed, 1235 insertions(+), 2722 deletions(-) delete mode 100644 doc/usb-midi.md create mode 100644 examples/AltPinSerial/AltPinSerial.ino create mode 100644 examples/AltPinSerial/altPinSerialMIDI.h create mode 100644 examples/ErrorCallback/ErrorCallback.ino delete mode 100644 examples/MidiUSB-CompleteAPI/MidiUSB-CompleteAPI.ino delete mode 100644 examples/MidiUSB-Thru/MidiUSB-Thru.ino delete mode 100644 examples/MidiUSB/MidiUSB.ino delete mode 160000 external/midi-usb rename src/{midi_UsbPacketInterface.h => midi_Platform.h} (69%) delete mode 100644 src/midi_RingBuffer.h delete mode 100644 src/midi_RingBuffer.hpp delete mode 100644 src/midi_UsbDefs.h delete mode 100644 src/midi_UsbPacketInterface.hpp delete mode 100644 src/midi_UsbTransport.h delete mode 100644 src/midi_UsbTransport.hpp create mode 100644 src/serialMIDI.h delete mode 100644 test/unit-tests/tests/unit-tests_MidiUsbDefs.cpp delete mode 100644 test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp delete mode 100644 test/unit-tests/tests/unit-tests_RingBuffer.cpp diff --git a/.gitignore b/.gitignore index b9e2962d..eaad794c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,8 @@ logs/ build/ .vscode/.cmaketools.json +src/.DS_Store +examples/.DS_Store +.DS_Store +test/xcode +.development diff --git a/.travis.yml b/.travis.yml index 945ce9e8..5fb5ba21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,10 +25,8 @@ env: - 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/MidiUSB REQUIRES_USB=1 - - PLATFORMIO_CI_SRC=examples/MidiUSB-CompleteAPI REQUIRES_USB=1 - - PLATFORMIO_CI_SRC=examples/MidiUSB-Thru REQUIRES_USB=1 - PLATFORMIO_CI_SRC=examples/RPN_NRPN - PLATFORMIO_CI_SRC=examples/SimpleSynth @@ -76,11 +74,7 @@ script: # Build current example - | if [ ! "${BUILD_UNIT_TESTS}" ]; then - if [ "${REQUIRES_USB}" ]; then - platformio ci --lib="." --lib=external/midi-usb --board="due" --board="dueUSB" --board="zero" --board="zeroUSB" --board="leonardo" - else - platformio ci --lib="." --board=uno --board="due" --board="zero" --board="leonardo" --board="micro" --board="nanoatmega328" --board="megaatmega2560" --board="teensy20" --board="teensy20pp" --board="teensy30" --board="teensy31" - fi + platformio ci --lib="." --board=uno --board="due" --board="leonardo" --board="micro" --board="nanoatmega328" --board="megaatmega2560" fi after_success: diff --git a/doc/usb-midi.md b/doc/usb-midi.md deleted file mode 100644 index 0c19f681..00000000 --- a/doc/usb-midi.md +++ /dev/null @@ -1,16 +0,0 @@ -# Using the library for MIDI through USB - -Some boards have "native" USB functionality, which means they can directly -connect to a computer and appear as a USB MIDI device. - -Here are the officially supported boards: - -- Arduino Leonardo -- Arduino Due - -## Pre-requisites - -To use USB MIDI to send messages to the computer, you must disable Running -Status in the settings. This is because the USB transport is stateless and has -no knowledge of the last message's running status. -The setting is off by default, so if you use custom settings, keep that in mind. diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino new file mode 100644 index 00000000..311f6449 --- /dev/null +++ b/examples/AltPinSerial/AltPinSerial.ino @@ -0,0 +1,27 @@ +#include +#include "altPinSerialMIDI.h" + +// Simple tutorial on how to receive and send MIDI messages. +// Here, when receiving any message on channel 4, the Arduino +// will blink a led and play back a note for 1 second. + +AltSerialMIDI serialMIDI(Serial1, 18, 19); +MIDI_NAMESPACE::MidiInterface> MIDI((AltSerialMIDI&)serialMIDI); + +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); + MIDI.begin(4); // Launch MIDI and listen to channel 4 +} + +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); + } +} diff --git a/examples/AltPinSerial/altPinSerialMIDI.h b/examples/AltPinSerial/altPinSerialMIDI.h new file mode 100644 index 00000000..0d684060 --- /dev/null +++ b/examples/AltPinSerial/altPinSerialMIDI.h @@ -0,0 +1,54 @@ +struct DefaultAltSerialSettings +{ + static const long BaudRate = 31250; +}; + +template +class AltSerialMIDI +{ + typedef _Settings Settings; + + uint8_t rxPin = 0; + uint8_t txPin = 0; + + friend class midi::MidiInterface>; + +public: + AltSerialMIDI(SerialPort& inSerial, uint8_t inRxPin, uint8_t inTxPin) + : mSerial(inSerial), rxPin(inRxPin), txPin(inTxPin) + { + }; + +protected: + void begin() + { + mSerial.begin(Settings::BaudRate, SERIAL_8N1, rxPin, txPin); + } + + bool beginTransmission(midi::MidiType) + { + return true; + }; + + void write(byte value) + { + mSerial.write(value); + }; + + void endTransmission() + { + }; + + byte read() + { + return mSerial.read(); + }; + + unsigned available() + { + return mSerial.available(); + }; + +private: + SerialPort& mSerial; +}; diff --git a/examples/Basic_IO/Basic_IO.ino b/examples/Basic_IO/Basic_IO.ino index 434cf3fe..82097192 100644 --- a/examples/Basic_IO/Basic_IO.ino +++ b/examples/Basic_IO/Basic_IO.ino @@ -6,11 +6,9 @@ MIDI_CREATE_DEFAULT_INSTANCE(); -static const unsigned ledPin = 13; // LED pin on Arduino Uno - void setup() { - pinMode(ledPin, OUTPUT); + pinMode(LED_BUILTIN, OUTPUT); MIDI.begin(4); // Launch MIDI and listen to channel 4 } @@ -18,10 +16,10 @@ void loop() { if (MIDI.read()) // If we have received a message { - digitalWrite(ledPin, HIGH); + 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(ledPin, LOW); + digitalWrite(LED_BUILTIN, LOW); } } diff --git a/examples/Bench/Bench.ino b/examples/Bench/Bench.ino index a97eaafb..75855c36 100644 --- a/examples/Bench/Bench.ino +++ b/examples/Bench/Bench.ino @@ -71,8 +71,8 @@ void setup() midiBench.setHandleNoteOn(handleNoteOn); midiBench.begin(); - while(!Serial); Serial.begin(115200); + while(!Serial); Serial.println("Arduino Ready"); midiBench.sendNoteOn(69,127,1); diff --git a/examples/DualMerger/DualMerger.ino b/examples/DualMerger/DualMerger.ino index c87ae535..c6c30e36 100644 --- a/examples/DualMerger/DualMerger.ino +++ b/examples/DualMerger/DualMerger.ino @@ -6,12 +6,17 @@ // A out = A in + B in // B out = B in + A in -#ifdef ARDUINO_SAM_DUE +#if defined(ARDUINO_SAM_DUE) MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); #elif defined(ARDUINO_SAMD_ZERO) MIDI_CREATE_INSTANCE(HardwareSerial, SerialUSB, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); +#elif defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) + #include + SoftwareSerial softSerial(2,3); + MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiA); + MIDI_CREATE_INSTANCE(SoftwareSerial, softSerial, midiB); #else #include SoftwareSerial softSerial(2,3); diff --git a/examples/ErrorCallback/ErrorCallback.ino b/examples/ErrorCallback/ErrorCallback.ino new file mode 100644 index 00000000..e35cf81f --- /dev/null +++ b/examples/ErrorCallback/ErrorCallback.ino @@ -0,0 +1,25 @@ +#include + +// Before running the program below, make sure you set +// UseReceiverActiveSensing (optionally UseSenderActiveSensing) in Settings.h to true + +MIDI_CREATE_DEFAULT_INSTANCE(); + +void handleError(int8_t err) +{ + digitalWrite(LED_BUILTIN, (err == 0)? LOW : HIGH); +} + +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + + MIDI.setHandleError(handleError); + MIDI.begin(1); +} + +void loop() +{ + MIDI.read(); +} diff --git a/examples/MidiUSB-CompleteAPI/MidiUSB-CompleteAPI.ino b/examples/MidiUSB-CompleteAPI/MidiUSB-CompleteAPI.ino deleted file mode 100644 index 4b6690ea..00000000 --- a/examples/MidiUSB-CompleteAPI/MidiUSB-CompleteAPI.ino +++ /dev/null @@ -1,181 +0,0 @@ -#include - -#if defined(USBCON) -#include - -static const unsigned sUsbTransportBufferSize = 16; -typedef midi::UsbTransport UsbTransport; - -UsbTransport sUsbTransport; - -MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); - -#else // No USB available, fallback to Serial -MIDI_CREATE_DEFAULT_INSTANCE(); -#endif - -// -- - -void setupRxBenchmark() -{ - MIDI.setHandleNoteOff(handleNoteOff); - MIDI.setHandleNoteOn(handleNoteOn); - MIDI.setHandleControlChange(handleControlChange); - MIDI.setHandleProgramChange(handleProgramChange); - MIDI.setHandleAfterTouchChannel(handleAfterTouchChannel); - MIDI.setHandleAfterTouchPoly(handleAfterTouchPoly); - MIDI.setHandlePitchBend(handlePitchBend); - MIDI.setHandleStart(handleStart); - MIDI.setHandleStop(handleStop); - MIDI.setHandleContinue(handleContinue); - MIDI.setHandleClock(handleClock); - MIDI.setHandleSystemExclusive(handleSysEx); -} - -void startTxBenchmark() -{ - for (int i = 0; i < 128; ++i) - { - MIDI.sendNoteOn(i, 127, 1); - MIDI.sendNoteOff(i, 127, 1); - } - for (int i = 0; i < 128; ++i) - { - MIDI.sendControlChange(i, 127, 1); - MIDI.sendControlChange(i, 0, 1); - } - for (int i = 0; i < 128; ++i) - { - MIDI.sendProgramChange(i, 1); - } - MIDI.sendRealTime(midi::Start); - MIDI.sendRealTime(midi::Stop); - MIDI.sendRealTime(midi::Continue); - MIDI.sendRealTime(midi::Clock); - - const byte length = 128; - const byte data[128] = { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, - 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, - 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, - 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, - 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, - }; - MIDI.sendSysEx(length, data, false); -} - -// -- - -void printChannel(byte inChannel) -{ - Serial.print("["); - Serial.print(inChannel); - Serial.print("] "); -} - -void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) -{ - printChannel(inChannel); - Serial.print("Note Off\t"); - Serial.print(inNote); - Serial.print(' '); - Serial.println(inVelocity); -} -void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) -{ - printChannel(inChannel); - Serial.print("Note On\t"); - Serial.print(inNote); - Serial.print(' '); - Serial.println(inVelocity); -} -void handleControlChange(byte inChannel, byte inControl, byte inValue) -{ - printChannel(inChannel); - Serial.print("Control Change\t"); - Serial.print(inControl); - Serial.print(' '); - Serial.println(inValue); -} -void handleProgramChange(byte inChannel, byte inProgram) -{ - printChannel(inChannel); - Serial.print("Program Change\t"); - Serial.println(inProgram); -} -void handleAfterTouchChannel(byte inChannel, byte inPressure) -{ - printChannel(inChannel); - Serial.print("AT Channel\t"); - Serial.println(inPressure); -} -void handleAfterTouchPoly(byte inChannel, byte inNote, byte inPressure) -{ - printChannel(inChannel); - Serial.print("AT Poly\t"); - Serial.print(inNote); - Serial.print(' '); - Serial.println(inPressure); -} -void handlePitchBend(byte inChannel, int inBend) -{ - printChannel(inChannel); - Serial.print("Pitch Bend\t"); - Serial.println(inBend); -} -void handleStart() -{ - Serial.println("Start"); -} -void handleStop() -{ - Serial.println("Stop"); -} -void handleContinue() -{ - Serial.println("Continue"); -} -void handleClock() -{ - Serial.println("Clock"); -} -void handleSysEx(byte* inArray, unsigned inSize) -{ - Serial.print("SysEx\t[ "); - for (unsigned i = 0; i < inSize; ++i) - { - Serial.print(inArray[i], HEX); - Serial.print(' '); - } - Serial.println(']'); -} - -// -- - -void setup() -{ - Serial.begin(115200); - while(!Serial); // Wait for the Serial Monitor to open - - MIDI.begin(); - MIDI.turnThruOff(); - - setupRxBenchmark(); - startTxBenchmark(); -} - -void loop() -{ - MIDI.read(); -} diff --git a/examples/MidiUSB-Thru/MidiUSB-Thru.ino b/examples/MidiUSB-Thru/MidiUSB-Thru.ino deleted file mode 100644 index 13154bb1..00000000 --- a/examples/MidiUSB-Thru/MidiUSB-Thru.ino +++ /dev/null @@ -1,135 +0,0 @@ -#include - -#if defined(USBCON) -#include - -static const unsigned sUsbTransportBufferSize = 16; -typedef midi::UsbTransport UsbTransport; - -UsbTransport sUsbTransport; - -MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); - -#else // No USB available, fallback to Serial -MIDI_CREATE_DEFAULT_INSTANCE(); -#endif - -// -- - -void attachCallbacks() -{ - MIDI.setHandleNoteOff(handleNoteOff); - MIDI.setHandleNoteOn(handleNoteOn); - MIDI.setHandleControlChange(handleControlChange); - MIDI.setHandleProgramChange(handleProgramChange); - MIDI.setHandleAfterTouchChannel(handleAfterTouchChannel); - MIDI.setHandleAfterTouchPoly(handleAfterTouchPoly); - MIDI.setHandlePitchBend(handlePitchBend); - MIDI.setHandleStart(handleStart); - MIDI.setHandleStop(handleStop); - MIDI.setHandleContinue(handleContinue); - MIDI.setHandleClock(handleClock); - MIDI.setHandleSystemExclusive(handleSysEx); -} - -// -- - -void printChannel(byte inChannel) -{ - Serial.print("["); - Serial.print(inChannel); - Serial.print("] "); -} - -void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) -{ - printChannel(inChannel); - Serial.print("Note Off\t"); - Serial.print(inNote); - Serial.print(' '); - Serial.println(inVelocity); -} -void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) -{ - printChannel(inChannel); - Serial.print("Note On\t"); - Serial.print(inNote); - Serial.print(' '); - Serial.println(inVelocity); -} -void handleControlChange(byte inChannel, byte inControl, byte inValue) -{ - printChannel(inChannel); - Serial.print("Control Change\t"); - Serial.print(inControl); - Serial.print(' '); - Serial.println(inValue); -} -void handleProgramChange(byte inChannel, byte inProgram) -{ - printChannel(inChannel); - Serial.print("Program Change\t"); - Serial.println(inProgram); -} -void handleAfterTouchChannel(byte inChannel, byte inPressure) -{ - printChannel(inChannel); - Serial.print("AT Channel\t"); - Serial.println(inPressure); -} -void handleAfterTouchPoly(byte inChannel, byte inNote, byte inPressure) -{ - printChannel(inChannel); - Serial.print("AT Poly\t"); - Serial.print(inNote); - Serial.print(' '); - Serial.println(inPressure); -} -void handlePitchBend(byte inChannel, int inBend) -{ - printChannel(inChannel); - Serial.print("Pitch Bend\t"); - Serial.println(inBend); -} -void handleStart() -{ - Serial.println("Start"); -} -void handleStop() -{ - Serial.println("Stop"); -} -void handleContinue() -{ - Serial.println("Continue"); -} -void handleClock() -{ - Serial.println("Clock"); -} -void handleSysEx(byte* inArray, unsigned inSize) -{ - Serial.print("SysEx\t[ "); - for (unsigned i = 0; i < inSize; ++i) - { - Serial.print(inArray[i], HEX); - Serial.print(' '); - } - Serial.println(']'); -} - -// -- - -void setup() -{ - MIDI.begin(MIDI_CHANNEL_OMNI); - - // attachCallbacks(); - // Serial.begin(115200); - // while(!Serial); // Wait for the Serial Monitor to open -} - -void loop() -{ - MIDI.read(); -} diff --git a/examples/MidiUSB/MidiUSB.ino b/examples/MidiUSB/MidiUSB.ino deleted file mode 100644 index 2f677aae..00000000 --- a/examples/MidiUSB/MidiUSB.ino +++ /dev/null @@ -1,45 +0,0 @@ -#include - -#if defined(USBCON) -#include - -static const unsigned sUsbTransportBufferSize = 16; -typedef midi::UsbTransport UsbTransport; - -UsbTransport sUsbTransport; - -MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); - -#else // No USB available, fallback to Serial -MIDI_CREATE_DEFAULT_INSTANCE(); -#endif - -// -- - -void handleNoteOn(byte inChannel, byte inNumber, byte inVelocity) -{ - Serial.print("NoteOn "); - Serial.print(inNumber); - Serial.print("\tvelocity: "); - Serial.println(inVelocity); -} -void handleNoteOff(byte inChannel, byte inNumber, byte inVelocity) -{ - Serial.print("NoteOff "); - Serial.print(inNumber); - Serial.print("\tvelocity: "); - Serial.println(inVelocity); -} - -void setup() { - Serial.begin(115200); - while (!Serial); - MIDI.begin(); - MIDI.setHandleNoteOn(handleNoteOn); - MIDI.setHandleNoteOff(handleNoteOff); - Serial.println("Arduino ready."); -} - -void loop() { - MIDI.read(); -} diff --git a/external/google-test b/external/google-test index ec44c6c1..ecd53086 160000 --- a/external/google-test +++ b/external/google-test @@ -1 +1 @@ -Subproject commit ec44c6c1675c25b9827aacd08c02433cccde7780 +Subproject commit ecd530865cefdfa7dea58e84f6aa1b548950363d diff --git a/external/midi-usb b/external/midi-usb deleted file mode 160000 index 1f81cc9f..00000000 --- a/external/midi-usb +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1f81cc9f52184bbc271bfcee63b5a0efb03c8cf7 diff --git a/keywords.txt b/keywords.txt index a6d0b58f..cfb39f4f 100644 --- a/keywords.txt +++ b/keywords.txt @@ -29,6 +29,14 @@ sendSongPosition KEYWORD2 sendSongSelect KEYWORD2 sendTuneRequest KEYWORD2 sendRealTime KEYWORD2 +sendCommon KEYWORD2 +sendClock KEYWORD2 +sendStart KEYWORD2 +sendStop KEYWORD2 +sendTick KEYWORD2 +sendContinue KEYWORD2 +sendActiveSensing KEYWORD2 +sendSystemReset KEYWORD2 beginRpn KEYWORD2 sendRpnValue KEYWORD2 sendRpnIncrement KEYWORD2 diff --git a/library.json b/library.json index bf270c54..ce839e77 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "MIDI Library", - "version": "4.4.0", + "version": "5.0.0", "keywords": "midi", "description": "Enables MIDI I/O communications on the Arduino serial ports", "license": "MIT", diff --git a/library.properties b/library.properties index 2e67670f..c37afaba 100644 --- a/library.properties +++ b/library.properties @@ -1,6 +1,6 @@ name=MIDI Library -version=4.4.0 -author=Forty Seven Effects +version=5.0.0 +author=Forty Seven Effects, lathoub maintainer=Francois Best sentence=MIDI I/Os for Arduino paragraph=Read & send MIDI messages to interface with your controllers and synths diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 66f3355d..b9672fca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,12 +6,10 @@ add_library(midi STATIC midi_Namespace.h midi_Defs.h midi_Message.h + midi_Platform.h midi_Settings.h - midi_RingBuffer.h - midi_RingBuffer.hpp - midi_UsbTransport.h - midi_UsbTransport.hpp MIDI.cpp MIDI.hpp MIDI.h + serialMIDI.h ) diff --git a/src/MIDI.h b/src/MIDI.h index e00bf982..ac809d88 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -2,7 +2,7 @@ * @file MIDI.h * Project Arduino MIDI Library * @brief MIDI Library for the Arduino - * @author Francois Best + * @author Francois Best, lathoub * @date 24/02/11 * @license MIT - Copyright (c) 2015 Francois Best * @@ -28,31 +28,41 @@ #pragma once #include "midi_Defs.h" +#include "midi_Platform.h" #include "midi_Settings.h" #include "midi_Message.h" +#include "serialMIDI.h" + // ----------------------------------------------------------------------------- BEGIN_MIDI_NAMESPACE +#define MIDI_LIBRARY_VERSION 0x050000 +#define MIDI_LIBRARY_VERSION_MAJOR 5 +#define MIDI_LIBRARY_VERSION_MINOR 0 +#define MIDI_LIBRARY_VERSION_PATCH 0 + /*! \brief The main class for MIDI handling. It is templated over the type of serial port to provide abstraction from the hardware interface, meaning you can use HardwareSerial, SoftwareSerial or ak47's Uart classes. The only requirement is that the class implements the begin, read, write and available methods. */ -template +template class MidiInterface { public: typedef _Settings Settings; + typedef _Platform Platform; + typedef Message MidiMessage; public: - inline MidiInterface(SerialPort& inSerial); + inline MidiInterface(Transport&); inline ~MidiInterface(); public: - void begin(Channel inChannel = 1); + void begin(Channel inChannel = MIDI_CHANNEL_OMNI); // ------------------------------------------------------------------------- // MIDI Output @@ -97,8 +107,19 @@ class MidiInterface inline void sendSongPosition(unsigned inBeats); inline void sendSongSelect(DataByte inSongNumber); inline void sendTuneRequest(); - inline void sendRealTime(MidiType inType); + inline void 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 void sendRealTime(MidiType inType); + inline void beginRpn(unsigned inNumber, Channel inChannel); inline void sendRpnValue(unsigned inValue, @@ -125,6 +146,8 @@ class MidiInterface Channel inChannel); inline void endNrpn(Channel inChannel); + inline void send(const MidiMessage&); + public: void send(MidiType inType, DataByte inData1, @@ -160,48 +183,54 @@ class MidiInterface // Input Callbacks public: - inline void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); - inline void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); - inline void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); - inline void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); - inline void setHandleProgramChange(void (*fptr)(byte channel, byte number)); - inline void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); - inline void setHandlePitchBend(void (*fptr)(byte channel, int bend)); - inline void setHandleSystemExclusive(void (*fptr)(byte * array, unsigned size)); - inline void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); - inline void setHandleSongPosition(void (*fptr)(unsigned beats)); - inline void setHandleSongSelect(void (*fptr)(byte songnumber)); - inline void setHandleTuneRequest(void (*fptr)(void)); - inline void setHandleClock(void (*fptr)(void)); - inline void setHandleStart(void (*fptr)(void)); - inline void setHandleContinue(void (*fptr)(void)); - inline void setHandleStop(void (*fptr)(void)); - inline void setHandleActiveSensing(void (*fptr)(void)); - inline void setHandleSystemReset(void (*fptr)(void)); + 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); private: void launchCallback(); - void (*mNoteOffCallback)(byte channel, byte note, byte velocity); - void (*mNoteOnCallback)(byte channel, byte note, byte velocity); - void (*mAfterTouchPolyCallback)(byte channel, byte note, byte velocity); - void (*mControlChangeCallback)(byte channel, byte, byte); - void (*mProgramChangeCallback)(byte channel, byte); - void (*mAfterTouchChannelCallback)(byte channel, byte); - void (*mPitchBendCallback)(byte channel, int); - void (*mSystemExclusiveCallback)(byte * array, unsigned size); - void (*mTimeCodeQuarterFrameCallback)(byte data); - void (*mSongPositionCallback)(unsigned beats); - void (*mSongSelectCallback)(byte songnumber); - void (*mTuneRequestCallback)(void); - void (*mClockCallback)(void); - void (*mStartCallback)(void); - void (*mContinueCallback)(void); - void (*mStopCallback)(void); - void (*mActiveSensingCallback)(void); - void (*mSystemResetCallback)(void); + void (*mMessageCallback)(const MidiMessage& message) = nullptr; + ErrorCallback mErrorCallback = nullptr; + NoteOffCallback mNoteOffCallback = nullptr; + NoteOnCallback mNoteOnCallback = nullptr; + AfterTouchPolyCallback mAfterTouchPolyCallback = nullptr; + ControlChangeCallback mControlChangeCallback = nullptr; + ProgramChangeCallback mProgramChangeCallback = nullptr; + AfterTouchChannelCallback mAfterTouchChannelCallback = nullptr; + PitchBendCallback mPitchBendCallback = nullptr; + SystemExclusiveCallback mSystemExclusiveCallback = nullptr; + TimeCodeQuarterFrameCallback mTimeCodeQuarterFrameCallback = nullptr; + SongPositionCallback mSongPositionCallback = nullptr; + SongSelectCallback mSongSelectCallback = nullptr; + TuneRequestCallback mTuneRequestCallback = nullptr; + ClockCallback mClockCallback = nullptr; + StartCallback mStartCallback = nullptr; + TickCallback mTickCallback = nullptr; + ContinueCallback mContinueCallback = nullptr; + StopCallback mStopCallback = nullptr; + ActiveSensingCallback mActiveSensingCallback = nullptr; + SystemResetCallback mSystemResetCallback = nullptr; // ------------------------------------------------------------------------- // MIDI Soft Thru @@ -217,31 +246,45 @@ class MidiInterface private: void thruFilter(byte inChannel); + // ------------------------------------------------------------------------- + // MIDI Parsing + private: bool parse(); inline void handleNullVelocityNoteOnAsNoteOff(); inline bool inputFilter(Channel inChannel); inline void resetInput(); + inline void UpdateLastSentTime(); -private: - typedef Message MidiMessage; + // ------------------------------------------------------------------------- + // Transport + +public: + Transport* getTransport() { return &mTransport; }; private: - SerialPort& mSerial; + Transport& mTransport; + + // ------------------------------------------------------------------------- + // Internal variables private: Channel mInputChannel; StatusByte mRunningStatus_RX; StatusByte mRunningStatus_TX; byte mPendingMessage[3]; - unsigned mPendingMessageExpectedLenght; + unsigned mPendingMessageExpectedLength; unsigned mPendingMessageIndex; unsigned mCurrentRpnNumber; unsigned mCurrentNrpnNumber; bool mThruActivated : 1; Thru::Mode mThruFilterMode : 7; MidiMessage mMessage; - + unsigned long mLastMessageSentTime; + unsigned long mLastMessageReceivedTime; + unsigned long mSenderActiveSensingPeriodicity; + bool mReceiverActiveSensingActivated; + int8_t mLastError; private: inline StatusByte getStatus(MidiType inType, diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 7df1afb1..2e64aa3a 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -2,7 +2,7 @@ * @file MIDI.hpp * Project Arduino MIDI Library * @brief MIDI Library for the Arduino - Inline implementations - * @author Francois Best + * @author Francois Best, lathoub * @date 24/02/11 * @license MIT - Copyright (c) 2015 Francois Best * @@ -30,45 +30,33 @@ BEGIN_MIDI_NAMESPACE /// \brief Constructor for MidiInterface. -template -inline MidiInterface::MidiInterface(SerialPort& inSerial) - : mSerial(inSerial) +template +inline MidiInterface::MidiInterface(Transport& inTransport) + : mTransport(inTransport) , mInputChannel(0) , mRunningStatus_RX(InvalidType) , mRunningStatus_TX(InvalidType) - , mPendingMessageExpectedLenght(0) + , mPendingMessageExpectedLength(0) , mPendingMessageIndex(0) , mCurrentRpnNumber(0xffff) , mCurrentNrpnNumber(0xffff) , mThruActivated(true) , mThruFilterMode(Thru::Full) + , mLastMessageSentTime(0) + , mLastMessageReceivedTime(0) + , mSenderActiveSensingPeriodicity(0) + , mReceiverActiveSensingActivated(false) + , mLastError(0) { - mNoteOffCallback = 0; - mNoteOnCallback = 0; - mAfterTouchPolyCallback = 0; - mControlChangeCallback = 0; - mProgramChangeCallback = 0; - mAfterTouchChannelCallback = 0; - mPitchBendCallback = 0; - mSystemExclusiveCallback = 0; - mTimeCodeQuarterFrameCallback = 0; - mSongPositionCallback = 0; - mSongSelectCallback = 0; - mTuneRequestCallback = 0; - mClockCallback = 0; - mStartCallback = 0; - mContinueCallback = 0; - mStopCallback = 0; - mActiveSensingCallback = 0; - mSystemResetCallback = 0; + mSenderActiveSensingPeriodicity = Settings::SenderActiveSensingPeriodicity; } /*! \brief Destructor for MidiInterface. This is not really useful for the Arduino, as it is never called... */ -template -inline MidiInterface::~MidiInterface() +template +inline MidiInterface::~MidiInterface() { } @@ -80,31 +68,30 @@ inline MidiInterface::~MidiInterface() - Input channel set to 1 if no value is specified - Full thru mirroring */ -template -void MidiInterface::begin(Channel inChannel) +template +void MidiInterface::begin(Channel inChannel) { - // Initialise the Serial port -#if defined(AVR_CAKE) - mSerial. template open(); -#else - mSerial.begin(Settings::BaudRate); -#endif + // Initialise the Transport layer + mTransport.begin(); mInputChannel = inChannel; mRunningStatus_TX = InvalidType; mRunningStatus_RX = InvalidType; mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; + mPendingMessageExpectedLength = 0; mCurrentRpnNumber = 0xffff; mCurrentNrpnNumber = 0xffff; + mLastMessageSentTime = Platform::now(); + mMessage.valid = false; mMessage.type = InvalidType; mMessage.channel = 0; mMessage.data1 = 0; mMessage.data2 = 0; + mMessage.length = 0; mThruFilterMode = Thru::Full; mThruActivated = true; @@ -118,6 +105,46 @@ void MidiInterface::begin(Channel inChannel) @{ */ +/*! \brief Send a MIDI message. +\param inMessage The message + + This method is used when you want to send a Message that has not been constructed + by the library, but by an external source. + This method does *not* check against any of the constraints. + Typically this function is use by MIDI Bridges taking MIDI messages and passing + them thru. + */ +template +void MidiInterface::send(const MidiMessage& inMessage) +{ + if (!inMessage.valid) + return; + + if (mTransport.beginTransmission(inMessage.type)) + { + const StatusByte status = getStatus(inMessage.type, inMessage.channel); + mTransport.write(status); + + if (inMessage.type != MidiType::SystemExclusive) + { + if (inMessage.length > 1) mTransport.write(inMessage.data1); + if (inMessage.length > 2) mTransport.write(inMessage.data2); + } else + { + // sysexArray does not contain the start and end tags + mTransport.write(MidiType::SystemExclusiveStart); + + for (size_t i = 0; i < inMessage.getSysExSize(); i++) + mTransport.write(inMessage.sysexArray[i]); + + mTransport.write(MidiType::SystemExclusiveEnd); + } + } + mTransport.endTransmission(); + UpdateLastSentTime(); +} + + /*! \brief Generate and send a MIDI message from the values given. \param inType The message type (see type defines for reference) \param inData1 The first data byte. @@ -129,8 +156,8 @@ void MidiInterface::begin(Channel inChannel) This is an internal method, use it only if you need to send raw data from your code, at your own risks. */ -template -void MidiInterface::send(MidiType inType, +template +void MidiInterface::send(MidiType inType, DataByte inData1, DataByte inData2, Channel inChannel) @@ -151,26 +178,32 @@ void MidiInterface::send(MidiType inType, const StatusByte status = getStatus(inType, inChannel); - if (Settings::UseRunningStatus) + if (mTransport.beginTransmission(inType)) { - if (mRunningStatus_TX != status) + if (Settings::UseRunningStatus) { - // New message, memorise and send header - mRunningStatus_TX = status; - mSerial.write(mRunningStatus_TX); + if (mRunningStatus_TX != status) + { + // New message, memorise and send header + mRunningStatus_TX = status; + mTransport.write(mRunningStatus_TX); + } + } + else + { + // Don't care about running status, send the status byte. + mTransport.write(status); } - } - else - { - // Don't care about running status, send the status byte. - mSerial.write(status); - } - // Then send data - mSerial.write(inData1); - if (inType != ProgramChange && inType != AfterTouchChannel) - { - mSerial.write(inData2); + // Then send data + mTransport.write(inData1); + if (inType != ProgramChange && inType != AfterTouchChannel) + { + mTransport.write(inData2); + } + + mTransport.endTransmission(); + UpdateLastSentTime(); } } else if (inType >= Clock && inType <= SystemReset) @@ -190,8 +223,8 @@ void MidiInterface::send(MidiType inType, Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html */ -template -void MidiInterface::sendNoteOn(DataByte inNoteNumber, +template +void MidiInterface::sendNoteOn(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel) { @@ -209,8 +242,8 @@ void MidiInterface::sendNoteOn(DataByte inNoteNumber, Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html */ -template -void MidiInterface::sendNoteOff(DataByte inNoteNumber, +template +void MidiInterface::sendNoteOff(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel) { @@ -221,8 +254,8 @@ void MidiInterface::sendNoteOff(DataByte inNoteNumber, \param inProgramNumber The Program to select (0 to 127). \param inChannel The channel on which the message will be sent (1 to 16). */ -template -void MidiInterface::sendProgramChange(DataByte inProgramNumber, +template +void MidiInterface::sendProgramChange(DataByte inProgramNumber, Channel inChannel) { send(ProgramChange, inProgramNumber, 0, inChannel); @@ -234,8 +267,8 @@ void MidiInterface::sendProgramChange(DataByte inProgramNu \param inChannel The channel on which the message will be sent (1 to 16). @see MidiControlChangeNumber */ -template -void MidiInterface::sendControlChange(DataByte inControlNumber, +template +void MidiInterface::sendControlChange(DataByte inControlNumber, DataByte inControlValue, Channel inChannel) { @@ -249,8 +282,8 @@ void MidiInterface::sendControlChange(DataByte inControlNu Note: this method is deprecated and will be removed in a future revision of the library, @see sendAfterTouch to send polyphonic and monophonic AfterTouch messages. */ -template -void MidiInterface::sendPolyPressure(DataByte inNoteNumber, +template +void MidiInterface::sendPolyPressure(DataByte inNoteNumber, DataByte inPressure, Channel inChannel) { @@ -261,8 +294,8 @@ void MidiInterface::sendPolyPressure(DataByte inNoteNumber \param inPressure The amount of AfterTouch to apply to all notes. \param inChannel The channel on which the message will be sent (1 to 16). */ -template -void MidiInterface::sendAfterTouch(DataByte inPressure, +template +void MidiInterface::sendAfterTouch(DataByte inPressure, Channel inChannel) { send(AfterTouchChannel, inPressure, 0, inChannel); @@ -274,8 +307,8 @@ void MidiInterface::sendAfterTouch(DataByte inPressure, \param inChannel The channel on which the message will be sent (1 to 16). @see Replaces sendPolyPressure (which is now deprecated). */ -template -void MidiInterface::sendAfterTouch(DataByte inNoteNumber, +template +void MidiInterface::sendAfterTouch(DataByte inNoteNumber, DataByte inPressure, Channel inChannel) { @@ -288,8 +321,8 @@ void MidiInterface::sendAfterTouch(DataByte inNoteNumber, center value is 0. \param inChannel The channel on which the message will be sent (1 to 16). */ -template -void MidiInterface::sendPitchBend(int inPitchValue, +template +void MidiInterface::sendPitchBend(int inPitchValue, Channel inChannel) { const unsigned bend = unsigned(inPitchValue - int(MIDI_PITCHBEND_MIN)); @@ -303,8 +336,8 @@ void MidiInterface::sendPitchBend(int inPitchValue, and +1.0f (max upwards bend), center value is 0.0f. \param inChannel The channel on which the message will be sent (1 to 16). */ -template -void MidiInterface::sendPitchBend(double inPitchValue, +template +void MidiInterface::sendPitchBend(double inPitchValue, Channel inChannel) { const int scale = inPitchValue > 0.0 ? MIDI_PITCHBEND_MAX : MIDI_PITCHBEND_MIN; @@ -321,32 +354,30 @@ void MidiInterface::sendPitchBend(double inPitchValue, default value for ArrayContainsBoundaries is set to 'false' for compatibility with previous versions of the library. */ -template -void MidiInterface::sendSysEx(unsigned inLength, +template +void MidiInterface::sendSysEx(unsigned inLength, const byte* inArray, bool inArrayContainsBoundaries) { const bool writeBeginEndBytes = !inArrayContainsBoundaries; - if (writeBeginEndBytes) + if (mTransport.beginTransmission(MidiType::SystemExclusiveStart)) { - mSerial.write(0xf0); - } + if (writeBeginEndBytes) + mTransport.write(MidiType::SystemExclusiveStart); - for (unsigned i = 0; i < inLength; ++i) - { - mSerial.write(inArray[i]); - } + for (unsigned i = 0; i < inLength; ++i) + mTransport.write(inArray[i]); - if (writeBeginEndBytes) - { - mSerial.write(0xf7); - } + if (writeBeginEndBytes) + mTransport.write(MidiType::SystemExclusiveEnd); + + mTransport.endTransmission(); + UpdateLastSentTime(); + } if (Settings::UseRunningStatus) - { mRunningStatus_TX = InvalidType; - } } /*! \brief Send a Tune Request message. @@ -354,15 +385,10 @@ void MidiInterface::sendSysEx(unsigned inLength, When a MIDI unit receives this message, it should tune its oscillators (if equipped with any). */ -template -void MidiInterface::sendTuneRequest() +template +void MidiInterface::sendTuneRequest() { - mSerial.write(TuneRequest); - - if (Settings::UseRunningStatus) - { - mRunningStatus_TX = InvalidType; - } + sendCommon(TuneRequest); } /*! \brief Send a MIDI Time Code Quarter Frame. @@ -371,9 +397,9 @@ void MidiInterface::sendTuneRequest() \param inValuesNibble MTC data See MIDI Specification for more information. */ -template -void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, - DataByte inValuesNibble) +template +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, + DataByte inValuesNibble) { const byte data = byte((((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0f))); sendTimeCodeQuarterFrame(data); @@ -385,45 +411,76 @@ void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTy \param inData if you want to encode directly the nibbles in your program, you can send the byte here. */ -template -void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) +template +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) { - mSerial.write((byte)TimeCodeQuarterFrame); - mSerial.write(inData); - - if (Settings::UseRunningStatus) - { - mRunningStatus_TX = InvalidType; - } + 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) +template +void MidiInterface::sendSongPosition(unsigned inBeats) { - mSerial.write((byte)SongPosition); - mSerial.write(inBeats & 0x7f); - mSerial.write((inBeats >> 7) & 0x7f); - - if (Settings::UseRunningStatus) - { - mRunningStatus_TX = InvalidType; - } + sendCommon(SongPosition, inBeats); } /*! \brief Send a Song Select message */ -template -void MidiInterface::sendSongSelect(DataByte inSongNumber) +template +void MidiInterface::sendSongSelect(DataByte inSongNumber) { - mSerial.write((byte)SongSelect); - mSerial.write(inSongNumber & 0x7f); + sendCommon(SongSelect, inSongNumber); +} - if (Settings::UseRunningStatus) +/*! \brief Send a Common message. Common messages reset the running status. + + \param inType The available Common types are: + TimeCodeQuarterFrame, SongPosition, SongSelect and TuneRequest. + @see MidiType + \param inData1 The byte that goes with the common message. + */ +template +void MidiInterface::sendCommon(MidiType inType, unsigned inData1) +{ + switch (inType) { - mRunningStatus_TX = InvalidType; + case TimeCodeQuarterFrame: + case SongPosition: + case SongSelect: + case TuneRequest: + break; + default: + // Invalid Common marker + return; + } + + if (mTransport.beginTransmission(inType)) + { + mTransport.write((byte)inType); + switch (inType) + { + case TimeCodeQuarterFrame: + mTransport.write(inData1); + break; + case SongPosition: + mTransport.write(inData1 & 0x7f); + mTransport.write((inData1 >> 7) & 0x7f); + break; + case SongSelect: + mTransport.write(inData1 & 0x7f); + break; + case TuneRequest: + break; + default: + break; // LCOV_EXCL_LINE - Coverage blind spot + } + mTransport.endTransmission(); + UpdateLastSentTime(); } + + if (Settings::UseRunningStatus) + mRunningStatus_TX = InvalidType; } /*! \brief Send a Real Time (one byte) message. @@ -432,8 +489,8 @@ void MidiInterface::sendSongSelect(DataByte inSongNumber) Start, Stop, Continue, Clock, ActiveSensing and SystemReset. @see MidiType */ -template -void MidiInterface::sendRealTime(MidiType inType) +template +void MidiInterface::sendRealTime(MidiType inType) { // Do not invalidate Running Status for real-time messages // as they can be interleaved within any message. @@ -446,7 +503,12 @@ void MidiInterface::sendRealTime(MidiType inType) case Continue: case ActiveSensing: case SystemReset: - mSerial.write((byte)inType); + if (mTransport.beginTransmission(inType)) + { + mTransport.write((byte)inType); + mTransport.endTransmission(); + UpdateLastSentTime(); + } break; default: // Invalid Real Time marker @@ -458,8 +520,8 @@ void MidiInterface::sendRealTime(MidiType inType) \param inNumber The 14-bit number of the RPN you want to select. \param inChannel The channel on which the message will be sent (1 to 16). */ -template -inline void MidiInterface::beginRpn(unsigned inNumber, +template +inline void MidiInterface::beginRpn(unsigned inNumber, Channel inChannel) { if (mCurrentRpnNumber != inNumber) @@ -476,8 +538,8 @@ inline void MidiInterface::beginRpn(unsigned inNumber, \param inValue The 14-bit value of the selected RPN. \param inChannel The channel on which the message will be sent (1 to 16). */ -template -inline void MidiInterface::sendRpnValue(unsigned inValue, +template +inline void MidiInterface::sendRpnValue(unsigned inValue, Channel inChannel) {; const byte valMsb = 0x7f & (inValue >> 7); @@ -491,8 +553,8 @@ inline void MidiInterface::sendRpnValue(unsigned inValue, \param inLsb The LSB part of the value to send. Meaning depends on RPN number. \param inChannel The channel on which the message will be sent (1 to 16). */ -template -inline void MidiInterface::sendRpnValue(byte inMsb, +template +inline void MidiInterface::sendRpnValue(byte inMsb, byte inLsb, Channel inChannel) { @@ -503,8 +565,8 @@ inline void MidiInterface::sendRpnValue(byte inMsb, /* \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, +template +inline void MidiInterface::sendRpnIncrement(byte inAmount, Channel inChannel) { sendControlChange(DataIncrement, inAmount, inChannel); @@ -513,8 +575,8 @@ inline void MidiInterface::sendRpnIncrement(byte inAmount, /* \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, +template +inline void MidiInterface::sendRpnDecrement(byte inAmount, Channel inChannel) { sendControlChange(DataDecrement, inAmount, inChannel); @@ -524,8 +586,8 @@ inline void MidiInterface::sendRpnDecrement(byte inAmount, 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) +template +inline void MidiInterface::endRpn(Channel inChannel) { sendControlChange(RPNLSB, 0x7f, inChannel); sendControlChange(RPNMSB, 0x7f, inChannel); @@ -538,8 +600,8 @@ inline void MidiInterface::endRpn(Channel inChannel) \param inNumber The 14-bit number of the NRPN you want to select. \param inChannel The channel on which the message will be sent (1 to 16). */ -template -inline void MidiInterface::beginNrpn(unsigned inNumber, +template +inline void MidiInterface::beginNrpn(unsigned inNumber, Channel inChannel) { if (mCurrentNrpnNumber != inNumber) @@ -556,8 +618,8 @@ inline void MidiInterface::beginNrpn(unsigned inNumber, \param inValue The 14-bit value of the selected NRPN. \param inChannel The channel on which the message will be sent (1 to 16). */ -template -inline void MidiInterface::sendNrpnValue(unsigned inValue, +template +inline void MidiInterface::sendNrpnValue(unsigned inValue, Channel inChannel) {; const byte valMsb = 0x7f & (inValue >> 7); @@ -571,8 +633,8 @@ inline void MidiInterface::sendNrpnValue(unsigned inValue, \param inLsb The LSB part of the value to send. Meaning depends on NRPN number. \param inChannel The channel on which the message will be sent (1 to 16). */ -template -inline void MidiInterface::sendNrpnValue(byte inMsb, +template +inline void MidiInterface::sendNrpnValue(byte inMsb, byte inLsb, Channel inChannel) { @@ -583,8 +645,8 @@ inline void MidiInterface::sendNrpnValue(byte inMsb, /* \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, +template +inline void MidiInterface::sendNrpnIncrement(byte inAmount, Channel inChannel) { sendControlChange(DataIncrement, inAmount, inChannel); @@ -593,8 +655,8 @@ inline void MidiInterface::sendNrpnIncrement(byte inAmount /* \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, +template +inline void MidiInterface::sendNrpnDecrement(byte inAmount, Channel inChannel) { sendControlChange(DataDecrement, inAmount, inChannel); @@ -604,8 +666,8 @@ inline void MidiInterface::sendNrpnDecrement(byte inAmount 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) +template +inline void MidiInterface::endNrpn(Channel inChannel) { sendControlChange(NRPNLSB, 0x7f, inChannel); sendControlChange(NRPNMSB, 0x7f, inChannel); @@ -616,8 +678,8 @@ inline void MidiInterface::endNrpn(Channel inChannel) // ----------------------------------------------------------------------------- -template -StatusByte MidiInterface::getStatus(MidiType inType, +template +StatusByte MidiInterface::getStatus(MidiType inType, Channel inChannel) const { return StatusByte(((byte)inType | ((inChannel - 1) & 0x0f))); @@ -639,30 +701,76 @@ StatusByte MidiInterface::getStatus(MidiType inType, it is sent back on the MIDI output. @see see setInputChannel() */ -template -inline bool MidiInterface::read() +template +inline bool MidiInterface::read() { return read(mInputChannel); } /*! \brief Read messages on a specified channel. */ -template -inline bool MidiInterface::read(Channel inChannel) +template +inline bool MidiInterface::read(Channel inChannel) { + #ifndef RegionActiveSending + // Active Sensing. This message is intended to be sent + // repeatedly to tell the receiver that a connection is alive. Use + // of this message is optional. When initially received, the + // receiver will expect to receive another Active Sensing + // message each 300ms (max), and if it does not then it will + // assume that the connection has been terminated. At + // termination, the receiver will turn off all voices and return to + // normal (non- active sensing) operation. + if (Settings::UseSenderActiveSensing && (mSenderActiveSensingPeriodicity > 0) && (Platform::now() - mLastMessageSentTime) > mSenderActiveSensingPeriodicity) + { + sendActiveSensing(); + mLastMessageSentTime = Platform::now(); + } + + if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated && (mLastMessageReceivedTime + ActiveSensingTimeout < Platform::now())) + { + mReceiverActiveSensingActivated = false; + + mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit + if (mErrorCallback) + mErrorCallback(mLastError); + } + #endif + if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. if (!parse()) return false; + + #ifndef RegionActiveSending + + if (Settings::UseReceiverActiveSensing && mMessage.type == ActiveSensing) + { + // When an ActiveSensing message is received, the time keeping is activated. + // When a timeout occurs, an error message is send and time keeping ends. + mReceiverActiveSensingActivated = true; + + // is ErrorActiveSensingTimeout bit in mLastError on + if (mLastError & (1 << (ErrorActiveSensingTimeout - 1))) + { + mLastError &= ~(1UL << ErrorActiveSensingTimeout); // clear the ErrorActiveSensingTimeout bit + if (mErrorCallback) + mErrorCallback(mLastError); + } + } + + // Keep the time of the last received message, so we can check for the timeout + if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated) + mLastMessageReceivedTime = Platform::now(); + + #endif handleNullVelocityNoteOnAsNoteOff(); - const bool channelMatch = inputFilter(inChannel); + const bool channelMatch = inputFilter(inChannel); if (channelMatch) - { launchCallback(); - } thruFilter(inChannel); @@ -672,14 +780,14 @@ inline bool MidiInterface::read(Channel inChannel) // ----------------------------------------------------------------------------- // Private method: MIDI parser -template -bool MidiInterface::parse() +template +bool MidiInterface::parse() { - if (mSerial.available() == 0) - { - // No data available. - return false; - } + if (mTransport.available() == 0) + return false; // No data available. + + // clear the ErrorParse bit + mLastError &= ~(1UL << ErrorParse); // Parsing algorithm: // Get a byte from the serial buffer. @@ -690,20 +798,11 @@ bool MidiInterface::parse() // Else, add the extracted byte to the pending message, and check validity. // When the message is done, store it. - const byte extracted = mSerial.read(); + const byte extracted = mTransport.read(); // Ignore Undefined - if (extracted == 0xf9 || extracted == 0xfd) - { - if (Settings::Use1ByteParsing) - { - return false; - } - else - { - return parse(); - } - } + if (extracted == Undefined_FD) + return (Settings::Use1ByteParsing) ? false : parse(); if (mPendingMessageIndex == 0) { @@ -737,6 +836,7 @@ bool MidiInterface::parse() case Continue: case Stop: case Clock: + case Tick: case ActiveSensing: case SystemReset: case TuneRequest: @@ -750,56 +850,63 @@ 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; - // 2 bytes messages + // 2 bytes messages case ProgramChange: case AfterTouchChannel: case TimeCodeQuarterFrame: case SongSelect: - mPendingMessageExpectedLenght = 2; + mPendingMessageExpectedLength = 2; break; - // 3 bytes messages + // 3 bytes messages case NoteOn: case NoteOff: case ControlChange: case PitchBend: case AfterTouchPoly: case SongPosition: - mPendingMessageExpectedLenght = 3; + mPendingMessageExpectedLength = 3; break; - case SystemExclusive: + case SystemExclusiveStart: + case SystemExclusiveEnd: // 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; + mMessage.sysexArray[0] = pendingType; break; - + case InvalidType: default: // This is obviously wrong. Let's get the hell out'a here. + mLastError |= 1UL << ErrorParse; // set the ErrorParse bit + if (mErrorCallback) + mErrorCallback(mLastError); + resetInput(); return false; break; } - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) + if (mPendingMessageIndex >= (mPendingMessageExpectedLength - 1)) { // Reception complete mMessage.type = pendingType; mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); mMessage.data1 = mPendingMessage[1]; mMessage.data2 = 0; // Completed new message has 1 data byte - + mMessage.length = 1; + mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; + mPendingMessageExpectedLength = 0; mMessage.valid = true; + return true; } else @@ -808,17 +915,7 @@ bool MidiInterface::parse() mPendingMessageIndex++; } - if (Settings::Use1ByteParsing) - { - // Message is not complete. - return false; - } - else - { - // Call the parser recursively - // to parse the rest of the message. - return parse(); - } + return (Settings::Use1ByteParsing) ? false : parse(); } else { @@ -831,6 +928,7 @@ bool MidiInterface::parse() { case Clock: case Start: + case Tick: case Continue: case Stop: case ActiveSensing: @@ -847,29 +945,39 @@ bool MidiInterface::parse() mMessage.data1 = 0; mMessage.data2 = 0; mMessage.channel = 0; + mMessage.length = 1; mMessage.valid = true; + return true; - // End of Exclusive - case 0xf7: - if (mMessage.sysexArray[0] == SystemExclusive) + // Exclusive + case SystemExclusiveStart: + case SystemExclusiveEnd: + if ((mMessage.sysexArray[0] == SystemExclusiveStart) + || (mMessage.sysexArray[0] == SystemExclusiveEnd)) { // Store the last byte (EOX) - mMessage.sysexArray[mPendingMessageIndex++] = 0xf7; + mMessage.sysexArray[mPendingMessageIndex++] = extracted; mMessage.type = SystemExclusive; // Get length mMessage.data1 = mPendingMessageIndex & 0xff; // LSB mMessage.data2 = byte(mPendingMessageIndex >> 8); // MSB mMessage.channel = 0; + mMessage.length = mPendingMessageIndex; mMessage.valid = true; resetInput(); + return true; } else { // Well well well.. error. + mLastError |= 1UL << ErrorParse; // set the error bits + if (mErrorCallback) + mErrorCallback(mLastError); + resetInput(); return false; } @@ -880,20 +988,43 @@ bool MidiInterface::parse() } // Add extracted data byte to pending message - if (mPendingMessage[0] == SystemExclusive) + if ((mPendingMessage[0] == SystemExclusiveStart) + || (mPendingMessage[0] == SystemExclusiveEnd)) mMessage.sysexArray[mPendingMessageIndex] = extracted; else 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 - // the buffer. If this happens, try increasing MidiMessage::sSysExMaxSize. - if (mPendingMessage[0] == SystemExclusive) + // SysEx larger than the allocated buffer size, + // Split SysEx like so: + // first: 0xF0 .... 0xF0 + // midlle: 0xF7 .... 0xF0 + // last: 0xF7 .... 0xF7 + if ((mPendingMessage[0] == SystemExclusiveStart) + || (mPendingMessage[0] == SystemExclusiveEnd)) { - resetInput(); + auto lastByte = mMessage.sysexArray[DefaultSettings::SysExMaxSize - 1]; + mMessage.sysexArray[DefaultSettings::SysExMaxSize - 1] = SystemExclusiveStart; + mMessage.type = SystemExclusive; + + // Get length + mMessage.data1 = DefaultSettings::SysExMaxSize & 0xff; // LSB + mMessage.data2 = byte(DefaultSettings::SysExMaxSize >> 8); // MSB + mMessage.channel = 0; + mMessage.length = DefaultSettings::SysExMaxSize; + mMessage.valid = true; + + // No need to check against the inputChannel, + // SysEx ignores input channel + launchCallback(); + + mMessage.sysexArray[0] = SystemExclusiveEnd; + mMessage.sysexArray[1] = lastByte; + + mPendingMessageIndex = 2; + return false; } @@ -905,13 +1036,13 @@ bool MidiInterface::parse() mMessage.channel = 0; mMessage.data1 = mPendingMessage[1]; - // Save data2 only if applicable - mMessage.data2 = mPendingMessageExpectedLenght == 3 ? mPendingMessage[2] : 0; - + mMessage.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0; + mMessage.length = mPendingMessageExpectedLength; + // Reset local variables mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; + mPendingMessageExpectedLength = 0; mMessage.valid = true; @@ -941,23 +1072,14 @@ bool MidiInterface::parse() // Then update the index of the pending message. mPendingMessageIndex++; - if (Settings::Use1ByteParsing) - { - // Message is not complete. - return false; - } - else - { - // Call the parser recursively to parse the rest of the message. - return parse(); - } + return (Settings::Use1ByteParsing) ? false : parse(); } } } // Private method, see midi_Settings.h for documentation -template -inline void MidiInterface::handleNullVelocityNoteOnAsNoteOff() +template +inline void MidiInterface::handleNullVelocityNoteOnAsNoteOff() { if (Settings::HandleNullVelocityNoteOnAsNoteOff && getType() == NoteOn && getData2() == 0) @@ -967,8 +1089,8 @@ inline void MidiInterface::handleNullVelocityNoteOnAsNoteO } // Private method: check if the received message is on the listened channel -template -inline bool MidiInterface::inputFilter(Channel inChannel) +template +inline bool MidiInterface::inputFilter(Channel inChannel) { // This method handles recognition of channel // (to know if the message is destinated to the Arduino) @@ -996,11 +1118,11 @@ inline bool MidiInterface::inputFilter(Channel inChannel) } // Private method: reset input attributes -template -inline void MidiInterface::resetInput() +template +inline void MidiInterface::resetInput() { mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; + mPendingMessageExpectedLength = 0; mRunningStatus_RX = InvalidType; } @@ -1010,8 +1132,8 @@ inline void MidiInterface::resetInput() Returns an enumerated type. @see MidiType */ -template -inline MidiType MidiInterface::getType() const +template +inline MidiType MidiInterface::getType() const { return mMessage.type; } @@ -1021,22 +1143,22 @@ inline MidiType MidiInterface::getType() const \return Channel range is 1 to 16. For non-channel messages, this will return 0. */ -template -inline Channel MidiInterface::getChannel() const +template +inline Channel MidiInterface::getChannel() const { return mMessage.channel; } /*! \brief Get the first data byte of the last received message. */ -template -inline DataByte MidiInterface::getData1() const +template +inline DataByte MidiInterface::getData1() const { return mMessage.data1; } /*! \brief Get the second data byte of the last received message. */ -template -inline DataByte MidiInterface::getData2() const +template +inline DataByte MidiInterface::getData2() const { return mMessage.data2; } @@ -1045,8 +1167,8 @@ inline DataByte MidiInterface::getData2() const @see getSysExArrayLength to get the array's length in bytes. */ -template -inline const byte* MidiInterface::getSysExArray() const +template +inline const byte* MidiInterface::getSysExArray() const { return mMessage.sysexArray; } @@ -1056,23 +1178,23 @@ inline const byte* MidiInterface::getSysExArray() const It is coded using data1 as LSB and data2 as MSB. \return The array's length, in bytes. */ -template -inline unsigned MidiInterface::getSysExArrayLength() const +template +inline unsigned MidiInterface::getSysExArrayLength() const { return mMessage.getSysExSize(); } /*! \brief Check if a valid message is stored in the structure. */ -template -inline bool MidiInterface::check() const +template +inline bool MidiInterface::check() const { return mMessage.valid; } // ----------------------------------------------------------------------------- -template -inline Channel MidiInterface::getInputChannel() const +template +inline Channel MidiInterface::getInputChannel() const { return mInputChannel; } @@ -1081,8 +1203,8 @@ inline Channel MidiInterface::getInputChannel() const \param inChannel the channel value. Valid values are 1 to 16, MIDI_CHANNEL_OMNI if you want to listen to all channels, and MIDI_CHANNEL_OFF to disable input. */ -template -inline void MidiInterface::setInputChannel(Channel inChannel) +template +inline void MidiInterface::setInputChannel(Channel inChannel) { mInputChannel = inChannel; } @@ -1094,37 +1216,32 @@ inline void MidiInterface::setInputChannel(Channel inChann This is a utility static method, used internally, made public so you can handle MidiTypes more easily. */ -template -MidiType MidiInterface::getTypeFromStatusByte(byte inStatus) +template +MidiType MidiInterface::getTypeFromStatusByte(byte inStatus) { if ((inStatus < 0x80) || - (inStatus == 0xf4) || - (inStatus == 0xf5) || - (inStatus == 0xf9) || - (inStatus == 0xfD)) - { - // Data bytes and undefined. - return InvalidType; - } + (inStatus == Undefined_F4) || + (inStatus == Undefined_F5) || + (inStatus == Undefined_FD)) + return InvalidType; // Data bytes and undefined. + if (inStatus < 0xf0) - { // Channel message, remove channel nibble. return MidiType(inStatus & 0xf0); - } return MidiType(inStatus); } /*! \brief Returns channel in the range 1-16 */ -template -inline Channel MidiInterface::getChannelFromStatusByte(byte inStatus) +template +inline Channel MidiInterface::getChannelFromStatusByte(byte inStatus) { return Channel((inStatus & 0x0f) + 1); } -template -bool MidiInterface::isChannelMessage(MidiType inType) +template +bool MidiInterface::isChannelMessage(MidiType inType) { return (inType == NoteOff || inType == NoteOn || @@ -1137,58 +1254,36 @@ bool MidiInterface::isChannelMessage(MidiType inType) // ----------------------------------------------------------------------------- -/*! \addtogroup callbacks - @{ - */ - -template void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } -template void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } -template void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } -template void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } -template void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } -template void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } -template void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } -template void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte* array, unsigned size)) { mSystemExclusiveCallback = fptr; } -template void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } -template void MidiInterface::setHandleSongPosition(void (*fptr)(unsigned beats)) { mSongPositionCallback = fptr; } -template void MidiInterface::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } -template void MidiInterface::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } -template void MidiInterface::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } -template void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } -template void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } -template void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } -template void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } -template void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } - /*! \brief Detach an external function from the given type. Use this method to cancel the effects of setHandle********. \param inType The type of message to unbind. When a message of this type is received, no function will be called. */ -template -void MidiInterface::disconnectCallbackFromType(MidiType inType) +template +void MidiInterface::disconnectCallbackFromType(MidiType inType) { switch (inType) { - case NoteOff: mNoteOffCallback = 0; break; - case NoteOn: mNoteOnCallback = 0; break; - case AfterTouchPoly: mAfterTouchPolyCallback = 0; break; - case ControlChange: mControlChangeCallback = 0; break; - case ProgramChange: mProgramChangeCallback = 0; break; - case AfterTouchChannel: mAfterTouchChannelCallback = 0; break; - case PitchBend: mPitchBendCallback = 0; break; - case SystemExclusive: mSystemExclusiveCallback = 0; break; - case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = 0; break; - case SongPosition: mSongPositionCallback = 0; break; - case SongSelect: mSongSelectCallback = 0; break; - case TuneRequest: mTuneRequestCallback = 0; break; - case Clock: mClockCallback = 0; break; - case Start: mStartCallback = 0; break; - case Continue: mContinueCallback = 0; break; - case Stop: mStopCallback = 0; break; - case ActiveSensing: mActiveSensingCallback = 0; break; - case SystemReset: mSystemResetCallback = 0; break; + case NoteOff: mNoteOffCallback = nullptr; break; + case NoteOn: mNoteOnCallback = nullptr; break; + case AfterTouchPoly: mAfterTouchPolyCallback = nullptr; break; + case ControlChange: mControlChangeCallback = nullptr; break; + case ProgramChange: mProgramChangeCallback = nullptr; break; + case AfterTouchChannel: mAfterTouchChannelCallback = nullptr; break; + case PitchBend: mPitchBendCallback = nullptr; break; + case SystemExclusive: mSystemExclusiveCallback = nullptr; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = nullptr; break; + case SongPosition: mSongPositionCallback = nullptr; break; + case SongSelect: mSongSelectCallback = nullptr; break; + case TuneRequest: mTuneRequestCallback = nullptr; break; + case Clock: mClockCallback = nullptr; break; + case Start: mStartCallback = nullptr; break; + case Tick: mTickCallback = nullptr; break; + case Continue: mContinueCallback = nullptr; break; + case Stop: mStopCallback = nullptr; break; + case ActiveSensing: mActiveSensingCallback = nullptr; break; + case SystemReset: mSystemResetCallback = nullptr; break; default: break; } @@ -1197,39 +1292,42 @@ void MidiInterface::disconnectCallbackFromType(MidiType in /*! @} */ // End of doc group MIDI Callbacks // Private - launch callback function based on received type. -template -void MidiInterface::launchCallback() +template +void MidiInterface::launchCallback() { + if (mMessageCallback != 0) mMessageCallback(mMessage); + // The order is mixed to allow frequent messages to trigger their callback faster. switch (mMessage.type) { // Notes - case NoteOff: if (mNoteOffCallback != 0) mNoteOffCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; - case NoteOn: if (mNoteOnCallback != 0) mNoteOnCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case NoteOff: if (mNoteOffCallback != nullptr) mNoteOffCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != nullptr) mNoteOnCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; // Real-time messages - case Clock: if (mClockCallback != 0) mClockCallback(); break; - case Start: if (mStartCallback != 0) mStartCallback(); break; - case Continue: if (mContinueCallback != 0) mContinueCallback(); break; - case Stop: if (mStopCallback != 0) mStopCallback(); break; - case ActiveSensing: if (mActiveSensingCallback != 0) mActiveSensingCallback(); break; + case Clock: if (mClockCallback != nullptr) mClockCallback(); break; + case Start: if (mStartCallback != nullptr) mStartCallback(); break; + case Tick: if (mTickCallback != nullptr) mTickCallback(); break; + case Continue: if (mContinueCallback != nullptr) mContinueCallback(); break; + case Stop: if (mStopCallback != nullptr) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != nullptr) mActiveSensingCallback(); break; // Continuous controllers - case ControlChange: if (mControlChangeCallback != 0) mControlChangeCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; - case PitchBend: if (mPitchBendCallback != 0) mPitchBendCallback(mMessage.channel, (int)((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7)) + MIDI_PITCHBEND_MIN); break; // TODO: check this - case AfterTouchPoly: if (mAfterTouchPolyCallback != 0) mAfterTouchPolyCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; - case AfterTouchChannel: if (mAfterTouchChannelCallback != 0) mAfterTouchChannelCallback(mMessage.channel, mMessage.data1); break; + case ControlChange: if (mControlChangeCallback != nullptr) mControlChangeCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != nullptr) mPitchBendCallback(mMessage.channel, (int)((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7)) + MIDI_PITCHBEND_MIN); break; + case AfterTouchPoly: if (mAfterTouchPolyCallback != nullptr) mAfterTouchPolyCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != nullptr) mAfterTouchChannelCallback(mMessage.channel, mMessage.data1); break; - case ProgramChange: if (mProgramChangeCallback != 0) mProgramChangeCallback(mMessage.channel, mMessage.data1); break; - case SystemExclusive: if (mSystemExclusiveCallback != 0) mSystemExclusiveCallback(mMessage.sysexArray, mMessage.getSysExSize()); break; + case ProgramChange: if (mProgramChangeCallback != nullptr) mProgramChangeCallback(mMessage.channel, mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != nullptr) mSystemExclusiveCallback(mMessage.sysexArray, mMessage.getSysExSize()); break; // Occasional messages - case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != 0) mTimeCodeQuarterFrameCallback(mMessage.data1); break; - case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback(unsigned((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7))); break; - case SongSelect: if (mSongSelectCallback != 0) mSongSelectCallback(mMessage.data1); break; - case TuneRequest: if (mTuneRequestCallback != 0) mTuneRequestCallback(); break; + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != nullptr) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != nullptr) mSongPositionCallback(unsigned((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7))); break; + case SongSelect: if (mSongSelectCallback != nullptr) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != nullptr) mTuneRequestCallback(); break; - case SystemReset: if (mSystemResetCallback != 0) mSystemResetCallback(); break; + case SystemReset: if (mSystemResetCallback != nullptr) mSystemResetCallback(); break; case InvalidType: default: @@ -1252,39 +1350,46 @@ void MidiInterface::launchCallback() @see Thru::Mode */ -template -inline void MidiInterface::setThruFilterMode(Thru::Mode inThruFilterMode) +template +inline void MidiInterface::setThruFilterMode(Thru::Mode inThruFilterMode) { mThruFilterMode = inThruFilterMode; mThruActivated = mThruFilterMode != Thru::Off; } -template -inline Thru::Mode MidiInterface::getFilterMode() const +template +inline Thru::Mode MidiInterface::getFilterMode() const { return mThruFilterMode; } -template -inline bool MidiInterface::getThruState() const +template +inline bool MidiInterface::getThruState() const { return mThruActivated; } -template -inline void MidiInterface::turnThruOn(Thru::Mode inThruFilterMode) +template +inline void MidiInterface::turnThruOn(Thru::Mode inThruFilterMode) { mThruActivated = true; mThruFilterMode = inThruFilterMode; } -template -inline void MidiInterface::turnThruOff() +template +inline void MidiInterface::turnThruOff() { mThruActivated = false; mThruFilterMode = Thru::Off; } +template +inline void MidiInterface::UpdateLastSentTime() +{ + if (Settings::UseSenderActiveSensing && mSenderActiveSensingPeriodicity) + mLastMessageSentTime = Platform::now(); +} + /*! @} */ // End of doc group MIDI Thru // This method is called upon reception of a message @@ -1293,8 +1398,8 @@ inline void MidiInterface::turnThruOff() // to output unless filter is set to Off. // - 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) +template +void MidiInterface::thruFilter(Channel inChannel) { // If the feature is disabled, don't do anything. if (!mThruActivated || (mThruFilterMode == Thru::Off)) diff --git a/src/midi_Defs.h b/src/midi_Defs.h index 786da708..ef74621c 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -2,7 +2,7 @@ * @file midi_Defs.h * Project Arduino MIDI Library * @brief MIDI Library for the Arduino - Definitions - * @author Francois Best + * @author Francois Best, lathoub * @date 24/02/11 * @license MIT - Copyright (c) 2015 Francois Best * @@ -38,11 +38,6 @@ typedef uint8_t byte; BEGIN_MIDI_NAMESPACE -#define MIDI_LIBRARY_VERSION 0x040400 -#define MIDI_LIBRARY_VERSION_MAJOR 4 -#define MIDI_LIBRARY_VERSION_MINOR 4 -#define MIDI_LIBRARY_VERSION_PATCH 0 - // ----------------------------------------------------------------------------- #define MIDI_CHANNEL_OMNI 0 @@ -51,6 +46,10 @@ BEGIN_MIDI_NAMESPACE #define MIDI_PITCHBEND_MIN -8192 #define MIDI_PITCHBEND_MAX 8191 +/*! Receiving Active Sensing +*/ +static const uint16_t ActiveSensingTimeout = 300; + // ----------------------------------------------------------------------------- // Type definitions @@ -59,28 +58,65 @@ typedef byte DataByte; typedef byte Channel; typedef byte FilterMode; +// ----------------------------------------------------------------------------- +// Errors +static const uint8_t ErrorParse = 0; +static const uint8_t ErrorActiveSensingTimeout = 1; +static const uint8_t WarningSplitSysEx = 2; + +// ----------------------------------------------------------------------------- +// Aliasing + +using ErrorCallback = void (*)(int8_t); +using NoteOffCallback = void (*)(Channel channel, byte note, byte velocity); +using NoteOnCallback = void (*)(Channel channel, byte note, byte velocity); +using AfterTouchPolyCallback = void (*)(Channel channel, byte note, byte velocity); +using ControlChangeCallback = void (*)(Channel channel, byte, byte); +using ProgramChangeCallback = void (*)(Channel channel, byte); +using AfterTouchChannelCallback = void (*)(Channel channel, byte); +using PitchBendCallback = void (*)(Channel channel, int); +using SystemExclusiveCallback = void (*)(byte * array, unsigned size); +using TimeCodeQuarterFrameCallback = void (*)(byte data); +using SongPositionCallback = void (*)(unsigned beats); +using SongSelectCallback = void (*)(byte songnumber); +using TuneRequestCallback = void (*)(void); +using ClockCallback = void (*)(void); +using StartCallback = void (*)(void); +using TickCallback = void (*)(void); +using ContinueCallback = void (*)(void); +using StopCallback = void (*)(void); +using ActiveSensingCallback = void (*)(void); +using SystemResetCallback = void (*)(void); + // ----------------------------------------------------------------------------- /*! Enumeration of MIDI types */ enum MidiType: uint8_t { InvalidType = 0x00, ///< For notifying errors - NoteOff = 0x80, ///< Note Off - NoteOn = 0x90, ///< Note On - AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch - ControlChange = 0xB0, ///< Control Change / Channel Mode - ProgramChange = 0xC0, ///< Program Change - AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch - PitchBend = 0xE0, ///< Pitch Bend + NoteOff = 0x80, ///< Channel Message - Note Off + NoteOn = 0x90, ///< Channel Message - Note On + AfterTouchPoly = 0xA0, ///< Channel Message - Polyphonic AfterTouch + ControlChange = 0xB0, ///< Channel Message - Control Change / Channel Mode + ProgramChange = 0xC0, ///< Channel Message - Program Change + AfterTouchChannel = 0xD0, ///< Channel Message - Channel (monophonic) AfterTouch + PitchBend = 0xE0, ///< Channel Message - Pitch Bend SystemExclusive = 0xF0, ///< System Exclusive + SystemExclusiveStart = SystemExclusive, ///< System Exclusive Start TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame SongPosition = 0xF2, ///< System Common - Song Position Pointer SongSelect = 0xF3, ///< System Common - Song Select + Undefined_F4 = 0xF4, + Undefined_F5 = 0xF5, TuneRequest = 0xF6, ///< System Common - Tune Request + SystemExclusiveEnd = 0xF7, ///< System Exclusive End Clock = 0xF8, ///< System Real Time - Timing Clock + Undefined_F9 = 0xF9, + Tick = Undefined_F9, ///< System Real Time - Timing Tick (1 tick = 10 milliseconds) Start = 0xFA, ///< System Real Time - Start Continue = 0xFB, ///< System Real Time - Continue Stop = 0xFC, ///< System Real Time - Stop + Undefined_FD = 0xFD, ActiveSensing = 0xFE, ///< System Real Time - Active Sensing SystemReset = 0xFF, ///< System Real Time - System Reset }; @@ -99,17 +135,6 @@ struct Thru }; }; -/*! Deprecated: use Thru::Mode instead. - Will be removed in v5.0. -*/ -enum __attribute__ ((deprecated)) MidiFilterMode -{ - Off = Thru::Off, - Full = Thru::Full, - SameChannel = Thru::SameChannel, - DifferentChannel = Thru::DifferentChannel, -}; - // ----------------------------------------------------------------------------- /*! \brief Enumeration of Control Change command numbers. @@ -204,35 +229,4 @@ struct RPN }; }; -// ----------------------------------------------------------------------------- - -/*! \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); - Then call midi2.begin(), midi2.read() etc.. - */ -#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \ - midi::MidiInterface Name((Type&)SerialPort); - -#if defined(SERIAL_PORT_HARDWARE_OPEN) - // Use recommended default external serial port. - #define MIDI_CREATE_DEFAULT_INSTANCE() \ - MIDI_CREATE_INSTANCE(HardwareSerial, SERIAL_PORT_HARDWARE_OPEN, MIDI); -#else - /*! \brief Create an instance of the library with default name, serial port - and settings, for compatibility with sketches written with pre-v4.2 MIDI Lib, - or if you don't bother using custom names, serial port or settings. - */ - #define MIDI_CREATE_DEFAULT_INSTANCE() \ - 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::MidiInterface Name((Type&)SerialPort); - END_MIDI_NAMESPACE diff --git a/src/midi_Message.h b/src/midi_Message.h index 0285b584..dadb8960 100644 --- a/src/midi_Message.h +++ b/src/midi_Message.h @@ -46,7 +46,7 @@ struct Message */ inline Message() : channel(0) - , type(midi::InvalidType) + , type(MIDI_NAMESPACE::InvalidType) , data1(0) , data2(0) , valid(false) @@ -91,6 +91,10 @@ struct Message */ bool valid; + /*! Total Length of the message. + */ + unsigned length; + inline unsigned getSysExSize() const { const unsigned size = unsigned(data2) << 8 | data1; diff --git a/src/midi_UsbPacketInterface.h b/src/midi_Platform.h similarity index 69% rename from src/midi_UsbPacketInterface.h rename to src/midi_Platform.h index ee27901e..61ca26f7 100644 --- a/src/midi_UsbPacketInterface.h +++ b/src/midi_Platform.h @@ -1,10 +1,10 @@ /*! - * @file midi_UsbPacketInterface.h + * @file midi_Platform.h * Project Arduino MIDI Library - * @brief MIDI Library for the Arduino - Transport layer for USB MIDI - * @author Francois Best - * @date 2018-11-03 - * @license MIT - Copyright (c) 2018 Francois Best + * @brief MIDI Library for the Arduino - Platform + * @license MIT - Copyright (c) 2015 Francois Best + * @author lathoub + * @date 22/03/20 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,17 +28,24 @@ #pragma once #include "midi_Defs.h" -#include "midi_UsbDefs.h" -#include BEGIN_MIDI_NAMESPACE -template -bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket); +#if ARDUINO -template -void serialiseRxPacket(const midiEventPacket_t& inPacket, Buffer& outBuffer); +// DefaultPlatform is the Arduino Platform +struct DefaultPlatform +{ + static unsigned long now() { return ::millis(); }; +}; -END_MIDI_NAMESPACE +#else + +struct DefaultPlatform +{ + static unsigned long now() { return 0; }; +}; -#include "midi_UsbPacketInterface.hpp" +#endif + +END_MIDI_NAMESPACE diff --git a/src/midi_RingBuffer.h b/src/midi_RingBuffer.h deleted file mode 100644 index 348ad9e2..00000000 --- a/src/midi_RingBuffer.h +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * @file midi_RingBuffer.h - * Project Arduino MIDI Library - * @brief MIDI Library for Arduino - Ring Buffer - * @author Francois Best - * @date 10/10/2016 - * @license MIT - Copyright (c) 2016 Francois Best - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -#include "midi_Namespace.h" - -BEGIN_MIDI_NAMESPACE - -template -class RingBuffer -{ -private: - static const int sMask = Size - 1; - -public: - RingBuffer(); - ~RingBuffer(); - -public: - inline int getLength() const; - inline bool isEmpty() const; - -public: - void write(DataType inData); - void write(const DataType* inData, int inSize); - void pop(int inNumberOfItems = 1); - void clear(); - -public: - DataType peek(int inOffset = 0) const; - DataType read(); - void read(DataType* outData, int inSize); - -private: - DataType mData[Size]; - int mLength; - int mWriteHead; - int mReadHead; -}; - -END_MIDI_NAMESPACE - -#include "midi_RingBuffer.hpp" diff --git a/src/midi_RingBuffer.hpp b/src/midi_RingBuffer.hpp deleted file mode 100644 index f3c53eba..00000000 --- a/src/midi_RingBuffer.hpp +++ /dev/null @@ -1,145 +0,0 @@ -/*! - * @file midi_RingBuffer.hpp - * Project Arduino MIDI Library - * @brief MIDI Library for Arduino - Ring Buffer - * @author Francois Best - * @date 10/10/2016 - * @license MIT - Copyright (c) 2016 Francois Best - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -namespace { - - template - struct isPowerOfTwo - { - static const bool value = N && !(N & (N - 1)); - }; - -} - -// -- - -BEGIN_MIDI_NAMESPACE - -template -RingBuffer::RingBuffer() - : mLength(0) - , mWriteHead(0) - , mReadHead(0) -{ - static_assert(isPowerOfTwo::value, "Size must be a power of two."); - memset(mData, DataType(0), Size * sizeof(DataType)); -} - -template -RingBuffer::~RingBuffer() -{ -} - -// ----------------------------------------------------------------------------- - -template -inline int RingBuffer::getLength() const -{ - return mLength; -} - -template -inline bool RingBuffer::isEmpty() const -{ - return mLength == 0; -} - -// ----------------------------------------------------------------------------- - -template -void RingBuffer::write(DataType inData) -{ - mData[mWriteHead] = inData; - mWriteHead = (mWriteHead + 1) & sMask; - mLength++; - if (mLength > Size) { - mLength = Size; - mReadHead = (mReadHead + 1) & sMask; - } -} - -template -void RingBuffer::write(const DataType* inData, int inSize) -{ - for (int i = 0; i < inSize; ++i) - { - write(inData[i]); - } -} - -template -void RingBuffer::pop(int inNumberOfItems) -{ - for (int i = 0; i < inNumberOfItems; ++i) - { - read(); - } -} - -template -void RingBuffer::clear() -{ - memset(mData, DataType(0), Size * sizeof(DataType)); - mReadHead = 0; - mWriteHead = 0; - mLength = 0; -} - -// ----------------------------------------------------------------------------- - -template -DataType RingBuffer::peek(int inOffset) const -{ - const int head = (mReadHead + inOffset) & sMask; - return mData[head]; -} - -template -DataType RingBuffer::read() -{ - mLength--; - if (mLength < 0) { - mLength = 0; - return 0; - } - const DataType data = mData[mReadHead]; - mReadHead = (mReadHead + 1) & sMask; - return data; -} - -template -void RingBuffer::read(DataType* outData, int inSize) -{ - for (int i = 0; i < inSize; ++i) - { - outData[i] = read(); - } -} - -END_MIDI_NAMESPACE diff --git a/src/midi_Settings.h b/src/midi_Settings.h index 493a3b31..0066190c 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -67,16 +67,38 @@ struct DefaultSettings */ static const bool Use1ByteParsing = true; - /*! Override the default MIDI baudrate to transmit over USB serial, to - a decoding program such as Hairless MIDI (set baudrate to 115200)\n - http://projectgus.github.io/hairless-midiserial/ - */ - static const long BaudRate = 31250; - /*! Maximum size of SysEx receivable. Decrease to save RAM if you don't expect to receive SysEx, or adjust accordingly. */ static const unsigned SysExMaxSize = 128; + + /*! Global switch to turn on/off sender ActiveSensing + Set to true to send ActiveSensing + Set to false will not send ActiveSensing message (will also save memory) + */ + static const bool UseSenderActiveSensing = false; + + /*! Global switch to turn on/off receiver ActiveSensing + Set to true to check for message timeouts (via ErrorCallback) + Set to false will not check if chained device are still alive (if they use ActiveSensing) (will also save memory) + */ + static const bool UseReceiverActiveSensing = false; + + /*! Active Sensing is intended to be sent + repeatedly by the sender to tell the receiver that a connection is alive. Use + of this message is optional. When initially received, the + receiver will expect to receive another Active Sensing + message each 300ms (max), and if it does not then it will + assume that the connection has been terminated. At + termination, the receiver will turn off all voices and return to + normal (non- active sensing) operation. + + Typical value is 250 (ms) - an Active Sensing command is send every 250ms. + (All Roland devices send Active Sensing every 250ms) + + Setting this field to 0 will disable sending MIDI active sensing. + */ + static const uint16_t SenderActiveSensingPeriodicity = 0; }; END_MIDI_NAMESPACE diff --git a/src/midi_UsbDefs.h b/src/midi_UsbDefs.h deleted file mode 100644 index 54defb59..00000000 --- a/src/midi_UsbDefs.h +++ /dev/null @@ -1,135 +0,0 @@ -/*! - * @file midi_UsbDefs.h - * Project Arduino MIDI Library - * @brief MIDI Library for the Arduino - Definitions - * @author Francois Best - * @date 24/02/11 - * @license MIT - Copyright (c) 2016 Francois Best - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -#include "midi_Defs.h" - -BEGIN_MIDI_NAMESPACE - -struct CodeIndexNumbers -{ - enum - { - reserved = 0x00, - misc = reserved, - - cableEvent = 0x01, - systemCommon2Bytes = 0x02, - systemCommon3Bytes = 0x03, - - sysExStart = 0x04, - sysExContinue = sysExStart, - - systemCommon1Byte = 0x05, - sysExEnds1Byte = systemCommon1Byte, - - sysExEnds2Bytes = 0x06, - sysExEnds3Bytes = 0x07, - noteOff = 0x08, - noteOn = 0x09, - polyPressure = 0x0A, - controlChange = 0x0B, - programChange = 0x0C, - channelPressure = 0x0D, - pitchBend = 0x0E, - singleByte = 0x0F, - }; - - static inline byte fromStatus(StatusByte inStatus) - { - const byte statusWithoutChannel = inStatus & 0xf0; - if (statusWithoutChannel >= midi::NoteOff && - statusWithoutChannel <= midi::PitchBend) - { - // Channel Voice Messages - return inStatus >> 4; - } - switch (inStatus) - { - // System Real Time Messages - case midi::Clock: - case midi::Start: - case midi::Continue: - case midi::Stop: - case midi::ActiveSensing: - case midi::SystemReset: - return CodeIndexNumbers::singleByte; - - // System Exclusive - case midi::SystemExclusive: - return CodeIndexNumbers::sysExStart; - case 0xf7: - return CodeIndexNumbers::sysExEnds1Byte; - - // System Common Messages - case midi::TimeCodeQuarterFrame: - return CodeIndexNumbers::systemCommon2Bytes; - case midi::SongPosition: - return CodeIndexNumbers::systemCommon3Bytes; - case midi::SongSelect: - return CodeIndexNumbers::systemCommon2Bytes; - case midi::TuneRequest: - return CodeIndexNumbers::systemCommon1Byte; - - default: - return CodeIndexNumbers::reserved; - } - - } - - static inline byte getSize(byte inCodeIndexNumber) - { - switch (inCodeIndexNumber) - { - case noteOn: - case noteOff: - case controlChange: - case pitchBend: - case polyPressure: - case systemCommon3Bytes: - case sysExEnds3Bytes: - case sysExStart: - return 3; - - case programChange: - case channelPressure: - case systemCommon2Bytes: - case sysExEnds2Bytes: - return 2; - - case systemCommon1Byte: // also sysExEnds1Byte - case singleByte: - return 1; - - default: - return 0; // Can be any length (1, 2 or 3). - } - } -}; - -END_MIDI_NAMESPACE diff --git a/src/midi_UsbPacketInterface.hpp b/src/midi_UsbPacketInterface.hpp deleted file mode 100644 index 26cb22a5..00000000 --- a/src/midi_UsbPacketInterface.hpp +++ /dev/null @@ -1,146 +0,0 @@ -/*! - * @file midi_UsbPacketInterface.hpp - * Project Arduino MIDI Library - * @brief MIDI Library for the Arduino - Transport layer for USB MIDI - * @author Francois Best - * @date 2018-11-03 - * @license MIT - Copyright (c) 2018 Francois Best - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -BEGIN_MIDI_NAMESPACE - -template -bool composeTxPacket(Buffer& inBuffer, midiEventPacket_t& outPacket) -{ - if (inBuffer.isEmpty()) { - return false; - } - const int bufferLength = inBuffer.getLength(); - const byte status = inBuffer.peek(); - const byte cin = midi::CodeIndexNumbers::fromStatus(status); - const byte messageLength = midi::CodeIndexNumbers::getSize(cin); - - if (status == 0xf0) - { - // Start of SysEx, check if it can end in one go. - if (bufferLength == 2 && inBuffer.peek(1) == 0xf7) - { - outPacket.header = midi::CodeIndexNumbers::sysExEnds2Bytes; - outPacket.byte1 = status; - outPacket.byte2 = 0xf7; - outPacket.byte3 = 0x00; - inBuffer.pop(2); - return true; - } - if (bufferLength >= 3 && inBuffer.peek(2) == 0xf7) - { - outPacket.header = midi::CodeIndexNumbers::sysExEnds3Bytes; - outPacket.byte1 = status; - outPacket.byte2 = inBuffer.peek(1); - outPacket.byte3 = 0xf7; - inBuffer.pop(3); - return true; - } - } - - if ((status & 0x80) == 0x00) - { - // First byte is data, consider it's part of a running SysEx message. - // We look for the SysEx end byte in the next 2 bytes - // At this point, bufferLength should be 2 or more to continue. - if (bufferLength == 1) - { - return false; // Not enough data - } - if (bufferLength == 2) - { - const bool isSysExEnd = inBuffer.peek(1) == 0xf7; - if (!isSysExEnd) - { - return false; // Not enough data (eg: 0x12 0x42) - } - // eg: 0x42 0xf7 - outPacket.header = midi::CodeIndexNumbers::sysExEnds2Bytes; - outPacket.byte1 = status; - outPacket.byte2 = inBuffer.peek(1); - outPacket.byte3 = 0x00; - inBuffer.pop(2); - return true; - } - else - { - // bufferLength > 2 - const byte byte3 = inBuffer.peek(2); - outPacket.header = byte3 == 0xf7 - ? midi::CodeIndexNumbers::sysExEnds3Bytes - : midi::CodeIndexNumbers::sysExContinue; - outPacket.byte1 = status; - outPacket.byte2 = inBuffer.peek(1); - outPacket.byte3 = byte3; - inBuffer.pop(3); - return true; - } - } - - if (bufferLength < messageLength) { - return false; // Not enough data in the buffer to compose a full packet. - } - - outPacket.header = cin; - outPacket.byte1 = status; - outPacket.byte2 = messageLength >= 2 ? inBuffer.peek(1) : 0x00; - outPacket.byte3 = messageLength >= 3 ? inBuffer.peek(2) : 0x00; - - inBuffer.pop(messageLength); - return true; - - // todo: handle interleaved RealTime messages -} - -template -void serialiseRxPacket(const midiEventPacket_t& inPacket, Buffer& outBuffer) -{ - const byte cin = inPacket.header & 0x0f; - const byte messageLength = midi::CodeIndexNumbers::getSize(cin); - switch (messageLength) - { - case 1: - outBuffer.write(inPacket.byte1); - return; - case 2: - outBuffer.write(inPacket.byte1); - outBuffer.write(inPacket.byte2); - return; - case 3: - outBuffer.write(inPacket.byte1); - outBuffer.write(inPacket.byte2); - outBuffer.write(inPacket.byte3); - return; - case 0: - default: - // Invalid or ignored messages, don't serialise. - return; - } -} - -END_MIDI_NAMESPACE diff --git a/src/midi_UsbTransport.h b/src/midi_UsbTransport.h deleted file mode 100644 index eb869e94..00000000 --- a/src/midi_UsbTransport.h +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * @file midi_UsbTransport.h - * Project Arduino MIDI Library - * @brief MIDI Library for the Arduino - Transport layer for USB MIDI - * @author Francois Best - * @date 10/10/2016 - * @license MIT - Copyright (c) 2016 Francois Best - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -#include "midi_Defs.h" -#include "midi_RingBuffer.h" -#include "midi_UsbPacketInterface.h" -#include - -BEGIN_MIDI_NAMESPACE - -template -class UsbTransport -{ -public: - inline UsbTransport(); - inline ~UsbTransport(); - -public: // Serial / Stream API required for template compatibility - inline void begin(unsigned inBaudrate); - inline unsigned available(); - inline byte read(); - inline void write(byte inData); - -private: - inline void pollUsbMidi(); - inline void recomposeAndSendTxPackets(); - -private: - typedef RingBuffer Buffer; - Buffer mTxBuffer; - Buffer mRxBuffer; -}; - -END_MIDI_NAMESPACE - -#include "midi_UsbTransport.hpp" diff --git a/src/midi_UsbTransport.hpp b/src/midi_UsbTransport.hpp deleted file mode 100644 index 51594339..00000000 --- a/src/midi_UsbTransport.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/*! - * @file midi_UsbTransport.hpp - * Project Arduino MIDI Library - * @brief MIDI Library for the Arduino - Transport layer for USB MIDI - * @author Francois Best - * @date 10/10/2016 - * @license MIT - Copyright (c) 2016 Francois Best - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -BEGIN_MIDI_NAMESPACE - -template -inline UsbTransport::UsbTransport() -{ - -} - -template -inline UsbTransport::~UsbTransport() -{ - -} - -// ----------------------------------------------------------------------------- - -template -inline void UsbTransport::begin(unsigned inBaudrate) -{ - mTxBuffer.clear(); - mRxBuffer.clear(); -} - -template -inline unsigned UsbTransport::available() -{ - pollUsbMidi(); - return mRxBuffer.getLength(); -} - -template -inline byte UsbTransport::read() -{ - return mRxBuffer.read(); -} - -template -inline void UsbTransport::write(byte inData) -{ - mTxBuffer.write(inData); - recomposeAndSendTxPackets(); -} - -// ----------------------------------------------------------------------------- - -template -inline void UsbTransport::pollUsbMidi() -{ - midiEventPacket_t packet = MidiUSB.read(); - while (packet.header != 0) - { - serialiseRxPacket(packet, mRxBuffer); - packet = MidiUSB.read(); - } -} - -template -inline void UsbTransport::recomposeAndSendTxPackets() -{ - midiEventPacket_t packet; - bool sent = false; - while (composeTxPacket(mTxBuffer, packet)) - { - MidiUSB.sendMIDI(packet); - sent = true; - } - if (sent) - { - MidiUSB.flush(); - } -} - -END_MIDI_NAMESPACE diff --git a/src/serialMIDI.h b/src/serialMIDI.h new file mode 100644 index 00000000..b57d35c9 --- /dev/null +++ b/src/serialMIDI.h @@ -0,0 +1,115 @@ +/*! + * @file serialMIDI.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Platform + * @license MIT - Copyright (c) 2015 Francois Best + * @author lathoub, Francois Best + * @date 22/03/20 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + #pragma once + +#include "midi_Namespace.h" + +BEGIN_MIDI_NAMESPACE + +struct DefaultSerialSettings +{ + /*! Override the default MIDI baudrate to transmit over USB serial, to + a decoding program such as Hairless MIDI (set baudrate to 115200)\n + http://projectgus.github.io/hairless-midiserial/ + */ + static const long BaudRate = 31250; +}; + +template +class SerialMIDI +{ + typedef _Settings Settings; + +public: + SerialMIDI(SerialPort& inSerial) + : mSerial(inSerial) + { + }; + +public: + void begin() + { + // Initialise the Serial port + #if defined(AVR_CAKE) + mSerial. template open(); + #else + mSerial.begin(Settings::BaudRate); + #endif + } + + bool beginTransmission(MidiType) + { + return true; + }; + + void write(byte value) + { + mSerial.write(value); + }; + + void endTransmission() + { + }; + + byte read() + { + return mSerial.read(); + }; + + unsigned available() + { + return mSerial.available(); + }; + +private: + SerialPort& mSerial; +}; + +/*! \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); + Then call midi2.begin(), midi2.read() etc.. + */ +#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \ + MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ + MIDI_NAMESPACE::MidiInterface> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); + +#if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) + // Leonardo, Due and other USB boards use Serial1 by default. + #define MIDI_CREATE_DEFAULT_INSTANCE() \ + MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); +#else + /*! \brief Create an instance of the library with default name, serial port + and settings, for compatibility with sketches written with pre-v4.2 MIDI Lib, + or if you don't bother using custom names, serial port or settings. + */ + #define MIDI_CREATE_DEFAULT_INSTANCE() \ + MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); +#endif + + +END_MIDI_NAMESPACE diff --git a/test/mocks/test-mocks_SerialMock.h b/test/mocks/test-mocks_SerialMock.h index f3ab833d..b18ebb99 100644 --- a/test/mocks/test-mocks_SerialMock.h +++ b/test/mocks/test-mocks_SerialMock.h @@ -2,10 +2,36 @@ #include "test-mocks.h" #include -#include BEGIN_TEST_MOCKS_NAMESPACE +template +class RingBuffer +{ +public: + RingBuffer(); + ~RingBuffer(); + +public: + int getLength() const; + bool isEmpty() const; + +public: + void write(DataType inData); + void write(const DataType* inData, int inSize); + void clear(); + +public: + DataType peek() const; + DataType read(); + void read(DataType* outData, int inSize); + +private: + DataType mData[Size]; + DataType* mWriteHead; + DataType* mReadHead; +}; + template class SerialMock { @@ -23,7 +49,7 @@ class SerialMock void moveTxToRx(); // Simulate loopback public: - typedef midi::RingBuffer Buffer; + typedef RingBuffer Buffer; Buffer mTxBuffer; Buffer mRxBuffer; int mBaudrate; diff --git a/test/mocks/test-mocks_SerialMock.hpp b/test/mocks/test-mocks_SerialMock.hpp index 3c06e29a..f19f4e02 100644 --- a/test/mocks/test-mocks_SerialMock.hpp +++ b/test/mocks/test-mocks_SerialMock.hpp @@ -2,6 +2,103 @@ BEGIN_TEST_MOCKS_NAMESPACE +template +RingBuffer::RingBuffer() + : mWriteHead(mData) + , mReadHead(mData) +{ + memset(mData, DataType(0), Size * sizeof(DataType)); +} + +template +RingBuffer::~RingBuffer() +{ +} + +// ----------------------------------------------------------------------------- + +template +int RingBuffer::getLength() const +{ + if (mReadHead == mWriteHead) + { + return 0; + } + else if (mWriteHead > mReadHead) + { + return int(mWriteHead - mReadHead); + } + else + { + return int(mWriteHead - mData) + Size - int(mReadHead - mData); + } +} + +template +bool RingBuffer::isEmpty() const +{ + return mReadHead == mWriteHead; +} + +// ----------------------------------------------------------------------------- + +template +void RingBuffer::write(DataType inData) +{ + *mWriteHead++ = inData; + if (mWriteHead >= mData + Size) + { + mWriteHead = mData; + } +} + +template +void RingBuffer::write(const DataType* inData, int inSize) +{ + for (int i = 0; i < inSize; ++i) + { + write(inData[i]); + } +} + +template +void RingBuffer::clear() +{ + memset(mData, DataType(0), Size * sizeof(DataType)); + mReadHead = mData; + mWriteHead = mData; +} + +// ----------------------------------------------------------------------------- + +template +DataType RingBuffer::peek() const +{ + return *mReadHead; +} + +template +DataType RingBuffer::read() +{ + const DataType data = *mReadHead++; + if (mReadHead >= mData + Size) + { + mReadHead = mData; + } + return data; +} + +template +void RingBuffer::read(DataType* outData, int inSize) +{ + for (int i = 0; i < inSize; ++i) + { + outData[i] = read(); + } +} + +// ============================================================================= + template SerialMock::SerialMock() { diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index a63cbd4e..eaf1f3fd 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -6,7 +6,6 @@ include_directories( "${unit-tests_SOURCE_DIR}" "${gtest_SOURCE_DIR}/include" "${gmock_SOURCE_DIR}/include" - "${ROOT_SOURCE_DIR}/external/midi-usb/src" ) add_executable(unit-tests @@ -18,13 +17,10 @@ add_executable(unit-tests tests/unit-tests_Settings.cpp tests/unit-tests_Settings.h tests/unit-tests_SysExCodec.cpp - tests/unit-tests_RingBuffer.cpp tests/unit-tests_MidiInput.cpp tests/unit-tests_MidiInputCallbacks.cpp tests/unit-tests_MidiOutput.cpp tests/unit-tests_MidiThru.cpp - tests/unit-tests_MidiUsbDefs.cpp - tests/unit-tests_MidiUsbPacketInterface.cpp ) target_link_libraries(unit-tests diff --git a/test/unit-tests/tests/unit-tests_MidiInput.cpp b/test/unit-tests/tests/unit-tests_MidiInput.cpp index 38d5c9f1..bf63e0cf 100644 --- a/test/unit-tests/tests/unit-tests_MidiInput.cpp +++ b/test/unit-tests/tests/unit-tests_MidiInput.cpp @@ -14,7 +14,8 @@ BEGIN_UNNAMED_NAMESPACE using namespace testing; USING_NAMESPACE_UNIT_TESTS typedef test_mocks::SerialMock<32> SerialMock; -typedef midi::MidiInterface MidiInterface; +typedef midi::SerialMIDI Transport; +typedef midi::MidiInterface MidiInterface; template struct VariableSysExSettings : midi::DefaultSettings @@ -53,7 +54,6 @@ TEST(MidiInput, getTypeFromStatusByte) } EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf4), midi::InvalidType); EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf5), midi::InvalidType); - EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf9), midi::InvalidType); EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xfd), midi::InvalidType); } @@ -93,12 +93,13 @@ TEST(MidiInput, isChannelMessage) TEST(MidiInput, begin) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); // Default channel midi.begin(); EXPECT_EQ(serial.mBaudrate, 31250); - EXPECT_EQ(midi.getInputChannel(), 1); + EXPECT_EQ(midi.getInputChannel(), 0); // Specific channel midi.begin(12); @@ -109,7 +110,8 @@ TEST(MidiInput, begin) TEST(MidiInput, initInputChannel) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); EXPECT_EQ(midi.getInputChannel(), 0); midi.setInputChannel(12); @@ -119,7 +121,9 @@ TEST(MidiInput, initInputChannel) TEST(MidiInput, initMessage) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + EXPECT_EQ(midi.getType(), midi::InvalidType); EXPECT_EQ(midi.getChannel(), 0); EXPECT_EQ(midi.getData1(), 0); @@ -131,7 +135,9 @@ TEST(MidiInput, initMessage) TEST(MidiInput, channelFiltering) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 3; static const byte rxData[rxSize] = { 0x9b, 12, 34 }; midi.begin(4); // Mistmatching channel @@ -144,7 +150,9 @@ TEST(MidiInput, channelFiltering) TEST(MidiInput, noRxData) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + midi.begin(); EXPECT_EQ(midi.read(), false); } @@ -152,7 +160,9 @@ TEST(MidiInput, noRxData) TEST(MidiInput, inputDisabled) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 3; static const byte rxData[rxSize] = { 0x9b, 12, 34 }; midi.begin(MIDI_CHANNEL_OFF); // Invalid channel @@ -165,10 +175,12 @@ TEST(MidiInput, inputDisabled) TEST(MidiInput, multiByteParsing) { typedef VariableSettings Settings; - typedef midi::MidiInterface MultiByteMidiInterface; + typedef midi::MidiInterface MultiByteMidiInterface; SerialMock serial; - MultiByteMidiInterface midi(serial); + Transport transport(serial); + MultiByteMidiInterface midi(transport); + static const unsigned rxSize = 3; static const byte rxData[rxSize] = { 0x9b, 12, 34 }; midi.begin(12); @@ -179,7 +191,9 @@ TEST(MidiInput, multiByteParsing) TEST(MidiInput, noteOn) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 10; static const byte rxData[rxSize] = { 0x9b, 12, 34, @@ -230,7 +244,9 @@ TEST(MidiInput, noteOn) TEST(MidiInput, noteOff) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 8; static const byte rxData[rxSize] = { 0x8b, 12, 34, @@ -272,7 +288,9 @@ TEST(MidiInput, noteOff) TEST(MidiInput, programChange) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0xc3, 12, 34, @@ -316,7 +334,9 @@ TEST(MidiInput, programChange) TEST(MidiInput, controlChange) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 8; static const byte rxData[rxSize] = { 0xbb, 12, 34, @@ -358,7 +378,9 @@ TEST(MidiInput, controlChange) TEST(MidiInput, pitchBend) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 8; static const byte rxData[rxSize] = { 0xeb, 12, 34, @@ -400,7 +422,9 @@ TEST(MidiInput, pitchBend) TEST(MidiInput, afterTouchPoly) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 8; static const byte rxData[rxSize] = { 0xab, 12, 34, @@ -442,7 +466,9 @@ TEST(MidiInput, afterTouchPoly) TEST(MidiInput, afterTouchChannel) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0xd3, 12, 34, @@ -487,10 +513,12 @@ TEST(MidiInput, sysExWithinBufferSize) { typedef VariableSysExSettings<1024> Settings; typedef test_mocks::SerialMock<2048> LargerSerialMock; - typedef midi::MidiInterface LargerMidiInterface; + typedef midi::SerialMIDI LargerTransport; + typedef midi::MidiInterface LargerMidiInterface; LargerSerialMock serial; - LargerMidiInterface midi(serial); + LargerTransport transport(serial); + LargerMidiInterface midi(transport); // Short Frame < 256 { @@ -553,10 +581,11 @@ TEST(MidiInput, sysExWithinBufferSize) TEST(MidiInput, sysExOverBufferSize) { typedef VariableSysExSettings<8> Settings; - typedef midi::MidiInterface SmallMidiInterface; + typedef midi::MidiInterface SmallMidiInterface; SerialMock serial; - SmallMidiInterface midi(serial); + Transport transport(serial); + SmallMidiInterface midi(transport); static const unsigned frameLength = 15; static const byte frame[frameLength] = { @@ -566,17 +595,29 @@ TEST(MidiInput, sysExOverBufferSize) midi.begin(); serial.mRxBuffer.write(frame, frameLength); - for (unsigned i = 0; i < frameLength - 1; ++i) - { - EXPECT_EQ(midi.read(), false); - } - EXPECT_EQ(midi.read(), false); + EXPECT_EQ(midi.read(), false); // start sysex f0 + EXPECT_EQ(midi.read(), false); // H + EXPECT_EQ(midi.read(), false); // e + EXPECT_EQ(midi.read(), false); // l + EXPECT_EQ(midi.read(), false); // l + EXPECT_EQ(midi.read(), false); // o + EXPECT_EQ(midi.read(), false); // , message send and buffer cleared. + EXPECT_EQ(midi.read(), false); // start sysex + EXPECT_EQ(midi.read(), false); // (space) + EXPECT_EQ(midi.read(), false); // W + EXPECT_EQ(midi.read(), false); // o + EXPECT_EQ(midi.read(), false); // r + EXPECT_EQ(midi.read(), false); // l + EXPECT_EQ(midi.read(), false); // d + EXPECT_EQ(midi.read(), true); // end sysex } TEST(MidiInput, mtcQuarterFrame) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 4; static const byte rxData[rxSize] = { 0xf1, 12, @@ -606,7 +647,9 @@ TEST(MidiInput, mtcQuarterFrame) TEST(MidiInput, songPosition) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 6; static const byte rxData[rxSize] = { 0xf2, 12, 34, @@ -638,7 +681,9 @@ TEST(MidiInput, songPosition) TEST(MidiInput, songSelect) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 4; static const byte rxData[rxSize] = { 0xf3, 12, @@ -668,7 +713,9 @@ TEST(MidiInput, songSelect) TEST(MidiInput, tuneRequest) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 1; static const byte rxData[rxSize] = { 0xf6 @@ -686,7 +733,9 @@ TEST(MidiInput, tuneRequest) TEST(MidiInput, realTime) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 8; static const byte rxData[rxSize] = { 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff @@ -700,7 +749,11 @@ TEST(MidiInput, realTime) EXPECT_EQ(midi.getData1(), 0); EXPECT_EQ(midi.getData2(), 0); - EXPECT_EQ(midi.read(), false); // 0xf9 = undefined + EXPECT_EQ(midi.read(), true); + EXPECT_EQ(midi.getType(), midi::Tick); + EXPECT_EQ(midi.getChannel(), 0); + EXPECT_EQ(midi.getData1(), 0); + EXPECT_EQ(midi.getData2(), 0); EXPECT_EQ(midi.read(), true); EXPECT_EQ(midi.getType(), midi::Start); @@ -740,7 +793,8 @@ TEST(MidiInput, realTime) TEST(MidiInput, interleavedRealTime) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); // Interleaved Clocks between NoteOn / Off messages (with running status) { @@ -754,6 +808,7 @@ TEST(MidiInput, interleavedRealTime) }; midi.begin(12); serial.mRxBuffer.write(rxData, rxSize); + EXPECT_EQ(midi.read(), false); EXPECT_EQ(midi.read(), false); @@ -816,6 +871,7 @@ TEST(MidiInput, interleavedRealTime) }; midi.begin(12); serial.mRxBuffer.write(rxData, rxSize); + EXPECT_EQ(midi.read(), false); EXPECT_EQ(midi.read(), false); EXPECT_EQ(midi.read(), false); @@ -841,13 +897,16 @@ TEST(MidiInput, strayEox) { // A stray End of Exclusive will reset the parser, but should it ? SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + static const unsigned rxSize = 4; static const byte rxData[rxSize] = { 0x8b, 42, 0xf7, 12 }; midi.begin(MIDI_CHANNEL_OMNI); serial.mRxBuffer.write(rxData, rxSize); + EXPECT_EQ(midi.read(), false); EXPECT_EQ(midi.read(), false); EXPECT_EQ(midi.read(), false); @@ -857,18 +916,20 @@ TEST(MidiInput, strayEox) TEST(MidiInput, strayUndefinedOneByteParsing) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); static const unsigned rxSize = 13; static const byte rxData[rxSize] = { - 0xbb, 12, 0xf9, 34, + 0xbb, 12, 0xfd, 34, 12, 0, 42, 0xfd, 127, - 0xf9, + 0xfd, 42, 0xfd, 0 }; midi.begin(12); serial.mRxBuffer.write(rxData, rxSize); + EXPECT_EQ(midi.read(), false); EXPECT_EQ(midi.read(), false); EXPECT_EQ(midi.read(), false); // Invalid, should not reset parser @@ -907,17 +968,19 @@ TEST(MidiInput, strayUndefinedOneByteParsing) TEST(MidiInput, strayUndefinedMultiByteParsing) { typedef VariableSettings Settings; - typedef midi::MidiInterface MultiByteMidiInterface; + typedef midi::MidiInterface MultiByteMidiInterface; SerialMock serial; - MultiByteMidiInterface midi(serial); + Transport transport(serial); + MultiByteMidiInterface midi(transport); static const unsigned rxSize = 4; static const byte rxData[rxSize] = { - 0xbb, 12, 0xf9, 34, + 0xbb, 12, 0xfd, 34, }; midi.begin(12); serial.mRxBuffer.write(rxData, rxSize); + EXPECT_EQ(midi.read(), true); EXPECT_EQ(midi.getType(), midi::ControlChange); EXPECT_EQ(midi.getChannel(), 12); diff --git a/test/unit-tests/tests/unit-tests_MidiInputCallbacks.cpp b/test/unit-tests/tests/unit-tests_MidiInputCallbacks.cpp index 803e7382..2da2772b 100644 --- a/test/unit-tests/tests/unit-tests_MidiInputCallbacks.cpp +++ b/test/unit-tests/tests/unit-tests_MidiInputCallbacks.cpp @@ -21,8 +21,10 @@ struct VariableSysExSettings : midi::DefaultSettings }; typedef test_mocks::SerialMock<256> SerialMock; +typedef midi::SerialMIDI Transport; + typedef VariableSysExSettings<256> Settings; -typedef midi::MidiInterface MidiInterface; +typedef midi::MidiInterface MidiInterface; MidiInterface* midi; @@ -30,7 +32,8 @@ class MidiInputCallbacks : public Test { public: MidiInputCallbacks() - : mMidi(mSerial) + : mTransport(mSerial) + , mMidi(mTransport) { } virtual ~MidiInputCallbacks() @@ -50,6 +53,7 @@ class MidiInputCallbacks : public Test protected: SerialMock mSerial; + Transport mTransport; MidiInterface mMidi; }; diff --git a/test/unit-tests/tests/unit-tests_MidiOutput.cpp b/test/unit-tests/tests/unit-tests_MidiOutput.cpp index 4e7c8e4c..c243966b 100644 --- a/test/unit-tests/tests/unit-tests_MidiOutput.cpp +++ b/test/unit-tests/tests/unit-tests_MidiOutput.cpp @@ -15,7 +15,8 @@ using namespace testing; USING_NAMESPACE_UNIT_TESTS; typedef test_mocks::SerialMock<32> SerialMock; -typedef midi::MidiInterface MidiInterface; +typedef midi::SerialMIDI Transport; +typedef midi::MidiInterface MidiInterface; typedef std::vector Buffer; @@ -24,7 +25,8 @@ typedef std::vector Buffer; TEST(MidiOutput, sendInvalid) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); midi.begin(); midi.send(midi::NoteOn, 42, 42, 42); // Invalid channel > OFF @@ -40,7 +42,9 @@ TEST(MidiOutput, sendInvalid) TEST(MidiOutput, sendGenericSingle) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(3); @@ -54,10 +58,12 @@ TEST(MidiOutput, sendGenericSingle) TEST(MidiOutput, sendGenericWithRunningStatus) { typedef VariableSettings Settings; - typedef midi::MidiInterface RsMidiInterface; + typedef midi::MidiInterface RsMidiInterface; SerialMock serial; - RsMidiInterface midi(serial); + Transport transport(serial); + RsMidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(5); @@ -74,10 +80,12 @@ TEST(MidiOutput, sendGenericWithRunningStatus) TEST(MidiOutput, sendGenericWithoutRunningStatus) { typedef VariableSettings Settings; // No running status - typedef midi::MidiInterface NoRsMidiInterface; + typedef midi::MidiInterface NoRsMidiInterface; SerialMock serial; - NoRsMidiInterface midi(serial); + Transport transport(serial); + NoRsMidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(6); @@ -103,7 +111,9 @@ TEST(MidiOutput, sendGenericWithoutRunningStatus) TEST(MidiOutput, sendGenericBreakingRunningStatus) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(6); @@ -118,7 +128,9 @@ TEST(MidiOutput, sendGenericBreakingRunningStatus) TEST(MidiOutput, sendGenericRealTimeShortcut) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(6); @@ -140,7 +152,9 @@ TEST(MidiOutput, sendGenericRealTimeShortcut) TEST(MidiOutput, sendNoteOn) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(6); @@ -155,7 +169,9 @@ TEST(MidiOutput, sendNoteOn) TEST(MidiOutput, sendNoteOff) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(6); @@ -170,7 +186,9 @@ TEST(MidiOutput, sendNoteOff) TEST(MidiOutput, sendProgramChange) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(4); @@ -185,7 +203,9 @@ TEST(MidiOutput, sendProgramChange) TEST(MidiOutput, sendControlChange) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(6); @@ -200,7 +220,9 @@ TEST(MidiOutput, sendControlChange) TEST(MidiOutput, sendPitchBend) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; // Int signature - arbitrary values @@ -257,7 +279,9 @@ TEST(MidiOutput, sendPolyPressure) // This test is kept for coverage until removal of sendPolyPressure. SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(6); @@ -272,7 +296,9 @@ TEST(MidiOutput, sendPolyPressure) TEST(MidiOutput, sendAfterTouchMono) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(4); @@ -287,7 +313,9 @@ TEST(MidiOutput, sendAfterTouchMono) TEST(MidiOutput, sendAfterTouchPoly) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(6); @@ -302,10 +330,13 @@ TEST(MidiOutput, sendAfterTouchPoly) TEST(MidiOutput, sendSysEx) { typedef test_mocks::SerialMock<1024> LargeSerialMock; - typedef midi::MidiInterface LargeMidiInterface; + typedef midi::SerialMIDI LargeTransport; + typedef midi::MidiInterface LargeMidiInterface; LargeSerialMock serial; - LargeMidiInterface midi(serial); + LargeTransport transport(serial); + LargeMidiInterface midi((LargeTransport&)transport); + Buffer buffer; // Short frame @@ -384,7 +415,9 @@ TEST(MidiOutput, sendSysEx) TEST(MidiOutput, sendTimeCodeQuarterFrame) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; // Separate Nibbles @@ -418,7 +451,9 @@ TEST(MidiOutput, sendTimeCodeQuarterFrame) TEST(MidiOutput, sendSongPosition) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(6); @@ -434,7 +469,9 @@ TEST(MidiOutput, sendSongPosition) TEST(MidiOutput, sendSongSelect) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(4); @@ -449,7 +486,9 @@ TEST(MidiOutput, sendSongSelect) TEST(MidiOutput, sendTuneRequest) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; buffer.resize(1); @@ -463,7 +502,9 @@ TEST(MidiOutput, sendTuneRequest) TEST(MidiOutput, sendRealTime) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; // Test valid RealTime messages @@ -509,10 +550,12 @@ TEST(MidiOutput, sendRealTime) TEST(MidiOutput, RPN) { typedef VariableSettings Settings; - typedef midi::MidiInterface RsMidiInterface; + typedef midi::MidiInterface RsMidiInterface; SerialMock serial; - RsMidiInterface midi(serial); + Transport transport(serial); + RsMidiInterface midi((Transport&)transport); + Buffer buffer; // 14-bit Value Single Frame @@ -625,10 +668,12 @@ TEST(MidiOutput, RPN) TEST(MidiOutput, NRPN) { typedef VariableSettings Settings; - typedef midi::MidiInterface RsMidiInterface; + typedef midi::MidiInterface RsMidiInterface; SerialMock serial; - RsMidiInterface midi(serial); + Transport transport(serial); + RsMidiInterface midi((Transport&)transport); + Buffer buffer; // 14-bit Value Single Frame @@ -741,10 +786,12 @@ TEST(MidiOutput, NRPN) TEST(MidiOutput, runningStatusCancellation) { typedef VariableSettings Settings; - typedef midi::MidiInterface RsMidiInterface; + typedef midi::MidiInterface RsMidiInterface; SerialMock serial; - RsMidiInterface midi(serial); + Transport transport(serial); + RsMidiInterface midi((Transport&)transport); + Buffer buffer; static const unsigned sysExLength = 13; diff --git a/test/unit-tests/tests/unit-tests_MidiThru.cpp b/test/unit-tests/tests/unit-tests_MidiThru.cpp index 59a873da..dc0c0c1b 100644 --- a/test/unit-tests/tests/unit-tests_MidiThru.cpp +++ b/test/unit-tests/tests/unit-tests_MidiThru.cpp @@ -14,7 +14,8 @@ BEGIN_UNNAMED_NAMESPACE using namespace testing; USING_NAMESPACE_UNIT_TESTS typedef test_mocks::SerialMock<32> SerialMock; -typedef midi::MidiInterface MidiInterface; +typedef midi::SerialMIDI Transport; +typedef midi::MidiInterface MidiInterface; typedef std::vector Buffer; template @@ -28,7 +29,8 @@ struct VariableSysExSettings : midi::DefaultSettings TEST(MidiThru, defaultValues) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); EXPECT_EQ(midi.getThruState(), true); EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); @@ -40,7 +42,8 @@ TEST(MidiThru, defaultValues) TEST(MidiThru, beginEnablesThru) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); midi.turnThruOff(); EXPECT_EQ(midi.getThruState(), false); @@ -53,7 +56,8 @@ TEST(MidiThru, beginEnablesThru) TEST(MidiThru, setGet) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); midi.turnThruOff(); EXPECT_EQ(midi.getThruState(), false); @@ -86,7 +90,8 @@ TEST(MidiThru, setGet) TEST(MidiThru, off) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); midi.begin(MIDI_CHANNEL_OMNI); midi.turnThruOff(); @@ -106,7 +111,9 @@ TEST(MidiThru, off) TEST(MidiThru, full) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); @@ -148,7 +155,9 @@ TEST(MidiThru, full) TEST(MidiThru, sameChannel) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; midi.begin(12); @@ -177,7 +186,9 @@ TEST(MidiThru, sameChannel) TEST(MidiThru, sameChannelOmni) // Acts like full { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); @@ -219,7 +230,9 @@ TEST(MidiThru, sameChannelOmni) // Acts like full TEST(MidiThru, differentChannel) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; midi.begin(12); @@ -248,7 +261,9 @@ TEST(MidiThru, differentChannel) TEST(MidiThru, differentChannelOmni) // Acts like off { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); + Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); @@ -276,10 +291,12 @@ TEST(MidiThru, differentChannelOmni) // Acts like off TEST(MidiThru, multiByteThru) { typedef VariableSettings MultiByteParsing; - typedef midi::MidiInterface MultiByteMidiInterface; + typedef midi::MidiInterface MultiByteMidiInterface; SerialMock serial; - MultiByteMidiInterface midi(serial); + Transport transport(serial); + MultiByteMidiInterface midi((Transport&)transport); + Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); @@ -305,10 +322,12 @@ TEST(MidiThru, multiByteThru) TEST(MidiThru, withTxRunningStatus) { typedef VariableSettings Settings; - typedef midi::MidiInterface RsMidiInterface; + typedef midi::MidiInterface RsMidiInterface; SerialMock serial; - RsMidiInterface midi(serial); + Transport transport(serial); + RsMidiInterface midi((Transport&)transport); + Buffer buffer; midi.begin(MIDI_CHANNEL_OMNI); @@ -348,7 +367,8 @@ TEST(MidiThru, withTxRunningStatus) TEST(MidiThru, invalidMode) { SerialMock serial; - MidiInterface midi(serial); + Transport transport(serial); + MidiInterface midi((Transport&)transport); midi.begin(MIDI_CHANNEL_OMNI); midi.setThruFilterMode(midi::Thru::Mode(42)); diff --git a/test/unit-tests/tests/unit-tests_MidiUsbDefs.cpp b/test/unit-tests/tests/unit-tests_MidiUsbDefs.cpp deleted file mode 100644 index c5059eff..00000000 --- a/test/unit-tests/tests/unit-tests_MidiUsbDefs.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "unit-tests.h" -#include - -BEGIN_MIDI_NAMESPACE - -END_MIDI_NAMESPACE - -// ----------------------------------------------------------------------------- - -BEGIN_UNNAMED_NAMESPACE - -TEST(MidiUsbDefs, codeIndexNumberFromStatus) -{ - typedef midi::CodeIndexNumbers CIN; - EXPECT_EQ(CIN::fromStatus(midi::InvalidType), CIN::reserved); - EXPECT_EQ(CIN::fromStatus(midi::NoteOff), CIN::noteOff); - EXPECT_EQ(CIN::fromStatus(midi::NoteOn), CIN::noteOn); - EXPECT_EQ(CIN::fromStatus(midi::AfterTouchPoly), CIN::polyPressure); - EXPECT_EQ(CIN::fromStatus(midi::ControlChange), CIN::controlChange); - EXPECT_EQ(CIN::fromStatus(midi::ProgramChange), CIN::programChange); - EXPECT_EQ(CIN::fromStatus(midi::AfterTouchChannel), CIN::channelPressure); - EXPECT_EQ(CIN::fromStatus(midi::PitchBend), CIN::pitchBend); - EXPECT_EQ(CIN::fromStatus(midi::Clock), CIN::singleByte); - EXPECT_EQ(CIN::fromStatus(midi::Start), CIN::singleByte); - EXPECT_EQ(CIN::fromStatus(midi::Continue), CIN::singleByte); - EXPECT_EQ(CIN::fromStatus(midi::Stop), CIN::singleByte); - EXPECT_EQ(CIN::fromStatus(midi::ActiveSensing), CIN::singleByte); - EXPECT_EQ(CIN::fromStatus(midi::SystemReset), CIN::singleByte); - EXPECT_EQ(CIN::fromStatus(midi::SystemExclusive), CIN::sysExStart); - EXPECT_EQ(CIN::fromStatus(midi::TuneRequest), CIN::systemCommon1Byte); - EXPECT_EQ(CIN::fromStatus(midi::TimeCodeQuarterFrame), CIN::systemCommon2Bytes); - EXPECT_EQ(CIN::fromStatus(midi::SongSelect), CIN::systemCommon2Bytes); - EXPECT_EQ(CIN::fromStatus(midi::SongPosition), CIN::systemCommon3Bytes); -} - -TEST(MidiUsbDefs, codeIndexNumberSizes) -{ - typedef midi::CodeIndexNumbers CIN; - EXPECT_EQ(CIN::getSize(CIN::reserved), 0); - EXPECT_EQ(CIN::getSize(CIN::misc), 0); - EXPECT_EQ(CIN::getSize(CIN::cableEvent), 0); - EXPECT_EQ(CIN::getSize(CIN::systemCommon2Bytes), 2); - EXPECT_EQ(CIN::getSize(CIN::systemCommon3Bytes), 3); - EXPECT_EQ(CIN::getSize(CIN::sysExStart), 3); - EXPECT_EQ(CIN::getSize(CIN::sysExContinue), 3); - EXPECT_EQ(CIN::getSize(CIN::systemCommon1Byte), 1); - EXPECT_EQ(CIN::getSize(CIN::sysExEnds1Byte), 1); - EXPECT_EQ(CIN::getSize(CIN::sysExEnds2Bytes), 2); - EXPECT_EQ(CIN::getSize(CIN::sysExEnds3Bytes), 3); - EXPECT_EQ(CIN::getSize(CIN::noteOff), 3); - EXPECT_EQ(CIN::getSize(CIN::noteOn), 3); - EXPECT_EQ(CIN::getSize(CIN::polyPressure), 3); - EXPECT_EQ(CIN::getSize(CIN::controlChange), 3); - EXPECT_EQ(CIN::getSize(CIN::programChange), 2); - EXPECT_EQ(CIN::getSize(CIN::channelPressure), 2); - EXPECT_EQ(CIN::getSize(CIN::pitchBend), 3); - EXPECT_EQ(CIN::getSize(CIN::singleByte), 1); -} - -END_UNNAMED_NAMESPACE diff --git a/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp b/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp deleted file mode 100644 index e66cb659..00000000 --- a/test/unit-tests/tests/unit-tests_MidiUsbPacketInterface.cpp +++ /dev/null @@ -1,843 +0,0 @@ -#include "unit-tests.h" -#include -#include -#include - -BEGIN_MIDI_NAMESPACE - -END_MIDI_NAMESPACE - -// ----------------------------------------------------------------------------- - -BEGIN_UNNAMED_NAMESPACE - -using Buffer = midi::RingBuffer; -using CIN = midi::CodeIndexNumbers; - -TEST(MidiUsbPacketInterfaceTx, EmptyBufferShouldNotComposeAPacket) -{ - midiEventPacket_t packet; - Buffer buffer; - - const bool result = midi::composeTxPacket(buffer, packet); - EXPECT_FALSE(result) << "Empty buffer should not compose a packet"; -} - -TEST(MidiUsbPacketInterfaceTx, IncompleteDataShouldNotComposeAPacket) -{ - midiEventPacket_t packet; - Buffer buffer; - bool result = false; - - buffer.write(0x81); - result = midi::composeTxPacket(buffer, packet); - EXPECT_FALSE(result) << "Insufficient data should not compose a packet"; - EXPECT_EQ(1, buffer.getLength()) << "Partial data should not be read out of the buffer"; - - buffer.write(0x12); - buffer.write(0x42); - result = midi::composeTxPacket(buffer, packet); - EXPECT_TRUE(result) << "Complete data should compose a packet"; - EXPECT_EQ(0, buffer.getLength()) << "Complete packet data should be read out of the buffer"; - EXPECT_EQ(CIN::noteOff, packet.header); - EXPECT_EQ(0x81, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x42, packet.byte3); -} - -// Channel Voice Messages -- - -TEST(MidiUsbPacketInterfaceTx, NoteOff) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(0x80); - buffer.write(0x12); - buffer.write(0x42); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::noteOff, packet.header); - EXPECT_EQ(0x80, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x42, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, NoteOn) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(0x91); - buffer.write(0x12); - buffer.write(0x42); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::noteOn, packet.header); - EXPECT_EQ(0x91, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x42, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, PolyPressure) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(0xA2); - buffer.write(0x12); - buffer.write(0x42); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::polyPressure, packet.header); - EXPECT_EQ(0xA2, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x42, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, ControlChange) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(0xB3); - buffer.write(0x12); - buffer.write(0x42); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::controlChange, packet.header); - EXPECT_EQ(0xB3, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x42, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, ProgramChange) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(0xC4); - buffer.write(0x12); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::programChange, packet.header); - EXPECT_EQ(0xC4, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, ChannelPressure) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(0xD5); - buffer.write(0x12); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::channelPressure, packet.header); - EXPECT_EQ(0xD5, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, PitchBend) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(0xE6); - buffer.write(0x12); - buffer.write(0x42); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::pitchBend, packet.header); - EXPECT_EQ(0xE6, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x42, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -// System Real Time Messages -- - -TEST(MidiUsbPacketInterfaceTx, Clock) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::Clock); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::singleByte, packet.header); - EXPECT_EQ(midi::Clock, packet.byte1); - EXPECT_EQ(0x00, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, Start) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::Start); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::singleByte, packet.header); - EXPECT_EQ(midi::Start, packet.byte1); - EXPECT_EQ(0x00, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, Stop) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::Stop); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::singleByte, packet.header); - EXPECT_EQ(midi::Stop, packet.byte1); - EXPECT_EQ(0x00, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, Continue) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::Continue); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::singleByte, packet.header); - EXPECT_EQ(midi::Continue, packet.byte1); - EXPECT_EQ(0x00, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, ActiveSensing) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::ActiveSensing); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::singleByte, packet.header); - EXPECT_EQ(midi::ActiveSensing, packet.byte1); - EXPECT_EQ(0x00, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, SystemReset) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::SystemReset); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::singleByte, packet.header); - EXPECT_EQ(midi::SystemReset, packet.byte1); - EXPECT_EQ(0x00, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -// System Common Messages -- - -TEST(MidiUsbPacketInterfaceTx, TuneRequest) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::TuneRequest); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::systemCommon1Byte, packet.header); - EXPECT_EQ(midi::TuneRequest, packet.byte1); - EXPECT_EQ(0x00, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, SongSelect) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::SongSelect); - buffer.write(0x12); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::systemCommon2Bytes, packet.header); - EXPECT_EQ(midi::SongSelect, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, SongPosition) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::SongPosition); - buffer.write(0x12); - buffer.write(0x42); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::systemCommon3Bytes, packet.header); - EXPECT_EQ(midi::SongPosition, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x42, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, TimeCodeQuarterFrame) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(midi::TimeCodeQuarterFrame); - buffer.write(0x12); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::systemCommon2Bytes, packet.header); - EXPECT_EQ(midi::TimeCodeQuarterFrame, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -// System Exclusive -- - -TEST(MidiUsbPacketInterfaceTx, SysExNotEnoughData) -{ - midiEventPacket_t packet; - Buffer buffer; - - buffer.write(0x12); - buffer.write(0x42); - EXPECT_FALSE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(2, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, SysExSinglePacket) -{ - midiEventPacket_t packet; - Buffer buffer; - - // Two-byte SysEx (utterly useless) - buffer.write(0xf0); - buffer.write(0xf7); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExEnds2Bytes, packet.header); - EXPECT_EQ(0xf0, packet.byte1); - EXPECT_EQ(0xf7, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); - - // Single-data byte SysEx (non-spec conformant ?) - buffer.write(0xf0); - buffer.write(0x12); - buffer.write(0xf7); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExEnds3Bytes, packet.header); - EXPECT_EQ(0xf0, packet.byte1); - EXPECT_EQ(0x12, packet.byte2); - EXPECT_EQ(0xf7, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, SysExTwoPackets) -{ - midiEventPacket_t packet; - Buffer buffer; - - const byte deviceIdentityRequest[6] = { - 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7 - }; - - buffer.write(deviceIdentityRequest, 6); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExStart, packet.header); - EXPECT_EQ(0xf0, packet.byte1); - EXPECT_EQ(0x7e, packet.byte2); - EXPECT_EQ(0x7f, packet.byte3); - EXPECT_EQ(3, buffer.getLength()); - - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExEnds3Bytes, packet.header); - EXPECT_EQ(0x06, packet.byte1); - EXPECT_EQ(0x01, packet.byte2); - EXPECT_EQ(0xf7, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, SysExMultiplePacketsEndingWith1Byte) -{ - midiEventPacket_t packet; - Buffer buffer; - - const byte message[7] = { - 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0xf7 - }; - - buffer.write(message, 7); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExStart, packet.header); - EXPECT_EQ(0xf0, packet.byte1); - EXPECT_EQ(0x01, packet.byte2); - EXPECT_EQ(0x02, packet.byte3); - EXPECT_EQ(4, buffer.getLength()); - - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExContinue, packet.header); - EXPECT_EQ(0x03, packet.byte1); - EXPECT_EQ(0x04, packet.byte2); - EXPECT_EQ(0x05, packet.byte3); - EXPECT_EQ(1, buffer.getLength()); - - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExEnds1Byte, packet.header); - EXPECT_EQ(0xf7, packet.byte1); - EXPECT_EQ(0x00, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -TEST(MidiUsbPacketInterfaceTx, SysExMultiplePacketsEndingWith2Bytes) -{ - midiEventPacket_t packet; - Buffer buffer; - - const byte message[8] = { - 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xf7 - }; - - buffer.write(message, 8); - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExStart, packet.header); - EXPECT_EQ(0xf0, packet.byte1); - EXPECT_EQ(0x01, packet.byte2); - EXPECT_EQ(0x02, packet.byte3); - EXPECT_EQ(5, buffer.getLength()); - - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExContinue, packet.header); - EXPECT_EQ(0x03, packet.byte1); - EXPECT_EQ(0x04, packet.byte2); - EXPECT_EQ(0x05, packet.byte3); - EXPECT_EQ(2, buffer.getLength()); - - EXPECT_TRUE(midi::composeTxPacket(buffer, packet)); - EXPECT_EQ(CIN::sysExEnds2Bytes, packet.header); - EXPECT_EQ(0x06, packet.byte1); - EXPECT_EQ(0xf7, packet.byte2); - EXPECT_EQ(0x00, packet.byte3); - EXPECT_EQ(0, buffer.getLength()); -} - -// ============================================================================= - -TEST(MidiUsbPacketInterfaceRx, InvalidPacketIsNotSerialised) -{ - Buffer buffer; - midiEventPacket_t packet; - packet.header = CIN::reserved; - packet.byte1 = 0x01; - packet.byte2 = 0x02; - packet.byte3 = 0x03; - midi::serialiseRxPacket(packet, buffer); - EXPECT_TRUE(buffer.isEmpty()); -} - -TEST(MidiUsbPacketInterfaceRx, CableEventsAreNotSerialised) -{ - Buffer buffer; - midiEventPacket_t packet; - packet.header = CIN::cableEvent; - packet.byte1 = 0x01; - packet.byte2 = 0x02; - packet.byte3 = 0x03; - midi::serialiseRxPacket(packet, buffer); - EXPECT_TRUE(buffer.isEmpty()); -} - -// Channel Voice Messages -- - -TEST(MidiUsbPacketInterfaceRx, NoteOff) -{ - Buffer buffer; - midiEventPacket_t packet; - packet.header = CIN::noteOff; - packet.byte1 = 0x80; - packet.byte2 = 0x12; - packet.byte3 = 0x42; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0x80, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); - EXPECT_EQ(0x42, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, NoteOn) -{ - Buffer buffer; - midiEventPacket_t packet; - packet.header = CIN::noteOn; - packet.byte1 = 0x91; - packet.byte2 = 0x12; - packet.byte3 = 0x42; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0x91, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); - EXPECT_EQ(0x42, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, PolyPressure) -{ - Buffer buffer; - midiEventPacket_t packet; - packet.header = CIN::polyPressure; - packet.byte1 = 0xA2; - packet.byte2 = 0x12; - packet.byte3 = 0x42; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0xA2, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); - EXPECT_EQ(0x42, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, ControlChange) -{ - Buffer buffer; - midiEventPacket_t packet; - packet.header = CIN::controlChange; - packet.byte1 = 0xB3; - packet.byte2 = 0x12; - packet.byte3 = 0x42; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0xB3, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); - EXPECT_EQ(0x42, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, ProgramChange) -{ - Buffer buffer; - midiEventPacket_t packet; - packet.header = CIN::programChange; - packet.byte1 = 0xC4; - packet.byte2 = 0x12; - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(2, buffer.getLength()); - EXPECT_EQ(0xC4, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, ChannelPressure) -{ - Buffer buffer; - midiEventPacket_t packet; - packet.header = CIN::channelPressure; - packet.byte1 = 0xD5; - packet.byte2 = 0x12; - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(2, buffer.getLength()); - EXPECT_EQ(0xD5, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, PitchBend) -{ - Buffer buffer; - midiEventPacket_t packet; - packet.header = CIN::pitchBend; - packet.byte1 = 0xE6; - packet.byte2 = 0x12; - packet.byte3 = 0x42; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0xE6, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); - EXPECT_EQ(0x42, buffer.read()); -} - -// System Real Time Messages -- - -TEST(MidiUsbPacketInterfaceRx, Clock) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::singleByte; - packet.byte1 = midi::Clock; - packet.byte2 = 0x12; // Should be ignored - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(1, buffer.getLength()); - EXPECT_EQ(midi::Clock, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, Start) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::singleByte; - packet.byte1 = midi::Start; - packet.byte2 = 0x12; // Should be ignored - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(1, buffer.getLength()); - EXPECT_EQ(midi::Start, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, Stop) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::singleByte; - packet.byte1 = midi::Stop; - packet.byte2 = 0x12; // Should be ignored - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(1, buffer.getLength()); - EXPECT_EQ(midi::Stop, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, Continue) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::singleByte; - packet.byte1 = midi::Continue; - packet.byte2 = 0x12; // Should be ignored - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(1, buffer.getLength()); - EXPECT_EQ(midi::Continue, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, ActiveSensing) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::singleByte; - packet.byte1 = midi::ActiveSensing; - packet.byte2 = 0x12; // Should be ignored - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(1, buffer.getLength()); - EXPECT_EQ(midi::ActiveSensing, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, SystemReset) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::singleByte; - packet.byte1 = midi::SystemReset; - packet.byte2 = 0x12; // Should be ignored - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(1, buffer.getLength()); - EXPECT_EQ(midi::SystemReset, buffer.read()); -} - -// System Common Messages -- - -TEST(MidiUsbPacketInterfaceRx, TuneRequest) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::systemCommon1Byte; - packet.byte1 = midi::TuneRequest; - packet.byte2 = 0x12; // Should be ignored - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(1, buffer.getLength()); - EXPECT_EQ(midi::TuneRequest, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, SongSelect) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::systemCommon2Bytes; - packet.byte1 = midi::SongSelect; - packet.byte2 = 0x12; - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(2, buffer.getLength()); - EXPECT_EQ(midi::SongSelect, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, SongPosition) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::systemCommon2Bytes; - packet.byte1 = midi::SongPosition; - packet.byte2 = 0x12; - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(2, buffer.getLength()); - EXPECT_EQ(midi::SongPosition, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, TimeCodeQuarterFrame) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::systemCommon2Bytes; - packet.byte1 = midi::TimeCodeQuarterFrame; - packet.byte2 = 0x12; - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(2, buffer.getLength()); - EXPECT_EQ(midi::TimeCodeQuarterFrame, buffer.read()); - EXPECT_EQ(0x12, buffer.read()); -} - -// System Exclusive -- - -TEST(MidiUsbPacketInterfaceRx, SysExSinglePacket2Bytes) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::sysExEnds2Bytes; - packet.byte1 = 0xf0; - packet.byte2 = 0xf7; - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(2, buffer.getLength()); - EXPECT_EQ(0xf0, buffer.read()); - EXPECT_EQ(0xf7, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, SysExSinglePacket3Bytes) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::sysExEnds3Bytes; - packet.byte1 = 0xf0; - packet.byte2 = 0x42; - packet.byte3 = 0xf7; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0xf0, buffer.read()); - EXPECT_EQ(0x42, buffer.read()); - EXPECT_EQ(0xf7, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, SysExTwoPackets) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::sysExStart; - packet.byte1 = 0xf0; - packet.byte2 = 0x01; - packet.byte3 = 0x02; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0xf0, buffer.read()); - EXPECT_EQ(0x01, buffer.read()); - EXPECT_EQ(0x02, buffer.read()); - - packet.header = CIN::sysExEnds3Bytes; - packet.byte1 = 0x03; - packet.byte2 = 0x04; - packet.byte3 = 0xf7; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0x03, buffer.read()); - EXPECT_EQ(0x04, buffer.read()); - EXPECT_EQ(0xf7, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, SysExMultiplePacketsEndingWith1Byte) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::sysExStart; - packet.byte1 = 0xf0; - packet.byte2 = 0x01; - packet.byte3 = 0x02; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0xf0, buffer.read()); - EXPECT_EQ(0x01, buffer.read()); - EXPECT_EQ(0x02, buffer.read()); - - packet.header = CIN::sysExContinue; - packet.byte1 = 0x03; - packet.byte2 = 0x04; - packet.byte3 = 0x05; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0x03, buffer.read()); - EXPECT_EQ(0x04, buffer.read()); - EXPECT_EQ(0x05, buffer.read()); - - packet.header = CIN::sysExEnds1Byte; - packet.byte1 = 0xf7; - packet.byte2 = 0x12; // Should be ignored - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(1, buffer.getLength()); - EXPECT_EQ(0xf7, buffer.read()); -} - -TEST(MidiUsbPacketInterfaceRx, SysExMultiplePacketsEndingWith2Bytes) -{ - midiEventPacket_t packet; - Buffer buffer; - - packet.header = CIN::sysExStart; - packet.byte1 = 0xf0; - packet.byte2 = 0x01; - packet.byte3 = 0x02; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0xf0, buffer.read()); - EXPECT_EQ(0x01, buffer.read()); - EXPECT_EQ(0x02, buffer.read()); - - packet.header = CIN::sysExContinue; - packet.byte1 = 0x03; - packet.byte2 = 0x04; - packet.byte3 = 0x05; - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(3, buffer.getLength()); - EXPECT_EQ(0x03, buffer.read()); - EXPECT_EQ(0x04, buffer.read()); - EXPECT_EQ(0x05, buffer.read()); - - packet.header = CIN::sysExEnds2Bytes; - packet.byte1 = 0x06; - packet.byte2 = 0xf7; // Should be ignored - packet.byte3 = 0x42; // Should be ignored - midi::serialiseRxPacket(packet, buffer); - EXPECT_EQ(2, buffer.getLength()); - EXPECT_EQ(0x06, buffer.read()); - EXPECT_EQ(0xf7, buffer.read()); -} - -END_UNNAMED_NAMESPACE diff --git a/test/unit-tests/tests/unit-tests_RingBuffer.cpp b/test/unit-tests/tests/unit-tests_RingBuffer.cpp deleted file mode 100644 index 083deb76..00000000 --- a/test/unit-tests/tests/unit-tests_RingBuffer.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include "unit-tests.h" -#include - -BEGIN_UNNAMED_NAMESPACE -using namespace testing; -using Buffer = midi::RingBuffer; - -TEST(RingBuffer, writeScalar) -{ - Buffer buffer; - EXPECT_EQ(buffer.isEmpty(), true); - EXPECT_EQ(buffer.getLength(), 0); - buffer.write(42); - buffer.write(47); - EXPECT_EQ(buffer.isEmpty(), false); - EXPECT_EQ(buffer.getLength(), 2); -} - -TEST(RingBuffer, readScalar) -{ - Buffer buffer; - buffer.write(42); - EXPECT_EQ(buffer.getLength(), 1); - buffer.write(47); - EXPECT_EQ(buffer.getLength(), 2); - EXPECT_EQ(buffer.read(), 42); - EXPECT_EQ(buffer.getLength(), 1); - EXPECT_EQ(buffer.read(), 47); - EXPECT_EQ(buffer.isEmpty(), true); - EXPECT_EQ(buffer.getLength(), 0); -} - -TEST(RingBuffer, clear) -{ - Buffer buffer; - buffer.write(42); - buffer.write(47); - buffer.clear(); - EXPECT_EQ(buffer.isEmpty(), true); - EXPECT_EQ(buffer.getLength(), 0); -} - -TEST(RingBuffer, writeArray) -{ - Buffer buffer; - const uint8_t input[4] = { - 1, 2, 3, 4 - }; - buffer.write(input, 4); - EXPECT_EQ(buffer.isEmpty(), false); - EXPECT_EQ(buffer.getLength(), 4); -} - -TEST(RingBuffer, writeOverflow) -{ - Buffer buffer; - - buffer.write(1); - EXPECT_EQ(buffer.getLength(), 1); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(2); - EXPECT_EQ(buffer.getLength(), 2); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(3); - EXPECT_EQ(buffer.getLength(), 3); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(4); - EXPECT_EQ(buffer.getLength(), 4); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(5); - EXPECT_EQ(buffer.getLength(), 5); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(6); - EXPECT_EQ(buffer.getLength(), 6); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(7); - EXPECT_EQ(buffer.getLength(), 7); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(8); - EXPECT_EQ(buffer.getLength(), 8); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(9); - EXPECT_EQ(buffer.getLength(), 8); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(10); - EXPECT_EQ(buffer.getLength(), 8); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(11); - EXPECT_EQ(buffer.getLength(), 8); - EXPECT_EQ(buffer.isEmpty(), false); - buffer.write(12); - EXPECT_EQ(buffer.getLength(), 8); - EXPECT_EQ(buffer.isEmpty(), false); -} - -TEST(RingBuffer, readOverflow) -{ - Buffer buffer; - - buffer.write(1); - buffer.write(2); - buffer.write(3); - buffer.write(4); - buffer.write(5); - buffer.write(6); - buffer.write(7); - buffer.write(8); - buffer.write(9); - buffer.write(10); - buffer.write(11); - buffer.write(12); - EXPECT_EQ(buffer.read(), 5); - EXPECT_EQ(buffer.getLength(), 7); - EXPECT_EQ(buffer.read(), 6); - EXPECT_EQ(buffer.getLength(), 6); - EXPECT_EQ(buffer.read(), 7); - EXPECT_EQ(buffer.getLength(), 5); - EXPECT_EQ(buffer.read(), 8); - EXPECT_EQ(buffer.getLength(), 4); - EXPECT_EQ(buffer.read(), 9); - EXPECT_EQ(buffer.getLength(), 3); - EXPECT_EQ(buffer.read(), 10); - EXPECT_EQ(buffer.getLength(), 2); - EXPECT_EQ(buffer.read(), 11); - EXPECT_EQ(buffer.getLength(), 1); - EXPECT_EQ(buffer.read(), 12); - EXPECT_EQ(buffer.getLength(), 0); - EXPECT_EQ(buffer.read(), 0); - EXPECT_EQ(buffer.getLength(), 0); -} - -TEST(RingBuffer, writeArrayOverflow) -{ - Buffer buffer; - const uint8_t input[12] = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, - }; - buffer.write(input, 12); - EXPECT_EQ(buffer.isEmpty(), false); - EXPECT_EQ(buffer.getLength(), 8); // Max size -} - -TEST(RingBuffer, readArray) -{ - Buffer buffer; - const uint8_t input[4] = { - 1, 2, 3, 4 - }; - uint8_t output[4] = { 0 }; - buffer.write(input, 4); - buffer.read(output, 4); - EXPECT_THAT(output, ContainerEq(input)); -} - -TEST(RingBuffer, readArrayOverflow) -{ - Buffer buffer; - const uint8_t input[12] = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, - }; - const uint8_t expected[8] = { - 5, 6, 7, 8, 9, 10, 11, 12, - }; - uint8_t output[8] = { 0 }; - - buffer.write(input, 12); - buffer.read(output, 8); - EXPECT_THAT(output, ContainerEq(expected)); - EXPECT_EQ(buffer.isEmpty(), true); - EXPECT_EQ(buffer.getLength(), 0); -} - -TEST(RingBuffer, peek) -{ - Buffer buffer; - const uint8_t input[4] = { - 1, 2, 3, 4 - }; - - buffer.write(input, 4); - EXPECT_EQ(1, buffer.peek()); - EXPECT_EQ(2, buffer.peek(1)); - EXPECT_EQ(3, buffer.peek(2)); - EXPECT_EQ(4, buffer.peek(3)); - EXPECT_EQ(4, buffer.getLength()) << "Peek should not change buffer length"; -} - -TEST(RingBuffer, pop) -{ - Buffer buffer; - const uint8_t input[4] = { - 1, 2, 3, 4 - }; - - buffer.write(input, 4); - buffer.pop(); - EXPECT_EQ(3, buffer.getLength()); - buffer.pop(2); - EXPECT_EQ(1, buffer.getLength()); -} - -END_UNNAMED_NAMESPACE - diff --git a/test/unit-tests/tests/unit-tests_Settings.cpp b/test/unit-tests/tests/unit-tests_Settings.cpp index 72f44a1c..859db19d 100644 --- a/test/unit-tests/tests/unit-tests_Settings.cpp +++ b/test/unit-tests/tests/unit-tests_Settings.cpp @@ -5,7 +5,6 @@ BEGIN_MIDI_NAMESPACE const bool DefaultSettings::UseRunningStatus; const bool DefaultSettings::HandleNullVelocityNoteOnAsNoteOff; const bool DefaultSettings::Use1ByteParsing; -const long DefaultSettings::BaudRate; const unsigned DefaultSettings::SysExMaxSize; END_MIDI_NAMESPACE @@ -19,7 +18,6 @@ TEST(Settings, hasTheRightDefaultValues) EXPECT_EQ(midi::DefaultSettings::UseRunningStatus, false); EXPECT_EQ(midi::DefaultSettings::HandleNullVelocityNoteOnAsNoteOff, true); EXPECT_EQ(midi::DefaultSettings::Use1ByteParsing, true); - EXPECT_EQ(midi::DefaultSettings::BaudRate, 31250); EXPECT_EQ(midi::DefaultSettings::SysExMaxSize, unsigned(128)); } From 7a96fec0b5d2799840b6072fc2805754b6a87b0a Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 17 Apr 2020 08:15:01 +0200 Subject: [PATCH 31/48] chore: Update Google Test & remove USBMIDI --- .gitmodules | 3 --- external/google-test | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index c1c2a776..53d7ecda 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "external/google-test"] path = external/google-test url = https://github.com/google/googletest.git -[submodule "external/midi-usb"] - path = external/midi-usb - url = https://github.com/arduino-libraries/MIDIUSB.git diff --git a/external/google-test b/external/google-test index ecd53086..703bd9ca 160000 --- a/external/google-test +++ b/external/google-test @@ -1 +1 @@ -Subproject commit ecd530865cefdfa7dea58e84f6aa1b548950363d +Subproject commit 703bd9caab50b139428cea1aaff9974ebee5742e From a9664ef4a4f9176e41dc39c9892981397cff3a4f Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 17 Apr 2020 08:15:42 +0200 Subject: [PATCH 32/48] fix: Use SysEx size in Settings --- src/MIDI.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 2e64aa3a..71d4f767 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -1005,15 +1005,15 @@ bool MidiInterface::parse() if ((mPendingMessage[0] == SystemExclusiveStart) || (mPendingMessage[0] == SystemExclusiveEnd)) { - auto lastByte = mMessage.sysexArray[DefaultSettings::SysExMaxSize - 1]; - mMessage.sysexArray[DefaultSettings::SysExMaxSize - 1] = SystemExclusiveStart; + auto lastByte = mMessage.sysexArray[Settings::SysExMaxSize - 1]; + mMessage.sysexArray[Settings::SysExMaxSize - 1] = SystemExclusiveStart; mMessage.type = SystemExclusive; // Get length - mMessage.data1 = DefaultSettings::SysExMaxSize & 0xff; // LSB - mMessage.data2 = byte(DefaultSettings::SysExMaxSize >> 8); // MSB + mMessage.data1 = Settings::SysExMaxSize & 0xff; // LSB + mMessage.data2 = byte(Settings::SysExMaxSize >> 8); // MSB mMessage.channel = 0; - mMessage.length = DefaultSettings::SysExMaxSize; + mMessage.length = Settings::SysExMaxSize; mMessage.valid = true; // No need to check against the inputChannel, From f8707ed49abfe1092592a9915ae8c49190f7e0c1 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 17 Apr 2020 08:22:05 +0200 Subject: [PATCH 33/48] chore: Add all Teensy boards to CI --- .travis.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fb5ba21..9949f50e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,12 @@ os: - linux python: - - '2.7' + - "2.7" # Cache PlatformIO packages using Travis CI container-based infrastructure cache: directories: - - '~/.platformio' + - "~/.platformio" env: global: @@ -74,7 +74,21 @@ script: # Build current example - | if [ ! "${BUILD_UNIT_TESTS}" ]; then - platformio ci --lib="." --board=uno --board="due" --board="leonardo" --board="micro" --board="nanoatmega328" --board="megaatmega2560" + platformio ci --lib="." \ + --board="uno" \ + --board="due" \ + --board="zero" \ + --board="leonardo" \ + --board="micro" \ + --board="nanoatmega328" \ + --board="megaatmega2560" \ + --board="teensy2" \ + --board="teensy30" \ + --board="teensy31" \ + --board="teensy35" \ + --board="teensy36" \ + --board="teensy40" \ + --board="teensylc" fi after_success: From 23c53cafe1deaa6c41933341f217edfbca3efe10 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 17 Apr 2020 08:41:16 +0200 Subject: [PATCH 34/48] fix: Remove USB-based Teensys from CI --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9949f50e..e9d5e77e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -85,9 +85,6 @@ script: --board="teensy2" \ --board="teensy30" \ --board="teensy31" \ - --board="teensy35" \ - --board="teensy36" \ - --board="teensy40" \ --board="teensylc" fi From be85c7b3e020f02865c168520db8f99a92e69c36 Mon Sep 17 00:00:00 2001 From: lathoub Date: Fri, 17 Apr 2020 22:05:49 +0200 Subject: [PATCH 35/48] SerialUSB is of type Serial_, not HardwareSerial --- examples/DualMerger/DualMerger.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/DualMerger/DualMerger.ino b/examples/DualMerger/DualMerger.ino index c6c30e36..4e99ea1a 100644 --- a/examples/DualMerger/DualMerger.ino +++ b/examples/DualMerger/DualMerger.ino @@ -10,7 +10,7 @@ MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); #elif defined(ARDUINO_SAMD_ZERO) - MIDI_CREATE_INSTANCE(HardwareSerial, SerialUSB, midiA); + MIDI_CREATE_INSTANCE(Serial_, SerialUSB, midiA); MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); #elif defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) #include From b457f6db7c7d4fdecd3f1a7ce827defe251e8dce Mon Sep 17 00:00:00 2001 From: lathoub Date: Fri, 17 Apr 2020 22:09:06 +0200 Subject: [PATCH 36/48] added USB Migration and other Transport mechanisms --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index e75ba6a4..76cead1b 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,52 @@ void loop() 3. Read the [documentation](#documentation) or watch the awesome video tutorials from [Notes & Volts](https://www.youtube.com/playlist?list=PL4_gPbvyebyH2xfPXePHtx8gK5zPBrVkg). +## USB Migration + +All USB related code has been moved into a separate repository [Arduino-USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI), USB MIDI Device support with [`MIDIUSB`](https://github.com/arduino-libraries/MIDIUSB), still using this library to do all the MIDI heavy-lifting. + +Migration has been made as easy as possible: only the declaration of the MIDI object has been modified, the code remains identical. + +`4.3.1` code: + +```c++ + #include + #include + + static const unsigned sUsbTransportBufferSize = 16; + typedef midi::UsbTransport UsbTransport; + + UsbTransport sUsbTransport; + + MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); + ... +``` + +become in `5.0.0` + +```c++ + #include + USBMIDI_CREATE_DEFAULT_INSTANCE(); + ... +``` + +Start with the [NoteOnOffEverySec](https://github.com/lathoub/Arduino-USBMIDI/blob/master/examples/NoteOnOffEverySec/NoteOnOffEverySec.ino) example that is based on the original MidiUSB [sketch](https://github.com/lathoub/arduino_midi_library/blob/master/examples/MidiUSB/MidiUSB.ino). Note the only difference is in the declaration. + +The [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) Arduino library depends on [this library](https://github.com/FortySevenEffects/arduino_midi_library) and the [MIDIUSB](https://github.com/arduino-libraries/MIDIUSB) library. + +[USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) uses the latest Arduino IDE `depends` feature in the `library.properties` file installing all the dependencies automatically when installing from the IDE. + +## Other Transportation mechanisms + +Version 5 of this library, allows for other Transportation layers than the original MIDI 1.0 Electrical Specification. + + - [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) + - [AppleMIDI or rtpMIDI](https://github.com/lathoub/Arduino-AppleMIDI-Library) + - [ipMIDI](https://github.com/lathoub/Arduino-ipMIDI) + - [BLE-MIDI](https://github.com/lathoub/Arduino-BLE-MIDI) + +All these Transportation layers use this library for all the underlying MIDI work, making it easy to switch transport protocols or making transport protocol bridges. + ## Documentation - [Doxygen Extended Documentation](https://fortyseveneffects.github.io/arduino_midi_library/). From 9d43a8370e19ac38dfdad05ca256ddc452e85957 Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Sun, 19 Apr 2020 21:37:39 +0200 Subject: [PATCH 37/48] added AltPinSerial example --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e9d5e77e..ebb49363 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ env: - PLATFORMIO_CI_SRC=examples/Input - PLATFORMIO_CI_SRC=examples/RPN_NRPN - PLATFORMIO_CI_SRC=examples/SimpleSynth + - PLATFORMIO_CI_SRC=examples/AltPinSerial addons: apt: From 1e14162bf44f120f6841c9c3676eb503988fbb80 Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Mon, 20 Apr 2020 13:08:49 +0200 Subject: [PATCH 38/48] rewrote AltPinSerial to use SoftwareSerial --- examples/AltPinSerial/AltPinSerial.ino | 29 ++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index 311f6449..7e5e90ae 100644 --- a/examples/AltPinSerial/AltPinSerial.ino +++ b/examples/AltPinSerial/AltPinSerial.ino @@ -1,27 +1,30 @@ +#include #include -#include "altPinSerialMIDI.h" // Simple tutorial on how to receive and send MIDI messages. // Here, when receiving any message on channel 4, the Arduino // will blink a led and play back a note for 1 second. -AltSerialMIDI serialMIDI(Serial1, 18, 19); -MIDI_NAMESPACE::MidiInterface> MIDI((AltSerialMIDI&)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); void setup() { - pinMode(LED_BUILTIN, OUTPUT); - MIDI.begin(4); // Launch MIDI and listen to channel 4 + pinMode(LED_BUILTIN, OUTPUT); + MIDI.begin(4); // Launch MIDI and listen to channel 4 } 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); - } + 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); + } } From c5330945ee3c5b6409108998a44522ca82016def Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Mon, 20 Apr 2020 13:09:01 +0200 Subject: [PATCH 39/48] Delete altPinSerialMIDI.h --- examples/AltPinSerial/altPinSerialMIDI.h | 54 ------------------------ 1 file changed, 54 deletions(-) delete mode 100644 examples/AltPinSerial/altPinSerialMIDI.h diff --git a/examples/AltPinSerial/altPinSerialMIDI.h b/examples/AltPinSerial/altPinSerialMIDI.h deleted file mode 100644 index 0d684060..00000000 --- a/examples/AltPinSerial/altPinSerialMIDI.h +++ /dev/null @@ -1,54 +0,0 @@ -struct DefaultAltSerialSettings -{ - static const long BaudRate = 31250; -}; - -template -class AltSerialMIDI -{ - typedef _Settings Settings; - - uint8_t rxPin = 0; - uint8_t txPin = 0; - - friend class midi::MidiInterface>; - -public: - AltSerialMIDI(SerialPort& inSerial, uint8_t inRxPin, uint8_t inTxPin) - : mSerial(inSerial), rxPin(inRxPin), txPin(inTxPin) - { - }; - -protected: - void begin() - { - mSerial.begin(Settings::BaudRate, SERIAL_8N1, rxPin, txPin); - } - - bool beginTransmission(midi::MidiType) - { - return true; - }; - - void write(byte value) - { - mSerial.write(value); - }; - - void endTransmission() - { - }; - - byte read() - { - return mSerial.read(); - }; - - unsigned available() - { - return mSerial.available(); - }; - -private: - SerialPort& mSerial; -}; From e06e3677f06f6050a60693070be7fad01b293b59 Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Mon, 20 Apr 2020 13:09:39 +0200 Subject: [PATCH 40/48] bug fix: HardwareSerial => SoftwareSerial --- examples/AltPinSerial/AltPinSerial.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index 7e5e90ae..3e06dcce 100644 --- a/examples/AltPinSerial/AltPinSerial.ino +++ b/examples/AltPinSerial/AltPinSerial.ino @@ -9,7 +9,7 @@ int rxPin = 18; int txPin = 19; SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin); MIDI_NAMESPACE::SerialMIDI serialMIDI(mySerial); -MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); +MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); void setup() { From 05487c5415d1d8787d47f27f723fe0a48a89a6ea Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Mon, 20 Apr 2020 14:11:58 +0200 Subject: [PATCH 41/48] Example not relevant for Due and ZERO as they lack support for SoftwareSerial --- examples/AltPinSerial/AltPinSerial.ino | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/AltPinSerial/AltPinSerial.ino b/examples/AltPinSerial/AltPinSerial.ino index 3e06dcce..386fbcaa 100644 --- a/examples/AltPinSerial/AltPinSerial.ino +++ b/examples/AltPinSerial/AltPinSerial.ino @@ -1,15 +1,20 @@ -#include #include // Simple tutorial on how to receive and send MIDI messages. // Here, when receiving any message on channel 4, the Arduino // will blink a led and play back a note for 1 second. -int rxPin = 18; -int txPin = 19; -SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin); -MIDI_NAMESPACE::SerialMIDI serialMIDI(mySerial); -MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); +#if defined(ARDUINO_SAM_DUE) || defined(SAMD_SERIES) + /* example not relevant for this hardware */ + MIDI_CREATE_DEFAULT_INSTANCE(); +#else + #include + int rxPin = 18; + int txPin = 19; + SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin); + MIDI_NAMESPACE::SerialMIDI serialMIDI(mySerial); + MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); +#endif void setup() { From 45fb2df88f143af361560d92ee2cf1fcde2512e8 Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Mon, 20 Apr 2020 14:17:43 +0200 Subject: [PATCH 42/48] listen on channel 1 by default Listening to all channels by default breaks backward compatibility --- src/MIDI.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MIDI.h b/src/MIDI.h index ac809d88..0ebb52b6 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -62,7 +62,7 @@ class MidiInterface inline ~MidiInterface(); public: - void begin(Channel inChannel = MIDI_CHANNEL_OMNI); + void begin(Channel inChannel = 1); // ------------------------------------------------------------------------- // MIDI Output From c1d2f476b8c54db91b6c5cb1dd256a8945cc57ea Mon Sep 17 00:00:00 2001 From: lathoub <4082369+lathoub@users.noreply.github.com> Date: Mon, 20 Apr 2020 14:31:51 +0200 Subject: [PATCH 43/48] Update unit-tests_MidiInput.cpp --- test/unit-tests/tests/unit-tests_MidiInput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit-tests/tests/unit-tests_MidiInput.cpp b/test/unit-tests/tests/unit-tests_MidiInput.cpp index bf63e0cf..df023761 100644 --- a/test/unit-tests/tests/unit-tests_MidiInput.cpp +++ b/test/unit-tests/tests/unit-tests_MidiInput.cpp @@ -99,7 +99,7 @@ TEST(MidiInput, begin) // Default channel midi.begin(); EXPECT_EQ(serial.mBaudrate, 31250); - EXPECT_EQ(midi.getInputChannel(), 0); + EXPECT_EQ(midi.getInputChannel(), 1); // Specific channel midi.begin(12); From c2d17ec4293405e34dcc13b7759aeb623cedece9 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Mon, 20 Apr 2020 15:22:18 +0200 Subject: [PATCH 44/48] chore: Update lcov to latest available version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ebb49363..faa2c9b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ install: # 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.12.orig.tar.gz --output-document="$LCOV_ROOT/lcov.tar.gz" + 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 From 2d6bdd6dc2f7bcfb7898bb103cd2b2c528f0789f Mon Sep 17 00:00:00 2001 From: Francois Best Date: Mon, 20 Apr 2020 15:22:41 +0200 Subject: [PATCH 45/48] doc: Move Documentation section up --- README.md | 61 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 76cead1b..45d908a0 100644 --- a/README.md +++ b/README.md @@ -50,33 +50,40 @@ void loop() 3. Read the [documentation](#documentation) or watch the awesome video tutorials from [Notes & Volts](https://www.youtube.com/playlist?list=PL4_gPbvyebyH2xfPXePHtx8gK5zPBrVkg). -## USB Migration +## Documentation + +- [Doxygen Extended Documentation](https://fortyseveneffects.github.io/arduino_midi_library/). +- [GitHub wiki](https://github.com/FortySevenEffects/arduino_midi_library/wiki). + +## USB Migration (4.x to 5.x) All USB related code has been moved into a separate repository [Arduino-USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI), USB MIDI Device support with [`MIDIUSB`](https://github.com/arduino-libraries/MIDIUSB), still using this library to do all the MIDI heavy-lifting. -Migration has been made as easy as possible: only the declaration of the MIDI object has been modified, the code remains identical. +Migration has been made as easy as possible: only the declaration of the MIDI object has been modified, the rest of your code remains identical. `4.3.1` code: ```c++ - #include - #include - - static const unsigned sUsbTransportBufferSize = 16; - typedef midi::UsbTransport UsbTransport; - - UsbTransport sUsbTransport; - - MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); - ... +#include +#include + +static const unsigned sUsbTransportBufferSize = 16; +typedef midi::UsbTransport UsbTransport; + +UsbTransport sUsbTransport; + +MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); + +// ... ``` -become in `5.0.0` +now becomes in `5.0.0`: ```c++ - #include - USBMIDI_CREATE_DEFAULT_INSTANCE(); - ... +#include +USBMIDI_CREATE_DEFAULT_INSTANCE(); + +// ... ``` Start with the [NoteOnOffEverySec](https://github.com/lathoub/Arduino-USBMIDI/blob/master/examples/NoteOnOffEverySec/NoteOnOffEverySec.ino) example that is based on the original MidiUSB [sketch](https://github.com/lathoub/arduino_midi_library/blob/master/examples/MidiUSB/MidiUSB.ino). Note the only difference is in the declaration. @@ -85,21 +92,19 @@ The [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) Arduino library depen [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) uses the latest Arduino IDE `depends` feature in the `library.properties` file installing all the dependencies automatically when installing from the IDE. -## Other Transportation mechanisms - -Version 5 of this library, allows for other Transportation layers than the original MIDI 1.0 Electrical Specification. +## Other Transport mechanisms - - [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) - - [AppleMIDI or rtpMIDI](https://github.com/lathoub/Arduino-AppleMIDI-Library) - - [ipMIDI](https://github.com/lathoub/Arduino-ipMIDI) - - [BLE-MIDI](https://github.com/lathoub/Arduino-BLE-MIDI) +Version 5 of this library, allows for other Transport layers than the +original MIDI 1.0 Electrical Specification (hardware serial). -All these Transportation layers use this library for all the underlying MIDI work, making it easy to switch transport protocols or making transport protocol bridges. +- [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) +- [AppleMIDI or rtpMIDI](https://github.com/lathoub/Arduino-AppleMIDI-Library) +- [ipMIDI](https://github.com/lathoub/Arduino-ipMIDI) +- [BLE-MIDI](https://github.com/lathoub/Arduino-BLE-MIDI) -## Documentation - -- [Doxygen Extended Documentation](https://fortyseveneffects.github.io/arduino_midi_library/). -- [GitHub wiki](https://github.com/FortySevenEffects/arduino_midi_library/wiki). +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. ## Contact From ae49d3037de4fbc04b2acc7544841f2f3d546bfc Mon Sep 17 00:00:00 2001 From: Francois Best Date: Mon, 20 Apr 2020 15:31:26 +0200 Subject: [PATCH 46/48] chore: Ignore external coverage --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index faa2c9b5..be12ab02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -94,6 +94,7 @@ after_success: if [ "${GENERATE_COVERAGE}" ]; then # Generate code coverage information & send to Coveralls lcov --gcov-tool $GCOV --directory . --capture --output-file coverage.info + rm -rf ./external/**/*.gcda 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 From b384278b29859582a453259d59f134a9cb3b1a82 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Mon, 20 Apr 2020 15:36:12 +0200 Subject: [PATCH 47/48] chore: Ignore edge cases for coverage --- src/MIDI.hpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 71d4f767..81402dc2 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -107,7 +107,7 @@ void MidiInterface::begin(Channel inChannel) /*! \brief Send a MIDI message. \param inMessage The message - + This method is used when you want to send a Message that has not been constructed by the library, but by an external source. This method does *not* check against any of the constraints. @@ -119,12 +119,12 @@ void MidiInterface::send(const MidiMessage& inMes { if (!inMessage.valid) return; - + if (mTransport.beginTransmission(inMessage.type)) { const StatusByte status = getStatus(inMessage.type, inMessage.channel); mTransport.write(status); - + if (inMessage.type != MidiType::SystemExclusive) { if (inMessage.length > 1) mTransport.write(inMessage.data1); @@ -726,7 +726,7 @@ inline bool MidiInterface::read(Channel inChannel sendActiveSensing(); mLastMessageSentTime = Platform::now(); } - + if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated && (mLastMessageReceivedTime + ActiveSensingTimeout < Platform::now())) { mReceiverActiveSensingActivated = false; @@ -742,7 +742,7 @@ inline bool MidiInterface::read(Channel inChannel if (!parse()) return false; - + #ifndef RegionActiveSending if (Settings::UseReceiverActiveSensing && mMessage.type == ActiveSensing) @@ -881,13 +881,13 @@ bool MidiInterface::parse() mRunningStatus_RX = InvalidType; mMessage.sysexArray[0] = pendingType; break; - + case InvalidType: default: // This is obviously wrong. Let's get the hell out'a here. mLastError |= 1UL << ErrorParse; // set the ErrorParse bit if (mErrorCallback) - mErrorCallback(mLastError); + mErrorCallback(mLastError); // LCOV_EXCL_LINE resetInput(); return false; @@ -902,11 +902,11 @@ bool MidiInterface::parse() mMessage.data1 = mPendingMessage[1]; mMessage.data2 = 0; // Completed new message has 1 data byte mMessage.length = 1; - + mPendingMessageIndex = 0; mPendingMessageExpectedLength = 0; mMessage.valid = true; - + return true; } else @@ -947,7 +947,7 @@ bool MidiInterface::parse() mMessage.channel = 0; mMessage.length = 1; mMessage.valid = true; - + return true; // Exclusive @@ -968,7 +968,7 @@ bool MidiInterface::parse() mMessage.valid = true; resetInput(); - + return true; } else @@ -976,7 +976,7 @@ bool MidiInterface::parse() // Well well well.. error. mLastError |= 1UL << ErrorParse; // set the error bits if (mErrorCallback) - mErrorCallback(mLastError); + mErrorCallback(mLastError); // LCOV_EXCL_LINE resetInput(); return false; @@ -1022,9 +1022,9 @@ bool MidiInterface::parse() mMessage.sysexArray[0] = SystemExclusiveEnd; mMessage.sysexArray[1] = lastByte; - + mPendingMessageIndex = 2; - + return false; } @@ -1037,9 +1037,9 @@ bool MidiInterface::parse() mMessage.data1 = mPendingMessage[1]; // Save data2 only if applicable - mMessage.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0; + mMessage.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0; mMessage.length = mPendingMessageExpectedLength; - + // Reset local variables mPendingMessageIndex = 0; mPendingMessageExpectedLength = 0; @@ -1224,7 +1224,7 @@ MidiType MidiInterface::getTypeFromStatusByte(byt (inStatus == Undefined_F5) || (inStatus == Undefined_FD)) return InvalidType; // Data bytes and undefined. - + if (inStatus < 0xf0) // Channel message, remove channel nibble. return MidiType(inStatus & 0xf0); From fc360d60b3cfda74dcab69a507fd8115695a44d6 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Mon, 20 Apr 2020 16:47:36 +0200 Subject: [PATCH 48/48] chore: Remove Google test coverage --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index be12ab02..de7c28d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -93,8 +93,8 @@ 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 - rm -rf ./external/**/*.gcda 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