From 324d0404f52ee7c215b5329aeb3fdc2aab45782d Mon Sep 17 00:00:00 2001 From: lathoub Date: Mon, 2 Mar 2020 22:14:42 +0100 Subject: [PATCH] initial upload using feat/2.0.0 (not working) --- .development | 9 + examples/MidiBle/MidiBle.ino | 42 +- src/{utility => }/midi_bleDefs.h | 0 src/{utility => }/midi_bleNamespace.h | 0 src/midi_bleSettings.h | 3 + src/midi_bleTransport.h | 8 +- src/utility/Deque.h | 215 +++ src/utility/Logging.h | 71 + src/utility/endian.h | 139 ++ src/utility/midi_bleSettings.h | 29 - src/utility/midi_feat4_4_0/MIDI.cpp | 115 ++ src/utility/midi_feat4_4_0/MIDI.h | 266 +++ src/utility/midi_feat4_4_0/MIDI.hpp | 1428 +++++++++++++++++ src/utility/midi_feat4_4_0/midi_Defs.h | 209 +++ src/utility/midi_feat4_4_0/midi_Message.h | 101 ++ src/utility/midi_feat4_4_0/midi_Namespace.h | 38 + src/utility/midi_feat4_4_0/midi_Settings.h | 87 + test/Arduino.h | 90 ++ test/Ethernet.h | 403 +++++ test/IPAddress.h | 12 + test/NoteOn.cpp | 12 + test/TestParser.sln | 31 + test/TestParser.vcxproj | 138 ++ test/TestParser.vcxproj.filters | 26 + test/TestParser.vcxproj.user | 4 + test/bleMidi.xcodeproj/project.pbxproj | 278 ++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../IDEFindNavigatorScopes.plist | 5 + .../UserInterfaceState.xcuserstate | Bin 0 -> 207649 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 24 + .../xcschemes/xcschememanagement.plist | 14 + 32 files changed, 3756 insertions(+), 56 deletions(-) create mode 100644 .development rename src/{utility => }/midi_bleDefs.h (100%) rename src/{utility => }/midi_bleNamespace.h (100%) create mode 100755 src/midi_bleSettings.h create mode 100644 src/utility/Deque.h create mode 100644 src/utility/Logging.h create mode 100644 src/utility/endian.h delete mode 100755 src/utility/midi_bleSettings.h create mode 100644 src/utility/midi_feat4_4_0/MIDI.cpp create mode 100644 src/utility/midi_feat4_4_0/MIDI.h create mode 100644 src/utility/midi_feat4_4_0/MIDI.hpp create mode 100644 src/utility/midi_feat4_4_0/midi_Defs.h create mode 100644 src/utility/midi_feat4_4_0/midi_Message.h create mode 100644 src/utility/midi_feat4_4_0/midi_Namespace.h create mode 100644 src/utility/midi_feat4_4_0/midi_Settings.h create mode 100644 test/Arduino.h create mode 100644 test/Ethernet.h create mode 100644 test/IPAddress.h create mode 100644 test/NoteOn.cpp create mode 100644 test/TestParser.sln create mode 100644 test/TestParser.vcxproj create mode 100644 test/TestParser.vcxproj.filters create mode 100644 test/TestParser.vcxproj.user create mode 100644 test/bleMidi.xcodeproj/project.pbxproj create mode 100644 test/bleMidi.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 test/bleMidi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 test/bleMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/IDEFindNavigatorScopes.plist create mode 100644 test/bleMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 test/bleMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 test/bleMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/.development b/.development new file mode 100644 index 0000000..f7f5e81 --- /dev/null +++ b/.development @@ -0,0 +1,9 @@ +.DS_Store +examples/.DS_Store +src/.DS_Store +test/.vs +test/Debug +examples/ESP32_NoteOnOffEverySec/config.h +src/.vscode +test/x64 +.development diff --git a/examples/MidiBle/MidiBle.ino b/examples/MidiBle/MidiBle.ino index d5b72d0..2e3e6a1 100644 --- a/examples/MidiBle/MidiBle.ino +++ b/examples/MidiBle/MidiBle.ino @@ -1,5 +1,4 @@ -#include - +#define DEBUG 4 #include #include @@ -16,10 +15,7 @@ bool isConnected = false; void setup() { // Serial communications and wait for port to open: - Serial.begin(115200); - while (!Serial); - - Serial.println(F("booting")); + DEBUG_BEGIN(115200); MIDI.begin("Huzzah BLE MIDI", 1); @@ -29,7 +25,7 @@ void setup() MIDI.setHandleNoteOn(OnBleMidiNoteOn); MIDI.setHandleNoteOff(OnBleMidiNoteOff); - Serial.println(F("ready")); + N_DEBUG_PRINTLN(F("Ready")); } // ----------------------------------------------------------------------------- @@ -57,7 +53,7 @@ void loop() // rtpMIDI session. Device connected // ----------------------------------------------------------------------------- void OnBleMidiConnected() { - Serial.println(F("Connected")); + N_DEBUG_PRINTLN(F("Connected")); isConnected = true; } @@ -73,13 +69,13 @@ void OnBleMidiDisconnected() { // received note on // ----------------------------------------------------------------------------- void OnBleMidiNoteOn(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOn from channel:")); - Serial.print(channel); - Serial.print(F(" note:")); - Serial.print(note); - Serial.print(F(" velocity:")); - Serial.print(velocity); - Serial.println(); + N_DEBUG_PRINT(F("Incoming NoteOn from channel:")); + N_DEBUG_PRINT(channel); + N_DEBUG_PRINT(F(" note:")); + N_DEBUG_PRINT(note); + N_DEBUG_PRINT(F(" velocity:")); + N_DEBUG_PRINT(velocity); + N_DEBUG_PRINTLN(); } @@ -87,11 +83,11 @@ void OnBleMidiNoteOn(byte channel, byte note, byte velocity) { // received note off // ----------------------------------------------------------------------------- void OnBleMidiNoteOff(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOff from channel:")); - Serial.print(channel); - Serial.print(F(" note:")); - Serial.print(note); - Serial.print(F(" velocity:")); - Serial.print(velocity); - Serial.println(); -} \ No newline at end of file + N_DEBUG_PRINT(F("Incoming NoteOff from channel:")); + N_DEBUG_PRINT(channel); + N_DEBUG_PRINT(F(" note:")); + N_DEBUG_PRINT(note); + N_DEBUG_PRINT(F(" velocity:")); + N_DEBUG_PRINT(velocity); + N_DEBUG_PRINTLN(); +} diff --git a/src/utility/midi_bleDefs.h b/src/midi_bleDefs.h similarity index 100% rename from src/utility/midi_bleDefs.h rename to src/midi_bleDefs.h diff --git a/src/utility/midi_bleNamespace.h b/src/midi_bleNamespace.h similarity index 100% rename from src/utility/midi_bleNamespace.h rename to src/midi_bleNamespace.h diff --git a/src/midi_bleSettings.h b/src/midi_bleSettings.h new file mode 100755 index 0000000..8771a06 --- /dev/null +++ b/src/midi_bleSettings.h @@ -0,0 +1,3 @@ +#pragma once + +#include "midi_bleNamespace.h" diff --git a/src/midi_bleTransport.h b/src/midi_bleTransport.h index c2a141b..070ed9c 100755 --- a/src/midi_bleTransport.h +++ b/src/midi_bleTransport.h @@ -4,10 +4,10 @@ #pragma once -#include "utility/midi_bleSettings.h" -#include "utility/midi_bleDefs.h" +#include "midi_bleSettings.h" +#include "midi_bleDefs.h" -#include +#include "utility/Deque.h" BEGIN_BLEMIDI_NAMESPACE @@ -15,7 +15,7 @@ template class BleMidiTransport { private: - midi::RingBuffer mRxBuffer; + Deque mRxBuffer; byte mTxBuffer[44]; unsigned mTxIndex = 0; diff --git a/src/utility/Deque.h b/src/utility/Deque.h new file mode 100644 index 0000000..a1676a5 --- /dev/null +++ b/src/utility/Deque.h @@ -0,0 +1,215 @@ +#pragma once + +template +class Deque { +// class iterator; + +private: + int _head, _tail; + T _data[Size]; + +public: + Deque() + { + clear(); + }; + + size_t free(); + const size_t size() const; + const size_t max_size() const; + T & front(); + const T & front() const; + T & back(); + const T & back() const; + void push_front(const T &); + void push_back(const T &); + T pop_front(); + T pop_back(); + + T& operator[](size_t); + const T& operator[](size_t) const; + T& at(size_t); + const T& at(size_t) const; + + void clear(); + +// iterator begin(); +// iterator end(); + + void erase(size_t); + void erase(size_t, size_t); + + bool empty() const { + return size() == 0; + } + bool full() const { + return (size() == Size); + } +}; + +template +size_t Deque::free() +{ + return Size - size(); +} + +template +const size_t Deque::size() const +{ + if (_tail < 0) + return 0; // empty + else if (_head > _tail) + return _head - _tail; + else + return Size - _tail + _head; +} + +template +const size_t Deque::max_size() const +{ + return Size; +} + +template +T & Deque::front() +{ + return _data[_tail]; +} + +template +const T & Deque::front() const +{ + return _data[_tail]; +} + +template +T & Deque::back() +{ + int idx = _head - 1; + if (idx < 0) idx = Size - 1; + return _data[idx]; +} + +template +const T & Deque::back() const +{ + int idx = _head - 1; + if (idx < 0) idx = Size - 1; + return _data[idx]; +} + +template +void Deque::push_front(const T &value) +{ + //if container is full, do nothing. + if (free()){ + if (--_tail < 0) + _tail = Size - 1; + _data[_tail] = value; + } +} + +template +void Deque::push_back(const T &value) +{ + //if container is full, do nothing. + if (free()){ + _data[_head] = value; + if (empty()) + _tail = _head; + if (++_head >= Size) + _head %= Size; + } +} + +template +T Deque::pop_front() { + if (empty()) // if empty, do nothing. + return T(); + auto item = front(); + if (++_tail >= Size) + _tail %= Size; + if (_tail == _head) + clear(); + return item; +} + +template +T Deque::pop_back() { + if (empty()) // if empty, do nothing. + return T(); + auto item = front(); + if (--_head < 0) + _head = Size - 1; + if (_head == _tail) //now buffer is empty + clear(); + return item; +} + +template +void Deque::erase(size_t position) { + if (position >= size()) // out-of-range! + return; // do nothing. + for (size_t i = position; i < size() - 1; i++){ + at(i) = at(i + 1); + } + pop_back(); +} + +template +void Deque::erase(size_t first, size_t last) { + if (first > last // invalid arguments + || first >= size()) // out-of-range + return; //do nothing. + + size_t tgt = first; + for (size_t i = last + 1; i < size(); i++){ + at(tgt++) = at(i); + } + for (size_t i = first; i <= last; i++){ + pop_back(); + } +} + +template +T& Deque::operator[](size_t index) +{ + auto i = _tail + index; + if (i >= Size) + i %= Size; + return _data[i]; +} + +template +const T& Deque::operator[](size_t index) const +{ + auto i = _tail + index; + if (i >= Size) + i %= Size; + return _data[i]; +} + +template +T& Deque::at(size_t index) +{ + auto i = _tail + index; + if (i >= Size) + i %= Size; + return _data[i]; +} + +template +const T& Deque::at(size_t index) const +{ + auto i = _tail + index; + if (i >= Size) + i %= Size; + return _data[i]; +} + +template +void Deque::clear() +{ + _tail = -1; + _head = 0; +} diff --git a/src/utility/Logging.h b/src/utility/Logging.h new file mode 100644 index 0000000..4f2ed3b --- /dev/null +++ b/src/utility/Logging.h @@ -0,0 +1,71 @@ +#pragma once + +#ifndef DEBUGSTREAM +#define DEBUGSTREAM Serial +#endif + +#define LOG_LEVEL_NONE 0 +#define LOG_LEVEL_FATAL 1 +#define LOG_LEVEL_ERROR 2 +#define LOG_LEVEL_WARNING 3 +#define LOG_LEVEL_NOTICE 4 +#define LOG_LEVEL_TRACE 5 +#define LOG_LEVEL_VERBOSE 6 + +#ifndef DEBUG +#define DEBUG LOG_LEVEL_NONE +#endif + +#if DEBUG > LOG_LEVEL_NONE +#define DEBUG_BEGIN(SPEED) \ + DEBUGSTREAM.begin(SPEED); \ + while (!DEBUGSTREAM) \ + ; \ + DEBUGSTREAM.println(F("Booting...")); +#define F_DEBUG_PRINT(...) DEBUGSTREAM.print(__VA_ARGS__) +#define F_DEBUG_PRINTLN(...) DEBUGSTREAM.println(__VA_ARGS__) +#else +#define DEBUG_BEGIN(SPEED) +#define F_DEBUG_PRINT(...) +#define F_DEBUG_PRINTLN(...) +#endif + +#if DEBUG >= LOG_LEVEL_ERROR +#define E_DEBUG_PRINT(...) DEBUGSTREAM.print(__VA_ARGS__) +#define E_DEBUG_PRINTLN(...) DEBUGSTREAM.println(__VA_ARGS__) +#else +#define E_DEBUG_PRINT(...) +#define E_DEBUG_PRINTLN(...) +#endif + +#if DEBUG >= LOG_LEVEL_WARNING +#define W_DEBUG_PRINT(...) DEBUGSTREAM.print(__VA_ARGS__) +#define W_DEBUG_PRINTLN(...) DEBUGSTREAM.println(__VA_ARGS__) +#else +#define W_DEBUG_PRINT(...) +#define W_DEBUG_PRINTLN(...) +#endif + +#if DEBUG >= LOG_LEVEL_NOTICE +#define N_DEBUG_PRINT(...) DEBUGSTREAM.print(__VA_ARGS__) +#define N_DEBUG_PRINTLN(...) DEBUGSTREAM.println(__VA_ARGS__) +#else +#define N_DEBUG_PRINT(...) +#define N_DEBUG_PRINTLN(...) +#endif + +#if DEBUG >= LOG_LEVEL_TRACE +#define T_DEBUG_PRINT(...) DEBUGSTREAM.print(__VA_ARGS__) +#define T_DEBUG_PRINTLN(...) DEBUGSTREAM.println(__VA_ARGS__) +#else +#define T_DEBUG_PRINT(...) +#define T_DEBUG_PRINTLN(...) +#endif + +#if DEBUG >= LOG_LEVEL_VERBOSE +#define V_DEBUG_PRINT(...) DEBUGSTREAM.print(__VA_ARGS__) +#define V_DEBUG_PRINTLN(...) DEBUGSTREAM.println(__VA_ARGS__) +#else +#define V_DEBUG_PRINT(...) +#define V_DEBUG_PRINTLN(...) +#endif diff --git a/src/utility/endian.h b/src/utility/endian.h new file mode 100644 index 0000000..0f9271c --- /dev/null +++ b/src/utility/endian.h @@ -0,0 +1,139 @@ +#pragma once + +#ifndef BYTE_ORDER + +#ifndef BIG_ENDIAN +#define BIG_ENDIAN 4321 +#endif +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN 1234 +#endif + +#define TEST_LITTLE_ENDIAN (((union { unsigned x; unsigned char c; }){1}).c) + +#ifdef TEST_LITTLE_ENDIAN +#define BYTE_ORDER LITTLE_ENDIAN +#else +#define BYTE_ORDER BIG_ENDIAN +#endif + +#undef TEST_LITTLE_ENDIAN +#endif + +#include + +#ifndef __bswap16 +#define __bswap16(x) ((uint16_t)((((uint16_t)(x)&0xff00) >> 8) | (((uint16_t)(x)&0x00ff) << 8))) +#endif + +#ifndef __bswap32 +#define __bswap32(x) \ + ((uint32_t)((((uint32_t)(x)&0xff000000) >> 24) | (((uint32_t)(x)&0x00ff0000) >> 8) | \ + (((uint32_t)(x)&0x0000ff00) << 8) | (((uint32_t)(x)&0x000000ff) << 24))) +#endif + +#ifndef __bswap64 +#define __bswap64(x) \ + ((uint64_t)((((uint64_t)(x)&0xff00000000000000ULL) >> 56) | \ + (((uint64_t)(x)&0x00ff000000000000ULL) >> 40) | \ + (((uint64_t)(x)&0x0000ff0000000000ULL) >> 24) | \ + (((uint64_t)(x)&0x000000ff00000000ULL) >> 8) | \ + (((uint64_t)(x)&0x00000000ff000000ULL) << 8) | \ + (((uint64_t)(x)&0x0000000000ff0000ULL) << 24) | \ + (((uint64_t)(x)&0x000000000000ff00ULL) << 40) | \ + (((uint64_t)(x)&0x00000000000000ffULL) << 56))) +#endif + +union conversionBuffer +{ + uint8_t value8; + uint16_t value16; + uint32_t value32; + uint64_t value64; + byte buffer[8]; +}; + +#if BYTE_ORDER == LITTLE_ENDIAN + +// Definitions from musl libc +#define htobe16(x) __bswap16(x) +#define be16toh(x) __bswap16(x) +#define betoh16(x) __bswap16(x) +#define htobe32(x) __bswap32(x) +#define be32toh(x) __bswap32(x) +#define betoh32(x) __bswap32(x) +#define htobe64(x) __bswap64(x) +#define be64toh(x) __bswap64(x) +#define betoh64(x) __bswap64(x) +#define htole16(x) (uint16_t)(x) +#define le16toh(x) (uint16_t)(x) +#define letoh16(x) (uint16_t)(x) +#define htole32(x) (uint32_t)(x) +#define le32toh(x) (uint32_t)(x) +#define letoh32(x) (uint32_t)(x) +#define htole64(x) (uint64_t)(x) +#define le64toh(x) (uint64_t)(x) +#define letoh64(x) (uint64_t)(x) + +// From Apple Open Source Libc +#define ntohs(x) __bswap16(x) +#define htons(x) __bswap16(x) +#define ntohl(x) __bswap32(x) +#define htonl(x) __bswap32(x) +#define ntohll(x) __bswap64(x) +#define htonll(x) __bswap64(x) + +#define NTOHL(x) (x) = ntohl((uint32_t)x) +#define NTOHS(x) (x) = ntohs((uint16_t)x) +#define NTOHLL(x) (x) = ntohll((uint64_t)x) +#define HTONL(x) (x) = htonl((uint32_t)x) +#define HTONS(x) (x) = htons((uint16_t)x) +#define HTONLL(x) (x) = htonll((uint64_t)x) + +#else // BIG_ENDIAN + +// Definitions from musl libc + +#define htobe16(x) (uint16_t)(x) +#define be16toh(x) (uint16_t)(x) +#define betoh16(x) (uint16_t)(x) +#define htobe32(x) (uint32_t)(x) +#define be32toh(x) (uint32_t)(x) +#define betoh32(x) (uint32_t)(x) +#define htobe64(x) (uint64_t)(x) +#define be64toh(x) (uint64_t)(x) +#define betoh64(x) (uint64_t)(x) +#define htole16(x) __bswap16(x) +#define le16toh(x) __bswap16(x) +#define letoh16(x) __bswap16(x) +#define htole32(x) __bswap32(x) +#define le32toh(x) __bswap32(x) +#define letoh32(x) __bswap32(x) +#define htole64(x) __bswap64(x) +#define le64toh(x) __bswap64(x) +#define letoh64(x) __bswap64(x) + +// From Apple Open Source libc +#define ntohl(x) ((uint32_t)(x)) +#define ntohs(x) ((uint16_t)(x)) +#define htonl(x) ((uint32_t)(x)) +#define htons(x) ((uint16_t)(x)) +#define ntohll(x) ((uint64_t)(x)) +#define htonll(x) ((uint64_t)(x)) + +#define NTOHL(x) (x) +#define NTOHS(x) (x) +#define NTOHLL(x) (x) +#define HTONL(x) (x) +#define HTONS(x) (x) +#define HTONLL(x) (x) + + +void aa(uint64_t value) +{ + if ( value >= 10 ) + aa(value / 10); + N_DEBUG_PRINT((uint32_t)(value % 10)); +} + +#endif diff --git a/src/utility/midi_bleSettings.h b/src/utility/midi_bleSettings.h deleted file mode 100755 index d9f20a8..0000000 --- a/src/utility/midi_bleSettings.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "midi_bleNamespace.h" - -//#define DEBUG -#define RELEASE - -#if defined(RELEASE) -#define RELEASE_BUILD -#undef DEBUG_BUILD -#endif - -#if defined(DEBUG) -#define DEBUG_BUILD -#undef RELEASE_BUILD -#endif - - -#if defined(RELEASE_BUILD) -#undef BLEMIDI_DEBUG -#undef BLEMIDI_DEBUG_VERBOSE -#endif - -#if defined(DEBUG_BUILD) -#define BLEMIDI_DEBUG 1 -#undef BLEMIDI_DEBUG_VERBOSE -#define BLEMIDI_DEBUG_PARSING -#endif - diff --git a/src/utility/midi_feat4_4_0/MIDI.cpp b/src/utility/midi_feat4_4_0/MIDI.cpp new file mode 100644 index 0000000..6d9b6a7 --- /dev/null +++ b/src/utility/midi_feat4_4_0/MIDI.cpp @@ -0,0 +1,115 @@ +/*! + * @file MIDI.cpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @author Francois Best + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 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. + */ + +#include "MIDI.h" + +// ----------------------------------------------------------------------------- + +BEGIN_MIDI_NAMESPACE + +/*! \brief Encode System Exclusive messages. + SysEx messages are encoded to guarantee transmission of data bytes higher than + 127 without breaking the MIDI protocol. Use this static method to convert the + data you want to send. + \param inData The data to encode. + \param outSysEx The output buffer where to store the encoded message. + \param inLength The lenght of the input buffer. + \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, + bool inFlipHeaderBits) +{ + unsigned outLength = 0; // Num bytes in output array. + byte count = 0; // Num 7bytes in a block. + outSysEx[0] = 0; + + for (unsigned i = 0; i < inLength; ++i) + { + const byte data = inData[i]; + const byte msb = data >> 7; + const byte body = data & 0x7f; + + outSysEx[0] |= (msb << (inFlipHeaderBits ? count : (6 - count))); + outSysEx[1 + count] = body; + + if (count++ == 6) + { + outSysEx += 8; + outLength += 8; + outSysEx[0] = 0; + count = 0; + } + } + return outLength + count + (count != 0 ? 1 : 0); +} + +/*! \brief Decode System Exclusive messages. + SysEx messages are encoded to guarantee transmission of data bytes higher than + 127 without breaking the MIDI protocol. Use this static method to reassemble + your received message. + \param inSysEx The SysEx data received from MIDI in. + \param outData The output buffer where to store the decrypted message. + \param inLength The lenght of the input buffer. + \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, + bool inFlipHeaderBits) +{ + unsigned count = 0; + byte msbStorage = 0; + byte byteIndex = 0; + + for (unsigned i = 0; i < inLength; ++i) + { + if ((i % 8) == 0) + { + msbStorage = inSysEx[i]; + byteIndex = 6; + } + else + { + const byte body = inSysEx[i]; + const byte shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex; + const byte msb = byte(((msbStorage >> shift) & 1) << 7); + byteIndex--; + outData[count++] = msb | body; + } + } + return count; +} + +END_MIDI_NAMESPACE diff --git a/src/utility/midi_feat4_4_0/MIDI.h b/src/utility/midi_feat4_4_0/MIDI.h new file mode 100644 index 0000000..9edc8ee --- /dev/null +++ b/src/utility/midi_feat4_4_0/MIDI.h @@ -0,0 +1,266 @@ +/*! + * @file MIDI.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @author Francois Best + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 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_Settings.h" +#include "midi_Message.h" + +// ----------------------------------------------------------------------------- + +BEGIN_MIDI_NAMESPACE + +/*! \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 +class MidiInterface +{ +public: + typedef _Settings Settings; + +public: + inline MidiInterface(Encoder&); + inline ~MidiInterface(); + +public: + void begin(Channel inChannel = 1); + + // ------------------------------------------------------------------------- + // MIDI Output + +public: + inline void sendNoteOn(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel); + + inline void sendNoteOff(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel); + + inline void sendProgramChange(DataByte inProgramNumber, + Channel inChannel); + + inline void sendControlChange(DataByte inControlNumber, + DataByte inControlValue, + Channel inChannel); + + inline void sendPitchBend(int inPitchValue, Channel inChannel); + inline void sendPitchBend(double inPitchValue, Channel inChannel); + + inline void sendPolyPressure(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel) __attribute__ ((deprecated)); + + inline void sendAfterTouch(DataByte inPressure, + Channel inChannel); + inline void sendAfterTouch(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel); + + inline void sendSysEx(unsigned inLength, + const byte* inArray, + bool inArrayContainsBoundaries = false); + + inline void sendTimeCodeQuarterFrame(DataByte inTypeNibble, + DataByte inValuesNibble); + inline void sendTimeCodeQuarterFrame(DataByte inData); + + inline void sendSongPosition(unsigned inBeats); + inline void sendSongSelect(DataByte inSongNumber); + inline void sendTuneRequest(); + inline void sendRealTime(MidiType inType); + + inline void beginRpn(unsigned inNumber, + Channel inChannel); + inline void sendRpnValue(unsigned inValue, + Channel inChannel); + inline void sendRpnValue(byte inMsb, + byte inLsb, + Channel inChannel); + inline void sendRpnIncrement(byte inAmount, + Channel inChannel); + inline void sendRpnDecrement(byte inAmount, + Channel inChannel); + inline void endRpn(Channel inChannel); + + inline void beginNrpn(unsigned inNumber, + Channel inChannel); + inline void sendNrpnValue(unsigned inValue, + Channel inChannel); + inline void sendNrpnValue(byte inMsb, + byte inLsb, + Channel inChannel); + inline void sendNrpnIncrement(byte inAmount, + Channel inChannel); + inline void sendNrpnDecrement(byte inAmount, + Channel inChannel); + inline void endNrpn(Channel inChannel); + +public: + void send(MidiType inType, + DataByte inData1, + DataByte inData2, + Channel inChannel); + + // ------------------------------------------------------------------------- + // MIDI Input + +public: + inline bool read(); + inline bool read(Channel inChannel); + +public: + inline MidiType getType() const; + inline Channel getChannel() const; + inline DataByte getData1() const; + inline DataByte getData2() const; + inline const byte* getSysExArray() const; + inline unsigned getSysExArrayLength() const; + inline bool check() const; + +public: + inline Channel getInputChannel() const; + inline void setInputChannel(Channel inChannel); + +public: + static inline MidiType getTypeFromStatusByte(byte inStatus); + static inline Channel getChannelFromStatusByte(byte inStatus); + static inline bool isChannelMessage(MidiType inType); + + // ------------------------------------------------------------------------- + // 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 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); + + // ------------------------------------------------------------------------- + // MIDI Soft Thru + +public: + inline Thru::Mode getFilterMode() const; + inline bool getThruState() const; + + inline void turnThruOn(Thru::Mode inThruFilterMode = Thru::Full); + inline void turnThruOff(); + inline void setThruFilterMode(Thru::Mode inThruFilterMode); + +private: + void thruFilter(byte inChannel); + +private: + bool parse(); + inline void handleNullVelocityNoteOnAsNoteOff(); + inline bool inputFilter(Channel inChannel); + inline void resetInput(); + +private: + typedef Message MidiMessage; + +private: + Encoder& mEncoder; + +private: + Channel mInputChannel; + StatusByte mRunningStatus_RX; + StatusByte mRunningStatus_TX; + byte mPendingMessage[3]; + unsigned mPendingMessageExpectedLenght; + unsigned mPendingMessageIndex; + unsigned mCurrentRpnNumber; + unsigned mCurrentNrpnNumber; + bool mThruActivated : 1; + Thru::Mode mThruFilterMode : 7; + MidiMessage mMessage; + + unsigned long mLastMessageSentTime; + bool mSenderActiveSensingActivated; + +private: + inline StatusByte getStatus(MidiType inType, + Channel inChannel) const; +}; + +// ----------------------------------------------------------------------------- + +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 + +#include "MIDI.hpp" diff --git a/src/utility/midi_feat4_4_0/MIDI.hpp b/src/utility/midi_feat4_4_0/MIDI.hpp new file mode 100644 index 0000000..1a53659 --- /dev/null +++ b/src/utility/midi_feat4_4_0/MIDI.hpp @@ -0,0 +1,1428 @@ +/*! + * @file MIDI.hpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Inline implementations + * @author Francois Best + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 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 + +/// \brief Constructor for MidiInterface. +template +inline MidiInterface::MidiInterface(Encoder& inEncoder) + : mEncoder(inEncoder) + , mInputChannel(0) + , mRunningStatus_RX(InvalidType) + , mRunningStatus_TX(InvalidType) + , mPendingMessageExpectedLenght(0) + , mPendingMessageIndex(0) + , mCurrentRpnNumber(0xffff) + , mCurrentNrpnNumber(0xffff) + , mLastMessageSentTime(0) + , mSenderActiveSensingActivated(false) + , mThruActivated(false) + , mThruFilterMode(Thru::Full) +{ + 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; +} + +/*! \brief Destructor for MidiInterface. + + This is not really useful for the Arduino, as it is never called... + */ +template +inline MidiInterface::~MidiInterface() +{ +} + +// ----------------------------------------------------------------------------- + +/*! \brief Call the begin method in the setup() function of the Arduino. + + All parameters are set to their default values: + - Input channel set to 1 if no value is specified + - Full thru mirroring + */ +template +void MidiInterface::begin(Channel inChannel) +{ + // Initialise the Serial port + mEncoder.begin(); + + mInputChannel = inChannel; + mRunningStatus_TX = InvalidType; + mRunningStatus_RX = InvalidType; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mCurrentRpnNumber = 0xffff; + mCurrentNrpnNumber = 0xffff; + + mSenderActiveSensingActivated = Settings::UseSenderActiveSensing; + mLastMessageSentTime = millis(); + + mMessage.valid = false; + mMessage.type = InvalidType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + + mThruFilterMode = Thru::Full; + mThruActivated = false; +} + +// ----------------------------------------------------------------------------- +// Output +// ----------------------------------------------------------------------------- + +/*! \addtogroup output + @{ + */ + +/*! \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. + \param inData2 The second data byte (if the message contains only 1 data byte, + set this one to 0). + \param inChannel The output channel on which the message will be sent + (values from 1 to 16). Note: you cannot send to OMNI. + + 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, + DataByte inData1, + DataByte inData2, + Channel inChannel) +{ + // Then test if channel is valid + if (inChannel >= MIDI_CHANNEL_OFF || + inChannel == MIDI_CHANNEL_OMNI || + inType < 0x80) + { + return; // Don't send anything + } + + if (inType <= PitchBend) // Channel messages + { + // Protection: remove MSBs on data + inData1 &= 0x7f; + inData2 &= 0x7f; + + const StatusByte status = getStatus(inType, inChannel); + + if (mEncoder.beginTransmission()) + { + if (Settings::UseRunningStatus) + { + if (mRunningStatus_TX != status) + { + // New message, memorise and send header + mRunningStatus_TX = status; + mEncoder.write(mRunningStatus_TX); + } + } + else + { + // Don't care about running status, send the status byte. + mEncoder.write(status); + } + + // Then send data + mEncoder.write(inData1); + if (inType != ProgramChange && inType != AfterTouchChannel) + { + mEncoder.write(inData2); + } + + mEncoder.endTransmission(); + } + } + else if (inType >= Clock && inType <= SystemReset) + { + sendRealTime(inType); // System Real-time and 1 byte. + } + + if (mSenderActiveSensingActivated) + mLastMessageSentTime = millis(); +} + +// ----------------------------------------------------------------------------- + +/*! \brief Send a Note On message + \param inNoteNumber Pitch value in the MIDI format (0 to 127). + \param inVelocity Note attack velocity (0 to 127). A NoteOn with 0 velocity + is considered as a NoteOff. + \param inChannel The channel on which the message will be sent (1 to 16). + + 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, + DataByte inVelocity, + Channel inChannel) +{ + send(NoteOn, inNoteNumber, inVelocity, inChannel); +} + +/*! \brief Send a Note Off message + \param inNoteNumber Pitch value in the MIDI format (0 to 127). + \param inVelocity Release velocity (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + + Note: you can send NoteOn with zero velocity to make a NoteOff, this is based + on the Running Status principle, to avoid sending status messages and thus + sending only NoteOn data. sendNoteOff will always send a real NoteOff message. + 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, + DataByte inVelocity, + Channel inChannel) +{ + send(NoteOff, inNoteNumber, inVelocity, inChannel); +} + +/*! \brief Send a Program Change message + \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, + Channel inChannel) +{ + send(ProgramChange, inProgramNumber, 0, inChannel); +} + +/*! \brief Send a Control Change message + \param inControlNumber The controller number (0 to 127). + \param inControlValue The value for the specified controller (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + @see MidiControlChangeNumber + */ +template +void MidiInterface::sendControlChange(DataByte inControlNumber, + DataByte inControlValue, + Channel inChannel) +{ + send(ControlChange, inControlNumber, inControlValue, inChannel); +} + +/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) + \param inNoteNumber The note to apply AfterTouch to (0 to 127). + \param inPressure The amount of AfterTouch to apply (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + 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, + DataByte inPressure, + Channel inChannel) +{ + send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); +} + +/*! \brief Send a MonoPhonic AfterTouch message (applies to all notes) + \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, + Channel inChannel) +{ + send(AfterTouchChannel, inPressure, 0, inChannel); +} + +/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) + \param inNoteNumber The note to apply AfterTouch to (0 to 127). + \param inPressure The amount of AfterTouch to apply (0 to 127). + \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, + DataByte inPressure, + Channel inChannel) +{ + send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); +} + +/*! \brief Send a Pitch Bend message using a signed integer value. + \param inPitchValue The amount of bend to send (in a signed integer format), + between MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX, + center value is 0. + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendPitchBend(int inPitchValue, + Channel inChannel) +{ + const unsigned bend = unsigned(inPitchValue - int(MIDI_PITCHBEND_MIN)); + send(PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, inChannel); +} + + +/*! \brief Send a Pitch Bend message using a floating point value. + \param inPitchValue The amount of bend to send (in a floating point format), + between -1.0f (maximum downwards bend) + 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, + Channel inChannel) +{ + const int scale = inPitchValue > 0.0 ? MIDI_PITCHBEND_MAX : MIDI_PITCHBEND_MIN; + const int value = int(inPitchValue * double(scale)); + sendPitchBend(value, inChannel); +} + +/*! \brief Generate and send a System Exclusive frame. + \param inLength The size of the array to send + \param inArray The byte array containing the data to send + \param inArrayContainsBoundaries When set to 'true', 0xf0 & 0xf7 bytes + (start & stop SysEx) will NOT be sent + (and therefore must be included in the array). + default value for ArrayContainsBoundaries is set to 'false' for compatibility + with previous versions of the library. + */ +template +void MidiInterface::sendSysEx(unsigned inLength, + const byte* inArray, + bool inArrayContainsBoundaries) +{ + const bool writeBeginEndBytes = !inArrayContainsBoundaries; + + if (mEncoder.beginTransmission()) + { + if (writeBeginEndBytes) + mEncoder.write(0xf0); + + for (unsigned i = 0; i < inLength; ++i) + mEncoder.write(inArray[i]); + + if (writeBeginEndBytes) + mEncoder.write(0xf7); + + mEncoder.endTransmission(); + } + + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! \brief Send a Tune Request message. + + When a MIDI unit receives this message, + it should tune its oscillators (if equipped with any). + */ +template +void MidiInterface::sendTuneRequest() +{ + if (mEncoder.beginTransmission()) + { + mEncoder.write(TuneRequest); + mEncoder.endTransmission(); + } + + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! \brief Send a MIDI Time Code Quarter Frame. + + \param inTypeNibble MTC type + \param inValuesNibble MTC data + See MIDI Specification for more information. + */ +template +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, + DataByte inValuesNibble) +{ + const byte data = byte((((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0f))); + sendTimeCodeQuarterFrame(data); +} + +/*! \brief Send a MIDI Time Code Quarter Frame. + + See MIDI Specification for more information. + \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) +{ + if (mEncoder.beginTransmission()) + { + mEncoder.write((byte)TimeCodeQuarterFrame); + mEncoder.write(inData); + mEncoder.endTransmission(); + } + + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! \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) +{ + if (mEncoder.beginTransmission()) + { + mEncoder.write((byte)SongPosition); + mEncoder.write(inBeats & 0x7f); + mEncoder.write((inBeats >> 7) & 0x7f); + mEncoder.endTransmission(); + } + + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! \brief Send a Song Select message */ +template +void MidiInterface::sendSongSelect(DataByte inSongNumber) +{ + if (mEncoder.beginTransmission()) + { + mEncoder.write((byte)SongSelect); + mEncoder.write(inSongNumber & 0x7f); + mEncoder.endTransmission(); + } + + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! \brief Send a Real Time (one byte) message. + + \param inType The available Real Time types are: + Start, Stop, Continue, Clock, ActiveSensing and SystemReset. + @see MidiType + */ +template +void MidiInterface::sendRealTime(MidiType inType) +{ + // Do not invalidate Running Status for real-time messages + // as they can be interleaved within any message. + + switch (inType) + { + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + if (mEncoder.beginTransmission()) + { + mEncoder.write((byte)inType); + mEncoder.endTransmission(); + } + break; + default: + // Invalid Real Time marker + break; + } +} + +/*! \brief Start a Registered Parameter Number frame. + \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, + Channel inChannel) +{ + if (mCurrentRpnNumber != inNumber) + { + const byte numMsb = 0x7f & (inNumber >> 7); + const byte numLsb = 0x7f & inNumber; + sendControlChange(RPNLSB, numLsb, inChannel); + sendControlChange(RPNMSB, numMsb, inChannel); + mCurrentRpnNumber = inNumber; + } +} + +/*! \brief Send a 14-bit value for the currently selected RPN number. + \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, + Channel inChannel) +{; + const byte valMsb = 0x7f & (inValue >> 7); + const byte valLsb = 0x7f & inValue; + sendControlChange(DataEntryMSB, valMsb, inChannel); + sendControlChange(DataEntryLSB, valLsb, inChannel); +} + +/*! \brief Send separate MSB/LSB values for the currently selected RPN number. + \param inMsb The MSB part of the value to send. Meaning depends on RPN number. + \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, + byte inLsb, + Channel inChannel) +{ + sendControlChange(DataEntryMSB, inMsb, inChannel); + sendControlChange(DataEntryLSB, inLsb, inChannel); +} + +/* \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, + Channel inChannel) +{ + sendControlChange(DataIncrement, inAmount, inChannel); +} + +/* \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, + Channel inChannel) +{ + sendControlChange(DataDecrement, inAmount, inChannel); +} + +/*! \brief Terminate an RPN frame. +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) +{ + sendControlChange(RPNLSB, 0x7f, inChannel); + sendControlChange(RPNMSB, 0x7f, inChannel); + mCurrentRpnNumber = 0xffff; +} + + + +/*! \brief Start a Non-Registered Parameter Number frame. + \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, + Channel inChannel) +{ + if (mCurrentNrpnNumber != inNumber) + { + const byte numMsb = 0x7f & (inNumber >> 7); + const byte numLsb = 0x7f & inNumber; + sendControlChange(NRPNLSB, numLsb, inChannel); + sendControlChange(NRPNMSB, numMsb, inChannel); + mCurrentNrpnNumber = inNumber; + } +} + +/*! \brief Send a 14-bit value for the currently selected NRPN number. + \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, + Channel inChannel) +{; + const byte valMsb = 0x7f & (inValue >> 7); + const byte valLsb = 0x7f & inValue; + sendControlChange(DataEntryMSB, valMsb, inChannel); + sendControlChange(DataEntryLSB, valLsb, inChannel); +} + +/*! \brief Send separate MSB/LSB values for the currently selected NRPN number. + \param inMsb The MSB part of the value to send. Meaning depends on NRPN number. + \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, + byte inLsb, + Channel inChannel) +{ + sendControlChange(DataEntryMSB, inMsb, inChannel); + sendControlChange(DataEntryLSB, inLsb, inChannel); +} + +/* \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, + Channel inChannel) +{ + sendControlChange(DataIncrement, inAmount, inChannel); +} + +/* \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, + Channel inChannel) +{ + sendControlChange(DataDecrement, inAmount, inChannel); +} + +/*! \brief Terminate an NRPN frame. +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) +{ + sendControlChange(NRPNLSB, 0x7f, inChannel); + sendControlChange(NRPNMSB, 0x7f, inChannel); + mCurrentNrpnNumber = 0xffff; +} + +/*! @} */ // End of doc group MIDI Output + +// ----------------------------------------------------------------------------- + +template +StatusByte MidiInterface::getStatus(MidiType inType, + Channel inChannel) const +{ + return StatusByte(((byte)inType | ((inChannel - 1) & 0x0f))); +} + +// ----------------------------------------------------------------------------- +// Input +// ----------------------------------------------------------------------------- + +/*! \addtogroup input + @{ +*/ + +/*! \brief Read messages from the serial port using the main input channel. + + \return True if a valid message has been stored in the structure, false if not. + A valid message is a message that matches the input channel. \n\n + If the Thru is enabled and the message matches the filter, + it is sent back on the MIDI output. + @see see setInputChannel() + */ +template +inline bool MidiInterface::read() +{ + return read(mInputChannel); +} + +/*! \brief Read messages on a specified channel. + */ +template +inline bool MidiInterface::read(Channel inChannel) +{ + // 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 (mSenderActiveSensingActivated && (millis() - mLastMessageSentTime) > 270) + { + sendRealTime(ActiveSensing); + mLastMessageSentTime = millis(); + } + + if (inChannel >= MIDI_CHANNEL_OFF) + return false; // MIDI Input disabled. + + if (!parse()) + return false; + + handleNullVelocityNoteOnAsNoteOff(); + const bool channelMatch = inputFilter(inChannel); + + if (channelMatch) + { + launchCallback(); + } + + thruFilter(inChannel); + + return channelMatch; +} + +// ----------------------------------------------------------------------------- + +// Private method: MIDI parser +template +bool MidiInterface::parse() +{ + if (mEncoder.available() == 0) + { + // No data available. + return false; + } + + // Parsing algorithm: + // Get a byte from the serial buffer. + // If there is no pending message to be recomposed, start a new one. + // - Find type and channel (if pertinent) + // - Look for other bytes in buffer, call parser recursively, + // until the message is assembled or the buffer is empty. + // Else, add the extracted byte to the pending message, and check validity. + // When the message is done, store it. + + const byte extracted = mEncoder.read(); + + // Ignore Undefined + if (extracted == 0xf9 || extracted == 0xfd) + { + if (Settings::Use1ByteParsing) + { + return false; + } + else + { + return parse(); + } + } + + if (mPendingMessageIndex == 0) + { + // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + if (isChannelMessage(getTypeFromStatusByte(mRunningStatus_RX))) + { + // Only these types allow Running Status + + // If the status byte is not received, prepend it + // to the pending message + if (extracted < 0x80) + { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, + // so the running status does not apply here. + // It will be updated upon completion of this message. + } + + const MidiType pendingType = getTypeFromStatusByte(mPendingMessage[0]); + + switch (pendingType) + { + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = pendingType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // Do not reset all input attributes, Running Status must remain unchanged. + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLenght = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLenght = 3; + break; + + case SystemExclusiveStart: + case SystemExclusiveEnd: + // The message can be any lenght + // between 3 and MidiMessage::sSysExMaxSize bytes + mPendingMessageExpectedLenght = MidiMessage::sSysExMaxSize; + mRunningStatus_RX = InvalidType; + mMessage.sysexArray[0] = pendingType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + resetInput(); + return false; + break; + } + + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 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 + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mMessage.valid = true; + return true; + } + else + { + // Waiting for more data + mPendingMessageIndex++; + } + + if (Settings::Use1ByteParsing) + { + // Message is not complete. + return false; + } + else + { + // Call the parser recursively + // to parse the rest of the message. + return parse(); + } + } + else + { + // First, test if this is a status byte + if (extracted >= 0x80) + { + // Reception of status bytes in the middle of an uncompleted message + // are allowed only for interleaved Real Time message or EOX + switch (extracted) + { + case Clock: + case Start: + case Continue: + case Stop: + case ActiveSensing: + case SystemReset: + + // Here we will have to extract the one-byte message, + // pass it to the structure for being read outside + // the MIDI class, and recompose the message it was + // interleaved into. Oh, and without killing the running status.. + // This is done by leaving the pending message as is, + // it will be completed on next calls. + + mMessage.type = (MidiType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + // Exclusive + case SystemExclusiveStart: + case SystemExclusiveEnd: + if ((mMessage.sysexArray[0] == SystemExclusiveStart) + || (mMessage.sysexArray[0] == SystemExclusiveEnd)) + { + // Store the last byte (EOX) + mMessage.sysexArray[mPendingMessageIndex++] = extracted; + mMessage.type = SystemExclusive; + + // Get length + mMessage.data1 = mPendingMessageIndex & 0xff; // LSB + mMessage.data2 = byte(mPendingMessageIndex >> 8); // MSB + mMessage.channel = 0; + mMessage.valid = true; + + resetInput(); + return true; + } + else + { + // Well well well.. error. + resetInput(); + return false; + } + + default: + break; // LCOV_EXCL_LINE - Coverage blind spot + } + } + + // Add extracted data byte to pending message + 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)) + { + // "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) + { + resetInput(); + return false; + } + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + + if (isChannelMessage(mMessage.type)) + mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); + else + mMessage.channel = 0; + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + mMessage.data2 = mPendingMessageExpectedLenght == 3 ? mPendingMessage[2] : 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) + { + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; + break; + + default: + // No running status + mRunningStatus_RX = InvalidType; + break; + } + return true; + } + else + { + // 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(); + } + } + } +} + +// Private method, see midi_Settings.h for documentation +template +inline void MidiInterface::handleNullVelocityNoteOnAsNoteOff() +{ + if (Settings::HandleNullVelocityNoteOnAsNoteOff && + getType() == NoteOn && getData2() == 0) + { + mMessage.type = NoteOff; + } +} + +// Private method: check if the received message is on the listened channel +template +inline bool MidiInterface::inputFilter(Channel inChannel) +{ + // This method handles recognition of channel + // (to know if the message is destinated to the Arduino) + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { + // Then we need to know if we listen to it + if ((mMessage.channel == inChannel) || + (inChannel == MIDI_CHANNEL_OMNI)) + { + return true; + } + else + { + // We don't listen to this channel + return false; + } + } + else + { + // System messages are always received + return true; + } +} + +// Private method: reset input attributes +template +inline void MidiInterface::resetInput() +{ + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; +} + +// ----------------------------------------------------------------------------- + +/*! \brief Get the last received message's type + + Returns an enumerated type. @see MidiType + */ +template +inline MidiType MidiInterface::getType() const +{ + return mMessage.type; +} + +/*! \brief Get the channel of the message stored in the structure. + + \return Channel range is 1 to 16. + For non-channel messages, this will return 0. + */ +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 +{ + return mMessage.data1; +} + +/*! \brief Get the second data byte of the last received message. */ +template +inline DataByte MidiInterface::getData2() const +{ + return mMessage.data2; +} + +/*! \brief Get the System Exclusive byte array. + + @see getSysExArrayLength to get the array's length in bytes. + */ +template +inline const byte* MidiInterface::getSysExArray() const +{ + return mMessage.sysexArray; +} + +/*! \brief Get the lenght of the System Exclusive array. + + It is coded using data1 as LSB and data2 as MSB. + \return The array's length, in bytes. + */ +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 +{ + return mMessage.valid; +} + +// ----------------------------------------------------------------------------- + +template +inline Channel MidiInterface::getInputChannel() const +{ + return mInputChannel; +} + +/*! \brief Set the value for the input MIDI channel + \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) +{ + mInputChannel = inChannel; +} + +// ----------------------------------------------------------------------------- + +/*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, + made public so you can handle MidiTypes more easily. + */ +template +MidiType MidiInterface::getTypeFromStatusByte(byte inStatus) +{ + if ((inStatus < 0x80) || + (inStatus == 0xf4) || + (inStatus == 0xf5) || + (inStatus == 0xf9) || + (inStatus == 0xfD)) + { + // Data bytes and undefined. + return InvalidType; + } + 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) +{ + return Channel((inStatus & 0x0f) + 1); +} + +template +bool MidiInterface::isChannelMessage(MidiType inType) +{ + return (inType == NoteOff || + inType == NoteOn || + inType == ControlChange || + inType == AfterTouchPoly || + inType == AfterTouchChannel || + inType == PitchBend || + inType == ProgramChange); +} + +// ----------------------------------------------------------------------------- + +/*! \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) +{ + 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; + default: + break; + } +} + +/*! @} */ // End of doc group MIDI Callbacks + +// Private - launch callback function based on received type. +template +void MidiInterface::launchCallback() +{ + // 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; + + // 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; + + // 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; + 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 ProgramChange: if (mProgramChangeCallback != 0) mProgramChangeCallback(mMessage.channel, mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != 0) 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 SystemReset: if (mSystemResetCallback != 0) mSystemResetCallback(); break; + + case InvalidType: + default: + break; // LCOV_EXCL_LINE - Unreacheable code, but prevents unhandled case warning. + } +} + +/*! @} */ // End of doc group MIDI Input + +// ----------------------------------------------------------------------------- +// Thru +// ----------------------------------------------------------------------------- + +/*! \addtogroup thru + @{ + */ + +/*! \brief Set the filter for thru mirroring + \param inThruFilterMode a filter mode + + @see Thru::Mode + */ +template +inline void MidiInterface::setThruFilterMode(Thru::Mode inThruFilterMode) +{ + mThruFilterMode = inThruFilterMode; + mThruActivated = mThruFilterMode != Thru::Off; +} + +template +inline Thru::Mode MidiInterface::getFilterMode() const +{ + return mThruFilterMode; +} + +template +inline bool MidiInterface::getThruState() const +{ + return mThruActivated; +} + +template +inline void MidiInterface::turnThruOn(Thru::Mode inThruFilterMode) +{ + mThruActivated = true; + mThruFilterMode = inThruFilterMode; +} + +template +inline void MidiInterface::turnThruOff() +{ + mThruActivated = false; + mThruFilterMode = Thru::Off; +} + +/*! @} */ // End of doc group MIDI Thru + +// This method is called upon reception of a message +// and takes care of Thru filtering and sending. +// - All system messages (System Exclusive, Common and Real Time) are passed +// 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) +{ + // If the feature is disabled, don't do anything. + if (!mThruActivated || (mThruFilterMode == Thru::Off)) + return; + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { + const bool filter_condition = ((mMessage.channel == inChannel) || + (inChannel == MIDI_CHANNEL_OMNI)); + + // Now let's pass it to the output + switch (mThruFilterMode) + { + case Thru::Full: + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + break; + + case Thru::SameChannel: + if (filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + } + break; + + case Thru::DifferentChannel: + if (!filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + } + break; + + default: + break; + } + } + else + { + // Send the message to the output + switch (mMessage.type) + { + // Real Time and 1 byte + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + case TuneRequest: + sendRealTime(mMessage.type); + break; + + case SystemExclusive: + // Send SysEx (0xf0 and 0xf7 are included in the buffer) + sendSysEx(getSysExArrayLength(), getSysExArray(), true); + break; + + case SongSelect: + sendSongSelect(mMessage.data1); + break; + + case SongPosition: + sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2 << 7)); + break; + + case TimeCodeQuarterFrame: + sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); + break; + + default: + break; // LCOV_EXCL_LINE - Unreacheable code, but prevents unhandled case warning. + } + } +} + +END_MIDI_NAMESPACE diff --git a/src/utility/midi_feat4_4_0/midi_Defs.h b/src/utility/midi_feat4_4_0/midi_Defs.h new file mode 100644 index 0000000..810e8de --- /dev/null +++ b/src/utility/midi_feat4_4_0/midi_Defs.h @@ -0,0 +1,209 @@ +/*! + * @file midi_Defs.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Definitions + * @author Francois Best + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 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" + +#if ARDUINO +#include +#else +#include +typedef uint8_t byte; +#endif + +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 +#define MIDI_CHANNEL_OFF 17 // and over + +#define MIDI_PITCHBEND_MIN -8192 +#define MIDI_PITCHBEND_MAX 8191 + +// ----------------------------------------------------------------------------- +// Type definitions + +typedef byte StatusByte; +typedef byte DataByte; +typedef byte Channel; +typedef byte FilterMode; + +// ----------------------------------------------------------------------------- + +/*! 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 + 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 + TuneRequest = 0xF6, ///< System Common - Tune Request + SystemExclusiveEnd = 0xF7, ///< System Exclusive End + Clock = 0xF8, ///< System Real Time - Timing Clock + Start = 0xFA, ///< System Real Time - Start + Continue = 0xFB, ///< System Real Time - Continue + Stop = 0xFC, ///< System Real Time - Stop + ActiveSensing = 0xFE, ///< System Real Time - Active Sensing + SystemReset = 0xFF, ///< System Real Time - System Reset +}; + +// ----------------------------------------------------------------------------- + +/*! Enumeration of Thru filter modes */ +struct Thru +{ + enum Mode + { + Off = 0, ///< Thru disabled (nothing passes through). + Full = 1, ///< Fully enabled Thru (every incoming message is sent back). + SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. + DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back. + }; +}; + +/*! 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. + See the detailed controllers numbers & description here: + http://www.somascape.org/midi/tech/spec.html#ctrlnums + */ +enum MidiControlChangeNumber: uint8_t +{ + // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- + BankSelect = 0, + ModulationWheel = 1, + BreathController = 2, + // CC3 undefined + FootController = 4, + PortamentoTime = 5, + DataEntryMSB = 6, + ChannelVolume = 7, + Balance = 8, + // CC9 undefined + Pan = 10, + ExpressionController = 11, + EffectControl1 = 12, + EffectControl2 = 13, + // CC14 undefined + // CC15 undefined + GeneralPurposeController1 = 16, + GeneralPurposeController2 = 17, + GeneralPurposeController3 = 18, + GeneralPurposeController4 = 19, + + DataEntryLSB = 38, + + // Switches ---------------------------------------------------------------- + Sustain = 64, + Portamento = 65, + Sostenuto = 66, + SoftPedal = 67, + Legato = 68, + Hold = 69, + + // Low resolution continuous controllers ----------------------------------- + SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off + SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off + SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off + SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off + SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off + SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off + SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off + SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off + SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off + SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off + GeneralPurposeController5 = 80, + GeneralPurposeController6 = 81, + GeneralPurposeController7 = 82, + GeneralPurposeController8 = 83, + PortamentoControl = 84, + // CC85 to CC90 undefined + Effects1 = 91, ///< Reverb send level + Effects2 = 92, ///< Tremolo depth + Effects3 = 93, ///< Chorus send level + Effects4 = 94, ///< Celeste depth + Effects5 = 95, ///< Phaser depth + DataIncrement = 96, + DataDecrement = 97, + NRPNLSB = 98, ///< Non-Registered Parameter Number (LSB) + NRPNMSB = 99, ///< Non-Registered Parameter Number (MSB) + RPNLSB = 100, ///< Registered Parameter Number (LSB) + RPNMSB = 101, ///< Registered Parameter Number (MSB) + + // Channel Mode messages --------------------------------------------------- + AllSoundOff = 120, + ResetAllControllers = 121, + LocalControl = 122, + AllNotesOff = 123, + OmniModeOff = 124, + OmniModeOn = 125, + MonoModeOn = 126, + PolyModeOn = 127 +}; + +struct RPN +{ + enum RegisteredParameterNumbers: uint16_t + { + PitchBendSensitivity = 0x0000, + ChannelFineTuning = 0x0001, + ChannelCoarseTuning = 0x0002, + SelectTuningProgram = 0x0003, + SelectTuningBank = 0x0004, + ModulationDepthRange = 0x0005, + NullFunction = (0x7f << 7) + 0x7f, + }; +}; + +END_MIDI_NAMESPACE diff --git a/src/utility/midi_feat4_4_0/midi_Message.h b/src/utility/midi_feat4_4_0/midi_Message.h new file mode 100644 index 0000000..04b56c9 --- /dev/null +++ b/src/utility/midi_feat4_4_0/midi_Message.h @@ -0,0 +1,101 @@ +/*! + * @file midi_Message.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Message struct definition + * @author Francois Best + * @date 11/06/14 + * @license MIT - Copyright (c) 2015 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" +#include "midi_Defs.h" +#ifndef ARDUINO +#include +#endif + +BEGIN_MIDI_NAMESPACE + +/*! The Message structure contains decoded data of a MIDI message + read from the serial port with read() + */ +template +struct Message +{ + /*! Default constructor + \n Initializes the attributes with their default values. + */ + inline Message() + : channel(0) + , type(MIDI_NAMESPACE::InvalidType) + , data1(0) + , data2(0) + , valid(false) + { + memset(sysexArray, 0, sSysExMaxSize * sizeof(DataByte)); + } + + /*! The maximum size for the System Exclusive array. + */ + static const unsigned sSysExMaxSize = SysExMaxSize; + + /*! The MIDI channel on which the message was recieved. + \n Value goes from 1 to 16. + */ + Channel channel; + + /*! The type of the message + (see the MidiType enum for types reference) + */ + MidiType type; + + /*! The first data byte. + \n Value goes from 0 to 127. + */ + DataByte data1; + + /*! The second data byte. + If the message is only 2 bytes long, this one is null. + \n Value goes from 0 to 127. + */ + DataByte data2; + + /*! System Exclusive dedicated byte array. + \n Array length is stocked on 16 bits, + in data1 (LSB) and data2 (MSB) + */ + DataByte sysexArray[sSysExMaxSize]; + + /*! This boolean indicates if the message is valid or not. + There is no channel consideration here, + validity means the message respects the MIDI norm. + */ + bool valid; + + inline unsigned getSysExSize() const + { + const unsigned size = unsigned(data2) << 8 | data1; + return size > sSysExMaxSize ? sSysExMaxSize : size; + } +}; + +END_MIDI_NAMESPACE diff --git a/src/utility/midi_feat4_4_0/midi_Namespace.h b/src/utility/midi_feat4_4_0/midi_Namespace.h new file mode 100644 index 0000000..cab0a2c --- /dev/null +++ b/src/utility/midi_feat4_4_0/midi_Namespace.h @@ -0,0 +1,38 @@ +/*! + * @file midi_Namespace.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Namespace declaration + * @author Francois Best + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 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 + +#define MIDI_NAMESPACE midi_v440 +#define BEGIN_MIDI_NAMESPACE namespace MIDI_NAMESPACE { +#define END_MIDI_NAMESPACE } + +#define USING_NAMESPACE_MIDI using namespace MIDI_NAMESPACE; + +BEGIN_MIDI_NAMESPACE + +END_MIDI_NAMESPACE diff --git a/src/utility/midi_feat4_4_0/midi_Settings.h b/src/utility/midi_feat4_4_0/midi_Settings.h new file mode 100644 index 0000000..60f034c --- /dev/null +++ b/src/utility/midi_feat4_4_0/midi_Settings.h @@ -0,0 +1,87 @@ +/*! + * @file midi_Settings.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Settings + * @author Francois Best + * @date 24/02/11 + * @license MIT - Copyright (c) 2015 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 + +/*! \brief Default Settings for the MIDI Library. + + To change the default settings, don't edit them there, create a subclass and + override the values in that subclass, then use the MIDI_CREATE_CUSTOM_INSTANCE + macro to create your instance. The settings you don't override will keep their + default value. Eg: + \code{.cpp} + struct MySettings : public MIDI::DefaultSettings + { + static const unsigned SysExMaxSize = 1024; // Accept SysEx messages up to 1024 bytes long. + }; + + MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial2, MIDI, MySettings); + \endcode + */ +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; + + /*! NoteOn with 0 velocity should be handled as NoteOf.\n + Set to true to get NoteOff events when receiving null-velocity NoteOn messages.\n + Set to false to get NoteOn events when receiving null-velocity NoteOn messages. + */ + static const bool HandleNullVelocityNoteOnAsNoteOff = true; + + /*! 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.. + */ + static const bool UseSenderActiveSensing = false; + + /*! Setting this to true will make MIDI.read parse only one byte of data for each + call when data is available. This can speed up your application if receiving + a lot of traffic, but might induce MIDI Thru and treatment latency. + */ + static const bool Use1ByteParsing = false; + + /*! 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; +}; + +END_MIDI_NAMESPACE diff --git a/test/Arduino.h b/test/Arduino.h new file mode 100644 index 0000000..c4df0cc --- /dev/null +++ b/test/Arduino.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include + +#include "IPAddress.h" + +#define HEX 0 +#define DEC 1 + +class _serial +{ +public: + void print(const char a[]) { std::cout << a; }; + void print(char a) { std::cout << a; }; + void print(unsigned char a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << (int)a; }; + void print(int a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; + void print(unsigned int a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; + void print(long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; + void print(unsigned long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; + void print(double a, int = 2) { std::cout << a; }; + void print(struct tm * timeinfo, const char * format = NULL) {}; + void print(IPAddress) {}; + + void println(const char a[]) { std::cout << a << "\n"; }; + void println(char a) { std::cout << a << "\n"; }; + void println(unsigned char a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << (int)a << "\n"; }; + void println(int a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; + void println(unsigned int a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; + void println(long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; + void println(unsigned long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; + void println(double a, int format = 2) { std::cout << a << "\n"; }; + void println(struct tm * timeinfo, const char * format = NULL) {}; + void println(IPAddress) {}; + void println(void) { std::cout << "\n"; }; +}; + +_serial Serial; + +#include +typedef uint8_t byte; + +void begin(); +void loop(); + +int main() +{ + begin(); + + while (true) + { + loop(); + } +} + +// avoid strncpy security warning +#pragma warning(disable:4996) + +#define __attribute__(A) /* do nothing */ + +#include "../src/utility/Deque.h" + +#include "../src/utility/midi_feat4_4_0/midi_Defs.h" + +float analogRead(int pin) +{ + return 0.0f; +} + +void randomSeed(float) +{ + srand(static_cast(time(0))); +} + +unsigned long millis() +{ + auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + return (unsigned long)now; +} + +int random(int min, int max) +{ + return RAND_MAX % std::rand() % (max-min) + min; +} + +template const T& min(const T& a, const T& b) { + return !(b < a) ? a : b; // or: return !comp(b,a)?a:b; for version (2) +} + +#define F(x) x diff --git a/test/Ethernet.h b/test/Ethernet.h new file mode 100644 index 0000000..360c252 --- /dev/null +++ b/test/Ethernet.h @@ -0,0 +1,403 @@ +#pragma once +#include + +#include "Arduino.h" + + +class EthernetUDP +{ + Deque _buffer; + uint16_t _port; + +public: + + EthernetUDP() + { + _port = 0; + } + + void begin(uint16_t port) + { + _port = port; + + if (port == 5004 && true) + { + // AppleMIDI messages + } + + if (port == 5005 && true) + { + // rtp-MIDI and AppleMIDI messages + + byte aa[] = { + 0x80, 0x61, 0xbf, 0xa2, 0x12, 0xb, 0x5a, 0xf7, 0xaa, 0x34, 0x96, 0x4a, + 0xc0, 0x2b, + 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x0, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x0, + 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x0, 0xf8, 0x00, 0xf8, 0xc0, 0xbf, 0x89, 0x90, 0x05, 0xd0, 0x7a, 0xd5 }; + + byte bb[] = { 0x80, 0x61, 0xD5, 0xE2, 0x18, 0xCC, 0xAD, 0x1D, 0xC5, 0xB1, 0x54, 0x0, 0x41, 0xF8, 0x20, 0xD5, 0x8B, 0x0, 0x9, 0x18, 0x80, 0x40, 0x81, 0xF1, 0x49, 0x40 }; + + byte lowHighJournalWrong[] = { + 0x80, 0x61, 0xcc, 0x73, 0x19, 0xe, + 0x4e, 0xd4, 0xc5, 0xb1, 0x54, 0x00, 0x42, 0xd0, 0x30, 0x20, 0xcc, 0x4a, 0x00, 0x0a, 0x18, 0x8, + 0x40, 0x81, 0xf1, 0x90, 0x40, 0x2d + }; + + byte sysexJournalMalformed[] = { + 0x80, 0x61, 0x99, 0xc6, 0x1e, 0x90, 0x97, 0xc4, 0xc8, 0x86, 0x76, 0xf9, + 0xc0, 0xc2, + 0xf0, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, + 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, + 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, + 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, + 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, + 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, + 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, + 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, + 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x66, + 0xf7, + 0xc0, 0x99, 0x96, 0x90, 0x05, 0xd0, 0x00, 0x7b }; + + + byte sysexTimingActiveSensingJournal[] = { + 0x80, 0x61, 0xae, 0xae, 0x20, 0x7f, 0xd6, 0xe7, 0xc8, 0x86, 0x76, 0xf9, + 0xc0, 0xc6, + 0xf0, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, + 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, + 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, + 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, + 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, + 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, + 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, + 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, + 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, + 0x19, 0x20, 0x21, 0x19, 0x20, 0x21, 0x19, 0x20, 0x66, + 0xf7, + 0x00, // time + 0xf8, // Timing Clock + 0x00, // Time + 0xfe, // Active Sensing + 0x40, 0xae, 0xa0, 0x10, 0x05, 0x50, 0x00, 0x8f }; // Journal + + byte sysexJournal[] = { + 0x80, 0x61, 0x85, 0xce, 0x1a, 0x5f, 0x1c, 0xa3, 0xc8, 0x86, 0x76, 0xf9, + 0xc1, 0x9a, + 0xf0, + 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x66, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x66, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, 0x19, 0x19, 0x20, 0x21, + 0x66, + 0xf7, + 0x40, 0x85, 0x8b, 0x10, 0x05, 0x50, 0x00, 0x8c }; + + byte sysexMalformedTimingClock[] = { + 0x80, 0x61, 0x85, 0xd9, 0x1a, 0x5f, 0x26, 0xb0, 0xc8, 0x86, 0x76, 0xf9, 0x41, 0xf8, 0xc0, 0x85, 0x8b, 0x90, 0x05, 0xd0, 0x00, 0x95 }; + + + // sysex (command length is xx (or 0x71) in 2 bytes - B-FLAG) + byte sysexSME[] = { + 0x80, 0x61, 0x9A, 0xF, 0x0, 0x2A, 0x7D, 0x3D, 0x29, 0xDC, 0x48, 0x99, + 0x80, 0x70, + 0xF0, + 0x41, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xF7 }; + + byte sysexSE[] = { + 0x80, 0x61, 0x9A, 0xF, 0x0, 0x2A, 0x7D, 0x3D, 0x29, 0xDC, 0x48, 0x99, + 0x80, 0x3f, + 0xF0, + 0x41, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0xF7 }; + + byte sysexF[] = { + 0x80, 0x61, 0x7c, 0xbc, 0x0a, 0xff, 0x56, 0xba, 0x0a, 0x1a, 0x2f, 0x43, + 0x05, + 0xf0, + 0x41, 0x19, 0x20, + 0xf7 }; + + // 36 bytes + byte noteOnOff[] = { + 0x80, 0x61, 0x27, 0x9e, 0x00, 0x1d, 0xb5, 0x36, 0x36, 0x09, 0x2f, 0x2a, // rtp + // MIDI section + 0x46, // flag + 0x80, 0x3f, 0x00, // note off 63 on channel 1, + 0x00, // delta time + 0x3d, 0x00, // note 61 + // Journal Section (17 bytes) + 0x20, // journal flag + 0x27, 0x34, // sequence nr + 0x00, 0x0e, 0x08, // channel 1 channel flag + 0x02, 0x59, // note on off + 0xbd, 0x40, 0xbf, 0x40, // Log list + 0x15, 0xad, 0x5a, 0xdf, 0xa8, // offbit octets + }; + + byte noteOnOff2[] = { + 0x80, 0x61, 0x27, 0x9e, 0x00, 0x1d, 0xb5, 0x36, 0x36, 0x09, 0x2f, 0x2a, // rtp + // MIDI section + 0x46, // flag + 0x80, 0x3f, 0x00, // note off 63 on channel 1, + 0x00, // delta time + 0x3d, 0x00, // note 61 + // Journal Section (17 bytes) + 0x20, // journal flag + 0x27, 0x34, // sequence nr + 0x00, 0x0e, 0x08, // channel 1 channel flag + 0x02, 0x59, // note on off + 0xbd, 0x40, 0xbf, 0x40, // Log list + 0x15, 0xad, 0x5a, 0xdf, 0xa8, // offbit octets + + 0x80, 0x61, 0x27, 0x9e, 0x00, 0x1d, 0xb5, 0x36, 0x36, 0x09, 0x2f, 0x2a, // rtp + // MIDI section + 0x46, // flag + 0x80, 0x3f, 0x00, // note off 63 on channel 1, + 0x00, // delta time + 0x3d, 0x00, // note off note 61 on channel 1 (note the running status) + // Journal Section (17 bytes) + 0x20, // journal flag + 0x27, 0x34, // sequence nr + 0x00, 0x0e, 0x08, // channel 1 channel flag + 0x02, 0x59, // note on off + 0xbd, 0x40, 0xbf, 0x40, // Log list + 0x15, 0xad, 0x5a, 0xdf, 0xa8, // offbit octets + }; + + + byte controlChange[] = { + 0x80, 0x61, 0x20, 0xa5, 0x7f, 0xc, + 0x73, 0x2d, 0xc5, 0xb1, 0x54, 0x00, 0x80, 0xbf, 0xb0, 0x7b, 0x00, 0x00, 0xb1, 0x7b, 0x00, 0x0, + 0xb2, 0x7b, 0x00, 0x00, 0xb3, 0x7b, 0x00, 0x00, 0xb4, 0x7b, 0x00, 0x00, 0xb5, 0x7b, 0x00, 0x0, + 0xb6, 0x7b, 0x00, 0x00, 0xb7, 0x7b, 0x00, 0x00, 0xb8, 0x7b, 0x00, 0x00, 0xb9, 0x7b, 0x00, 0x0, + 0xba, 0x7b, 0x00, 0x00, 0xbb, 0x7b, 0x00, 0x00, 0xbc, 0x7b, 0x00, 0x00, 0xbd, 0x7b, 0x00, 0x0, + 0xbe, 0x7b, 0x00, 0x00, 0xbf, 0x7b, 0x00, 0x00, 0xe0, 0x00, 0x40, 0x00, 0xe1, 0x00, 0x40, 0x0, + 0xe2, 0x00, 0x40, 0x00, 0xe3, 0x00, 0x40, 0x00, 0xe4, 0x00, 0x40, 0x00, 0xe5, 0x00, 0x40, 0x0, + 0xe6, 0x00, 0x40, 0x00, 0xe7, 0x00, 0x40, 0x00, 0xe8, 0x00, 0x40, 0x00, 0xe9, 0x00, 0x40, 0x0, + 0xea, 0x00, 0x40, 0x00, 0xeb, 0x00, 0x40, 0x00, 0xec, 0x00, 0x40, 0x00, 0xed, 0x00, 0x40, 0x0, + 0xee, 0x00, 0x40, 0x00, 0xef, 0x00, 0x40, 0x00, 0xb0, 0x40, 0x00, 0x00, 0xb1, 0x40, 0x00, 0x0, + 0xb2, 0x40, 0x00, 0x00, 0xb3, 0x40, 0x00, 0x00, 0xb4, 0x40, 0x00, 0x00, 0xb5, 0x40, 0x00, 0x0, + 0xb6, 0x40, 0x00, 0x00, 0xb7, 0x40, 0x00, 0x00, 0xb8, 0x40, 0x00, 0x00, 0xb9, 0x40, 0x00, 0x0, + 0xba, 0x40, 0x00, 0x00, 0xbb, 0x40, 0x00, 0x00, 0xbc, 0x40, 0x00, 0x00, 0xbd, 0x40, 0x00, 0x0, + 0xbe, 0x40, 0x00, 0x00, 0xbf, 0x40, 0x00 }; + + byte RTStart[] = { + 0x80, 0x61, 0x20, 0xa6, 0x7f, 0xc, 0x73, 0x66, 0xc5, 0xb1, 0x54, 0x00, 0x43, + 0xfa, 0x00, 0xf8, + 0x2f, 0x20, 0xa5, + 0x00, 0x0a, 0x5, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x08, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x0, 0x40, + 0x10, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x18, 0x0a, 0x50, 0x01, 0x4, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x20, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x28, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x30, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7, 0x00, 0x00, 0x40, + 0x38, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x40, 0x0a, 0x5, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x48, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x0, 0x40, + 0x50, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x58, 0x0a, 0x50, 0x01, 0x4, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x60, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x68, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40, + 0x70, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7, 0x00, 0x00, 0x40, + 0x78, 0x0a, 0x50, 0x01, 0x40, 0x00, 0x7b, 0x00, 0x00, 0x40 }; + + byte TCNote[] = { + 0x80, 0x61, 0x4e, 0x24, 0x82, 0x9f, 0xdc, 0x22, 0xc5, 0xb1, 0x54, 0x00, + 0xc0, 0x20, + 0xf8, 0x00, 0x90, 0x2b, 0x7f, 0x00, 0x34, 0x7f, 0x00, 0x35, 0x7f, 0x00, + 0x36, 0x7f, 0x00, 0x37, 0x7f, 0x00, 0x38, 0x7f, 0x00, 0x39, 0x7f, 0x00, + 0x3a, 0x7f, 0x00, 0x3b, 0x7f, 0x00, 0x3c, 0x7f, + 0x6f, 0x45, 0x85, 0x10, 0x05, 0x50, 0x00, 0x0f, + 0x80, 0x0f, 0x58, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0x80, 0x57, 0x10, 0x0f, 0xf8, 0x88, + 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0x90, 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, + 0x00, 0x80, 0x40, 0x98, 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0xa0, 0x0a, 0x50, + 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0xa8, 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, + 0x40, 0xb0, 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0xb8, 0x0a, 0x50, 0x81, 0xc0, + 0x00, 0xfb, 0x00, 0x80, 0x40, 0xc0, 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0xc8, + 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0xd0, 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, + 0x00, 0x80, 0x40, 0xd8, 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0xe0, 0x0a, 0x50, + 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0xe8, 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, + 0x40, 0xf0, 0x0a, 0x50, 0x81, 0xc0, 0x00, 0xfb, 0x00, 0x80, 0x40, 0xf8, 0x0a, 0x50, 0x81, 0xc0, + 0x00, 0xfb, 0x00, 0x80, 0x40 }; + + byte aaa[] = { + 0x80, 0x61, 0xa5, 0x05, 0x01, 0x08, 0x58, 0x2a, 0x34, 0xc7, 0xab, 0xfd, 0x4e, 0x80, 0x53, 0x00, 0x11, 0x35, 0x00, 0x8f, 0xff, 0xff, + 0xff, 0x00, 0x90, 0x4f, 0x40, 0x20, 0xa4, 0xdb, 0x00, 0x13, 0x08, 0x03, 0x3a, 0xb5, 0x7f, 0xcd, + 0x40, 0xd3, 0x40, 0x02, 0x10, 0x10, 0x10, 0x08, 0x00, 0xa9, 0x48, + }; + + + byte slecht[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; + + // write(noteOnOff, sizeof(noteOnOff)); + } + + + + if (port == 5005 && true) + { + // rtp-MIDI and AppleMIDI messages + } + + }; + + bool beginPacket(uint32_t, uint16_t) + { + return true; + } + + bool beginPacket(IPAddress, uint16_t) + { + return true; + } + + size_t parsePacket() + { + return _buffer.size(); + }; + + size_t available() + { + return _buffer.size(); + }; + + size_t read(byte* buffer, size_t size) + { + size = min(size, _buffer.size()); + + for (size_t i = 0; i < size; i++) + buffer[i] = _buffer.pop_front(); + + return size; + }; + + void write(uint8_t buffer) + { + _buffer.push_back(buffer); + }; + + void write(uint8_t* buffer, size_t size) + { + for (size_t i = 0; i < size; i++) + _buffer.push_back(buffer[i]); + }; + + void endPacket() { }; + void flush() + { + if (_port == 5004) + { + if (_buffer[0] == 0xff && _buffer[1] == 0xff && _buffer[2] == 'I' &&_buffer[3] == 'N') + { + _buffer.clear(); + + + byte u[] = { + 0xff, 0xff, + 0x4f, 0x4b, + 0x00, 0x00, 0x00, 0x02, + 0xb7, 0x06, 0x20, 0x30, + 0xda, 0x8d, 0xc5, 0x8a, + 0x4d, 0x61, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x20, 0x50, 0x72, 0x6f, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x53, 0x61, 0x6e, 0x64, 0x72, 0x61, 0x20, 0x56, 0x65, 0x72, 0x62, 0x65, 0x6b, 0x65, 0x6e, 0x20, 0x28, 0x32, 0x29, 0x00 }; + + + + + + byte r[] = { 0xff, 0xff, + 0x4f, 0x4b, + 0x00, 0x0, 0x00, 0x02, + 0xb7, 0x06, 0x20, 0x30, + 0xda, 0x8d, 0xc5, 0x8a, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6, 0x6e, 0x31, 0x2d, 0x42, 0x00 }; + write(u, sizeof(u)); + } + } + if (_port == 5005) + { + if (_buffer[0] == 0xff && _buffer[1] == 0xff && _buffer[2] == 'I' &&_buffer[3] == 'N') + { + _buffer.clear(); + byte r[] = { 0xff, 0xff, + 0x4f, 0x4b, + 0x00, 0x0, 0x00, 0x02, + 0xb7, 0x06, 0x20, 0x30, + 0xda, 0x8d, 0xc5, 0x8a, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6, 0x6e, 0x31, 0x2d, 0x42, 0x00 }; + write(r, sizeof(r)); + } + else if (_buffer[0] == 0xff && _buffer[1] == 0xff && _buffer[2] == 'C' &&_buffer[3] == 'K') + { + if (_buffer[8] == 0x00) + { + _buffer.clear(); + byte r[] = { 0xff, 0xff, + 0x43, 0x4b, + 0xda, 0x8d, 0xc5, 0x8a, + 0x01, + 0x65, 0x73, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x34, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x6c, 0x83, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + write(r, sizeof(r)); + } + else + _buffer.clear(); + } + } + }; + + void stop() { _buffer.clear(); }; + + uint32_t remoteIP() { return 1; } + uint16_t remotePort() { return _port; } +}; diff --git a/test/IPAddress.h b/test/IPAddress.h new file mode 100644 index 0000000..bcb1d0e --- /dev/null +++ b/test/IPAddress.h @@ -0,0 +1,12 @@ +#pragma once + +class IPAddress +{ +public: + IPAddress(){}; + IPAddress(const IPAddress& from){}; + IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet){}; + IPAddress(uint32_t address) { } + IPAddress(int address) { } + IPAddress(const uint8_t *address) {}; +}; diff --git a/test/NoteOn.cpp b/test/NoteOn.cpp new file mode 100644 index 0000000..1399dfa --- /dev/null +++ b/test/NoteOn.cpp @@ -0,0 +1,12 @@ +#define DEBUG 7 +#define APPLEMIDI_INITIATOR + +#include "../src/midi_bleTransport.h" + +void begin() +{ +} + +void loop() +{ +} diff --git a/test/TestParser.sln b/test/TestParser.sln new file mode 100644 index 0000000..02d7b49 --- /dev/null +++ b/test/TestParser.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29306.81 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestParser", "TestParser.vcxproj", "{25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Debug|x64.ActiveCfg = Debug|x64 + {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Debug|x64.Build.0 = Debug|x64 + {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Debug|x86.ActiveCfg = Debug|Win32 + {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Debug|x86.Build.0 = Debug|Win32 + {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Release|x64.ActiveCfg = Release|x64 + {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Release|x64.Build.0 = Release|x64 + {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Release|x86.ActiveCfg = Release|Win32 + {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E91ABBB5-98C4-4D25-B603-CA43FE39B327} + EndGlobalSection +EndGlobal diff --git a/test/TestParser.vcxproj b/test/TestParser.vcxproj new file mode 100644 index 0000000..05d5db0 --- /dev/null +++ b/test/TestParser.vcxproj @@ -0,0 +1,138 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E} + TestParser + 10.0 + + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + true + + + Console + + + + + Level3 + Disabled + true + true + + + Default + + + Console + + + + + Level3 + MaxSpeed + true + true + true + true + + + Console + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + Console + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/TestParser.vcxproj.filters b/test/TestParser.vcxproj.filters new file mode 100644 index 0000000..58698e7 --- /dev/null +++ b/test/TestParser.vcxproj.filters @@ -0,0 +1,26 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/test/TestParser.vcxproj.user b/test/TestParser.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/test/TestParser.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test/bleMidi.xcodeproj/project.pbxproj b/test/bleMidi.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ccf27c4 --- /dev/null +++ b/test/bleMidi.xcodeproj/project.pbxproj @@ -0,0 +1,278 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + CCE329C223C2040200A197D1 /* NoteOn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CCE329BF23C2040200A197D1 /* NoteOn.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + CCE329B323C2037C00A197D1 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + CC1A7D5F23F9378200206908 /* IPAddress.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = IPAddress.h; path = "/Users/bart/Documents/Arduino/libraries/Arduino-AppleMIDI-Library/test/IPAddress.h"; sourceTree = ""; }; + CCE329B523C2037C00A197D1 /* rtpMidi */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = rtpMidi; sourceTree = BUILT_PRODUCTS_DIR; }; + CCE329BF23C2040200A197D1 /* NoteOn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NoteOn.cpp; sourceTree = ""; }; + CCE329C023C2040200A197D1 /* Arduino.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Arduino.h; sourceTree = ""; }; + CCE329C123C2040200A197D1 /* Ethernet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ethernet.h; sourceTree = ""; }; + CCE329D623C28E9F00A197D1 /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = ../src; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CCE329B223C2037C00A197D1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + CCE329AC23C2037C00A197D1 = { + isa = PBXGroup; + children = ( + CCE329D623C28E9F00A197D1 /* src */, + CCE329C023C2040200A197D1 /* Arduino.h */, + CCE329C123C2040200A197D1 /* Ethernet.h */, + CC1A7D5F23F9378200206908 /* IPAddress.h */, + CCE329BF23C2040200A197D1 /* NoteOn.cpp */, + CCE329B623C2037C00A197D1 /* Products */, + ); + sourceTree = ""; + }; + CCE329B623C2037C00A197D1 /* Products */ = { + isa = PBXGroup; + children = ( + CCE329B523C2037C00A197D1 /* rtpMidi */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + CCE329B423C2037C00A197D1 /* rtpMidi */ = { + isa = PBXNativeTarget; + buildConfigurationList = CCE329BC23C2037C00A197D1 /* Build configuration list for PBXNativeTarget "rtpMidi" */; + buildPhases = ( + CCE329B123C2037C00A197D1 /* Sources */, + CCE329B223C2037C00A197D1 /* Frameworks */, + CCE329B323C2037C00A197D1 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = rtpMidi; + productName = rtpMidi; + productReference = CCE329B523C2037C00A197D1 /* rtpMidi */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CCE329AD23C2037C00A197D1 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1130; + ORGANIZATIONNAME = "Bart De Lathouwer"; + TargetAttributes = { + CCE329B423C2037C00A197D1 = { + CreatedOnToolsVersion = 11.3; + }; + }; + }; + buildConfigurationList = CCE329B023C2037C00A197D1 /* Build configuration list for PBXProject "bleMidi" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CCE329AC23C2037C00A197D1; + productRefGroup = CCE329B623C2037C00A197D1 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CCE329B423C2037C00A197D1 /* rtpMidi */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + CCE329B123C2037C00A197D1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CCE329C223C2040200A197D1 /* NoteOn.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + CCE329BA23C2037C00A197D1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + CCE329BB23C2037C00A197D1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + CCE329BD23C2037C00A197D1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + CCE329BE23C2037C00A197D1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + CCE329B023C2037C00A197D1 /* Build configuration list for PBXProject "bleMidi" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CCE329BA23C2037C00A197D1 /* Debug */, + CCE329BB23C2037C00A197D1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CCE329BC23C2037C00A197D1 /* Build configuration list for PBXNativeTarget "rtpMidi" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CCE329BD23C2037C00A197D1 /* Debug */, + CCE329BE23C2037C00A197D1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = CCE329AD23C2037C00A197D1 /* Project object */; +} diff --git a/test/bleMidi.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/test/bleMidi.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..4bed45a --- /dev/null +++ b/test/bleMidi.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/test/bleMidi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/test/bleMidi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/test/bleMidi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/test/bleMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/IDEFindNavigatorScopes.plist b/test/bleMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/test/bleMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/test/bleMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/UserInterfaceState.xcuserstate b/test/bleMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..a325218807fa843944beb26aecf225fa4a093ece GIT binary patch literal 207649 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�?pZ=Ij{SA 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

+ + + + + + + + diff --git a/test/bleMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcschemes/xcschememanagement.plist b/test/bleMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..47e8994 --- /dev/null +++ b/test/bleMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + rtpMidi.xcscheme_^#shared#^_ + + orderHint + 0 + + + +