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 0000000..a325218 Binary files /dev/null and b/test/bleMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/test/bleMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/test/bleMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..766f31f --- /dev/null +++ b/test/bleMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,24 @@ + + + + + + + + + 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 + + + +