Reverted Thru changes on branch release/4.0 (breaking thread/merge-safety).
This commit is contained in:
parent
cc9927fd50
commit
601ddb3773
34
src/MIDI.h
34
src/MIDI.h
|
|
@ -68,7 +68,7 @@ public:
|
||||||
inline void sendAfterTouch(DataByte inPressure,
|
inline void sendAfterTouch(DataByte inPressure,
|
||||||
Channel inChannel);
|
Channel inChannel);
|
||||||
|
|
||||||
inline void sendSysEx(unsigned inLength,
|
inline void sendSysEx(unsigned int inLength,
|
||||||
const byte* inArray,
|
const byte* inArray,
|
||||||
bool inArrayContainsBoundaries = false);
|
bool inArrayContainsBoundaries = false);
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ public:
|
||||||
DataByte inValuesNibble);
|
DataByte inValuesNibble);
|
||||||
inline void sendTimeCodeQuarterFrame(DataByte inData);
|
inline void sendTimeCodeQuarterFrame(DataByte inData);
|
||||||
|
|
||||||
inline void sendSongPosition(unsigned inBeats);
|
inline void sendSongPosition(unsigned int inBeats);
|
||||||
inline void sendSongSelect(DataByte inSongNumber);
|
inline void sendSongSelect(DataByte inSongNumber);
|
||||||
inline void sendTuneRequest();
|
inline void sendTuneRequest();
|
||||||
inline void sendRealTime(MidiType inType);
|
inline void sendRealTime(MidiType inType);
|
||||||
|
|
@ -109,7 +109,7 @@ public:
|
||||||
inline DataByte getData1() const;
|
inline DataByte getData1() const;
|
||||||
inline DataByte getData2() const;
|
inline DataByte getData2() const;
|
||||||
inline const byte* getSysExArray() const;
|
inline const byte* getSysExArray() const;
|
||||||
inline unsigned getSysExArrayLength() const;
|
inline unsigned int getSysExArrayLength() const;
|
||||||
inline bool check() const;
|
inline bool check() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -117,11 +117,8 @@ public:
|
||||||
inline void setInputChannel(Channel inChannel);
|
inline void setInputChannel(Channel inChannel);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static inline MidiType getTypeFromStatusByte(StatusByte inStatus);
|
static inline MidiType getTypeFromStatusByte(byte inStatus);
|
||||||
static inline byte getMessageLength(StatusByte inStatus);
|
|
||||||
static inline bool isChannelMessage(MidiType inType);
|
static inline bool isChannelMessage(MidiType inType);
|
||||||
static inline bool isSystemRealtimeMessage(MidiType inType);
|
|
||||||
static inline bool isSystemCommonMessage(MidiType inType);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool inputFilter(Channel inChannel);
|
bool inputFilter(Channel inChannel);
|
||||||
|
|
@ -133,8 +130,8 @@ private:
|
||||||
Channel mInputChannel;
|
Channel mInputChannel;
|
||||||
|
|
||||||
byte mPendingMessage[3]; // SysEx are dumped into mMessage directly.
|
byte mPendingMessage[3]; // SysEx are dumped into mMessage directly.
|
||||||
unsigned mPendingMessageExpectedLenght;
|
unsigned int mPendingMessageExpectedLenght;
|
||||||
unsigned mPendingMessageIndex; // Extended to unsigned for larger SysEx payloads.
|
unsigned int mPendingMessageIndex; // Extended to unsigned int for larger SysEx payloads.
|
||||||
Message mMessage;
|
Message mMessage;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -153,7 +150,7 @@ public:
|
||||||
inline void setHandlePitchBend(void (*fptr)(byte channel, int bend));
|
inline void setHandlePitchBend(void (*fptr)(byte channel, int bend));
|
||||||
inline void setHandleSystemExclusive(void (*fptr)(byte * array, byte size));
|
inline void setHandleSystemExclusive(void (*fptr)(byte * array, byte size));
|
||||||
inline void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data));
|
inline void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data));
|
||||||
inline void setHandleSongPosition(void (*fptr)(unsigned beats));
|
inline void setHandleSongPosition(void (*fptr)(unsigned int beats));
|
||||||
inline void setHandleSongSelect(void (*fptr)(byte songnumber));
|
inline void setHandleSongSelect(void (*fptr)(byte songnumber));
|
||||||
inline void setHandleTuneRequest(void (*fptr)(void));
|
inline void setHandleTuneRequest(void (*fptr)(void));
|
||||||
inline void setHandleClock(void (*fptr)(void));
|
inline void setHandleClock(void (*fptr)(void));
|
||||||
|
|
@ -178,7 +175,7 @@ private:
|
||||||
void (*mPitchBendCallback)(byte channel, int);
|
void (*mPitchBendCallback)(byte channel, int);
|
||||||
void (*mSystemExclusiveCallback)(byte * array, byte size);
|
void (*mSystemExclusiveCallback)(byte * array, byte size);
|
||||||
void (*mTimeCodeQuarterFrameCallback)(byte data);
|
void (*mTimeCodeQuarterFrameCallback)(byte data);
|
||||||
void (*mSongPositionCallback)(unsigned beats);
|
void (*mSongPositionCallback)(unsigned int beats);
|
||||||
void (*mSongSelectCallback)(byte songnumber);
|
void (*mSongSelectCallback)(byte songnumber);
|
||||||
void (*mTuneRequestCallback)(void);
|
void (*mTuneRequestCallback)(void);
|
||||||
void (*mClockCallback)(void);
|
void (*mClockCallback)(void);
|
||||||
|
|
@ -199,19 +196,20 @@ private:
|
||||||
#if MIDI_BUILD_THRU
|
#if MIDI_BUILD_THRU
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline void turnThruOn(ThruFlags inThruFlags = ThruFilterFlags::all);
|
inline MidiFilterMode getFilterMode() const;
|
||||||
|
inline bool getThruState() const;
|
||||||
|
|
||||||
|
inline void turnThruOn(MidiFilterMode inThruFilterMode = Full);
|
||||||
inline void turnThruOff();
|
inline void turnThruOff();
|
||||||
|
inline void setThruFilterMode(MidiFilterMode inThruFilterMode);
|
||||||
|
|
||||||
inline void setThruFilterMode(ThruFlags inFlags);
|
|
||||||
inline ThruFlags getThruFilterMode() const;
|
|
||||||
inline bool isThruOn() const;
|
|
||||||
inline bool hasThruFlag(byte inFlag) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline bool shouldMessageBeForwarded(StatusByte inStatus) const;
|
void thruFilter(byte inChannel);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ThruFlags mThruFlags;
|
bool mThruActivated : 1;
|
||||||
|
MidiFilterMode mThruFilterMode : 7;
|
||||||
|
|
||||||
#endif // MIDI_BUILD_THRU
|
#endif // MIDI_BUILD_THRU
|
||||||
|
|
||||||
|
|
|
||||||
533
src/MIDI.hpp
533
src/MIDI.hpp
|
|
@ -70,7 +70,7 @@ void MidiInterface<SerialPort>::begin(Channel inChannel)
|
||||||
|
|
||||||
mRunningStatus_TX = InvalidType;
|
mRunningStatus_TX = InvalidType;
|
||||||
|
|
||||||
#endif
|
#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS
|
||||||
|
|
||||||
|
|
||||||
#if MIDI_BUILD_INPUT
|
#if MIDI_BUILD_INPUT
|
||||||
|
|
@ -86,14 +86,16 @@ void MidiInterface<SerialPort>::begin(Channel inChannel)
|
||||||
mMessage.data1 = 0;
|
mMessage.data1 = 0;
|
||||||
mMessage.data2 = 0;
|
mMessage.data2 = 0;
|
||||||
|
|
||||||
#endif
|
#endif // MIDI_BUILD_INPUT
|
||||||
|
|
||||||
|
|
||||||
#if MIDI_BUILD_THRU
|
#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru
|
||||||
|
|
||||||
mThruFlags = ThruFilterFlags::all;
|
mThruFilterMode = Full;
|
||||||
|
mThruActivated = true;
|
||||||
|
|
||||||
|
#endif // Thru
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -266,10 +268,11 @@ template<class SerialPort>
|
||||||
void MidiInterface<SerialPort>::sendPitchBend(int inPitchValue,
|
void MidiInterface<SerialPort>::sendPitchBend(int inPitchValue,
|
||||||
Channel inChannel)
|
Channel inChannel)
|
||||||
{
|
{
|
||||||
const unsigned bend = inPitchValue - MIDI_PITCHBEND_MIN;
|
const unsigned int bend = inPitchValue - MIDI_PITCHBEND_MIN;
|
||||||
send(PitchBend, (bend & 0x7F), (bend >> 7) & 0x7F, inChannel);
|
send(PitchBend, (bend & 0x7F), (bend >> 7) & 0x7F, inChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! \brief Send a Pitch Bend message using a floating point value.
|
/*! \brief Send a Pitch Bend message using a floating point value.
|
||||||
\param inPitchValue The amount of bend to send (in a floating point format),
|
\param inPitchValue The amount of bend to send (in a floating point format),
|
||||||
between -1.0f (maximum downwards bend)
|
between -1.0f (maximum downwards bend)
|
||||||
|
|
@ -290,11 +293,11 @@ void MidiInterface<SerialPort>::sendPitchBend(double inPitchValue,
|
||||||
\param inArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes
|
\param inArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes
|
||||||
(start & stop SysEx) will NOT be sent
|
(start & stop SysEx) will NOT be sent
|
||||||
(and therefore must be included in the array).
|
(and therefore must be included in the array).
|
||||||
default value for inArrayContainsBoundaries is set to 'false' for compatibility
|
default value for ArrayContainsBoundaries is set to 'false' for compatibility
|
||||||
with previous versions of the library.
|
with previous versions of the library.
|
||||||
*/
|
*/
|
||||||
template<class SerialPort>
|
template<class SerialPort>
|
||||||
void MidiInterface<SerialPort>::sendSysEx(unsigned inLength,
|
void MidiInterface<SerialPort>::sendSysEx(unsigned int inLength,
|
||||||
const byte* inArray,
|
const byte* inArray,
|
||||||
bool inArrayContainsBoundaries)
|
bool inArrayContainsBoundaries)
|
||||||
{
|
{
|
||||||
|
|
@ -302,14 +305,14 @@ void MidiInterface<SerialPort>::sendSysEx(unsigned inLength,
|
||||||
{
|
{
|
||||||
mSerial.write(0xF0);
|
mSerial.write(0xF0);
|
||||||
|
|
||||||
for (unsigned i = 0; i < inLength; ++i)
|
for (unsigned int i = 0; i < inLength; ++i)
|
||||||
mSerial.write(inArray[i]);
|
mSerial.write(inArray[i]);
|
||||||
|
|
||||||
mSerial.write(0xF7);
|
mSerial.write(0xF7);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < inLength; ++i)
|
for (unsigned int i = 0; i < inLength; ++i)
|
||||||
mSerial.write(inArray[i]);
|
mSerial.write(inArray[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -364,7 +367,7 @@ void MidiInterface<SerialPort>::sendTimeCodeQuarterFrame(DataByte inData)
|
||||||
\param inBeats The number of beats since the start of the song.
|
\param inBeats The number of beats since the start of the song.
|
||||||
*/
|
*/
|
||||||
template<class SerialPort>
|
template<class SerialPort>
|
||||||
void MidiInterface<SerialPort>::sendSongPosition(unsigned inBeats)
|
void MidiInterface<SerialPort>::sendSongPosition(unsigned int inBeats)
|
||||||
{
|
{
|
||||||
mSerial.write((byte)SongPosition);
|
mSerial.write((byte)SongPosition);
|
||||||
mSerial.write(inBeats & 0x7F);
|
mSerial.write(inBeats & 0x7F);
|
||||||
|
|
@ -397,9 +400,20 @@ void MidiInterface<SerialPort>::sendSongSelect(DataByte inSongNumber)
|
||||||
template<class SerialPort>
|
template<class SerialPort>
|
||||||
void MidiInterface<SerialPort>::sendRealTime(MidiType inType)
|
void MidiInterface<SerialPort>::sendRealTime(MidiType inType)
|
||||||
{
|
{
|
||||||
if (isSystemRealtimeMessage(inType) || inType == TuneRequest)
|
switch (inType)
|
||||||
{
|
{
|
||||||
mSerial.write((byte)inType);
|
case TuneRequest: // Not really real-time, but one byte anyway.
|
||||||
|
case Clock:
|
||||||
|
case Start:
|
||||||
|
case Stop:
|
||||||
|
case Continue:
|
||||||
|
case ActiveSensing:
|
||||||
|
case SystemReset:
|
||||||
|
mSerial.write((byte)inType);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Invalid Real Time marker
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not cancel Running Status for real-time messages as they can be
|
// Do not cancel Running Status for real-time messages as they can be
|
||||||
|
|
@ -456,13 +470,22 @@ bool MidiInterface<SerialPort>::read(Channel inChannel)
|
||||||
if (inChannel >= MIDI_CHANNEL_OFF)
|
if (inChannel >= MIDI_CHANNEL_OFF)
|
||||||
return false; // MIDI Input disabled.
|
return false; // MIDI Input disabled.
|
||||||
|
|
||||||
if (parse() && inputFilter(inChannel))
|
if (parse())
|
||||||
{
|
{
|
||||||
#if MIDI_USE_CALLBACKS
|
if (inputFilter(inChannel))
|
||||||
launchCallback();
|
{
|
||||||
|
|
||||||
|
#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU)
|
||||||
|
thruFilter(inChannel);
|
||||||
#endif
|
#endif
|
||||||
return true;
|
|
||||||
|
#if MIDI_USE_CALLBACKS
|
||||||
|
launchCallback();
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -485,7 +508,7 @@ bool MidiInterface<SerialPort>::parse()
|
||||||
// - Look for other bytes in buffer, call parser recursively,
|
// - Look for other bytes in buffer, call parser recursively,
|
||||||
// until the message is assembled or the buffer is empty.
|
// until the message is assembled or the buffer is empty.
|
||||||
// Else, add the extracted byte to the pending message, and check validity.
|
// Else, add the extracted byte to the pending message, and check validity.
|
||||||
// When the message is recomposed, notify the caller.
|
// When the message is done, store it.
|
||||||
|
|
||||||
const byte extracted = mSerial.read();
|
const byte extracted = mSerial.read();
|
||||||
|
|
||||||
|
|
@ -511,10 +534,10 @@ bool MidiInterface<SerialPort>::parse()
|
||||||
// so the running status does not apply here.
|
// so the running status does not apply here.
|
||||||
// It will be updated upon completion of this message.
|
// It will be updated upon completion of this message.
|
||||||
|
|
||||||
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1))
|
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1))
|
||||||
{
|
{
|
||||||
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
|
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
|
||||||
mMessage.channel = (mPendingMessage[0] & 0x0F) + 1;
|
mMessage.channel = (mPendingMessage[0] & 0x0F)+1;
|
||||||
mMessage.data1 = mPendingMessage[1];
|
mMessage.data1 = mPendingMessage[1];
|
||||||
|
|
||||||
// Save data2 only if applicable
|
// Save data2 only if applicable
|
||||||
|
|
@ -530,57 +553,71 @@ bool MidiInterface<SerialPort>::parse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mPendingMessageExpectedLenght = getMessageLength(mPendingMessage[0]);
|
switch (getTypeFromStatusByte(mPendingMessage[0]))
|
||||||
|
|
||||||
if (mPendingMessageExpectedLenght == 1)
|
|
||||||
{
|
{
|
||||||
// RealTime and 1 byte messages -> send & handle rightaway
|
// 1 byte messages
|
||||||
if (shouldMessageBeForwarded(mPendingMessage[0]))
|
case Start:
|
||||||
{
|
case Continue:
|
||||||
mSerial.write(mPendingMessage[0]);
|
case Stop:
|
||||||
}
|
case Clock:
|
||||||
|
case ActiveSensing:
|
||||||
|
case SystemReset:
|
||||||
|
case TuneRequest:
|
||||||
|
// Handle the message type directly here.
|
||||||
|
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
|
||||||
|
mMessage.channel = 0;
|
||||||
|
mMessage.data1 = 0;
|
||||||
|
mMessage.data2 = 0;
|
||||||
|
mMessage.valid = true;
|
||||||
|
|
||||||
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
|
// \fix Running Status broken when receiving Clock messages.
|
||||||
mMessage.channel = 0;
|
// Do not reset all input attributes, Running Status must remain unchanged.
|
||||||
mMessage.data1 = 0;
|
//resetInput();
|
||||||
mMessage.data2 = 0;
|
|
||||||
mMessage.valid = true;
|
|
||||||
|
|
||||||
// \fix Running Status broken when receiving Clock messages.
|
// We still need to reset these
|
||||||
// Do not reset all input attributes, Running Status must remain unchanged.
|
mPendingMessageIndex = 0;
|
||||||
//resetInput();
|
mPendingMessageExpectedLenght = 0;
|
||||||
|
|
||||||
// We still need to reset these
|
return true;
|
||||||
mPendingMessageIndex = 0;
|
break;
|
||||||
mPendingMessageExpectedLenght = 0;
|
|
||||||
|
|
||||||
return true;
|
// 2 bytes messages
|
||||||
}
|
case ProgramChange:
|
||||||
else if (mPendingMessageExpectedLenght == 0xff)
|
case AfterTouchChannel:
|
||||||
{
|
case TimeCodeQuarterFrame:
|
||||||
// SysEx: The message can be any length
|
case SongSelect:
|
||||||
// between 3 and MIDI_SYSEX_ARRAY_SIZE bytes
|
mPendingMessageExpectedLenght = 2;
|
||||||
mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE;
|
break;
|
||||||
mRunningStatus_RX = InvalidType;
|
|
||||||
mMessage.sysexArray[0] = SystemExclusive;
|
|
||||||
|
|
||||||
|
// 3 bytes messages
|
||||||
|
case NoteOn:
|
||||||
|
case NoteOff:
|
||||||
|
case ControlChange:
|
||||||
|
case PitchBend:
|
||||||
|
case AfterTouchPoly:
|
||||||
|
case SongPosition:
|
||||||
|
mPendingMessageExpectedLenght = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
case SystemExclusive:
|
||||||
else if (mPendingMessageExpectedLenght == 0)
|
// The message can be any lenght
|
||||||
{
|
// between 3 and MIDI_SYSEX_ARRAY_SIZE bytes
|
||||||
// Parse error
|
mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE;
|
||||||
resetInput();
|
mRunningStatus_RX = InvalidType;
|
||||||
return false;
|
mMessage.sysexArray[0] = SystemExclusive;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InvalidType:
|
||||||
|
default:
|
||||||
|
// This is obviously wrong. Let's get the hell out'a here.
|
||||||
|
resetInput();
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then update the index of the pending message.
|
// Then update the index of the pending message.
|
||||||
mPendingMessageIndex++;
|
mPendingMessageIndex++;
|
||||||
|
|
||||||
if (shouldMessageBeForwarded(mPendingMessage[0]))
|
|
||||||
{
|
|
||||||
mSerial.write(mPendingMessage[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if USE_1BYTE_PARSING
|
#if USE_1BYTE_PARSING
|
||||||
// Message is not complete.
|
// Message is not complete.
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -589,6 +626,7 @@ bool MidiInterface<SerialPort>::parse()
|
||||||
// to parse the rest of the message.
|
// to parse the rest of the message.
|
||||||
return parse();
|
return parse();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -597,74 +635,70 @@ bool MidiInterface<SerialPort>::parse()
|
||||||
{
|
{
|
||||||
// Reception of status bytes in the middle of an uncompleted message
|
// Reception of status bytes in the middle of an uncompleted message
|
||||||
// are allowed only for interleaved Real Time message or EOX
|
// are allowed only for interleaved Real Time message or EOX
|
||||||
if (isSystemRealtimeMessage(getTypeFromStatusByte(extracted)))
|
switch (extracted)
|
||||||
{
|
{
|
||||||
if (shouldMessageBeForwarded(extracted))
|
case Clock:
|
||||||
{
|
case Start:
|
||||||
mSerial.write(extracted);
|
case Continue:
|
||||||
}
|
case Stop:
|
||||||
|
case ActiveSensing:
|
||||||
|
case SystemReset:
|
||||||
|
|
||||||
// Here we will have to extract the one-byte message,
|
// Here we will have to extract the one-byte message,
|
||||||
// pass it to the structure for being read outside
|
// pass it to the structure for being read outside
|
||||||
// the MIDI class, and recompose the message it was
|
// the MIDI class, and recompose the message it was
|
||||||
// interleaved into. Oh, and without killing the running status..
|
// interleaved into. Oh, and without killing the running status..
|
||||||
// This is done by leaving the pending message as is,
|
// This is done by leaving the pending message as is,
|
||||||
// it will be completed on next calls.
|
// it will be completed on next calls.
|
||||||
|
|
||||||
mMessage.type = (MidiType)extracted;
|
mMessage.type = (MidiType)extracted;
|
||||||
mMessage.data1 = 0;
|
mMessage.data1 = 0;
|
||||||
mMessage.data2 = 0;
|
mMessage.data2 = 0;
|
||||||
mMessage.channel = 0;
|
|
||||||
mMessage.valid = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (extracted == 0xF7)
|
|
||||||
{
|
|
||||||
if (mMessage.sysexArray[0] == SystemExclusive)
|
|
||||||
{
|
|
||||||
if (shouldMessageBeForwarded(SystemExclusive))
|
|
||||||
{
|
|
||||||
mSerial.write(extracted);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the last byte (EOX)
|
|
||||||
mMessage.sysexArray[mPendingMessageIndex++] = 0xF7;
|
|
||||||
|
|
||||||
mMessage.type = SystemExclusive;
|
|
||||||
|
|
||||||
// Get length
|
|
||||||
mMessage.data1 = mPendingMessageIndex & 0xFF;
|
|
||||||
mMessage.data2 = mPendingMessageIndex >> 8;
|
|
||||||
mMessage.channel = 0;
|
mMessage.channel = 0;
|
||||||
mMessage.valid = true;
|
mMessage.valid = true;
|
||||||
|
|
||||||
resetInput();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
break;
|
||||||
else
|
|
||||||
{
|
// End of Exclusive
|
||||||
// Parse error: unexpected status byte
|
case 0xF7:
|
||||||
|
if (mMessage.sysexArray[0] == SystemExclusive)
|
||||||
|
{
|
||||||
|
// Store the last byte (EOX)
|
||||||
|
mMessage.sysexArray[mPendingMessageIndex++] = 0xF7;
|
||||||
|
|
||||||
|
mMessage.type = SystemExclusive;
|
||||||
|
|
||||||
|
// Get length
|
||||||
|
mMessage.data1 = mPendingMessageIndex & 0xFF;
|
||||||
|
mMessage.data2 = mPendingMessageIndex >> 8;
|
||||||
|
mMessage.channel = 0;
|
||||||
|
mMessage.valid = true;
|
||||||
|
|
||||||
|
resetInput();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Well well well.. error.
|
||||||
|
resetInput();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add extracted data byte to pending message
|
// Add extracted data byte to pending message
|
||||||
if (mPendingMessage[0] == SystemExclusive)
|
if (mPendingMessage[0] == SystemExclusive)
|
||||||
{
|
|
||||||
mMessage.sysexArray[mPendingMessageIndex] = extracted;
|
mMessage.sysexArray[mPendingMessageIndex] = extracted;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
mPendingMessage[mPendingMessageIndex] = extracted;
|
mPendingMessage[mPendingMessageIndex] = extracted;
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldMessageBeForwarded(mPendingMessage[0]))
|
|
||||||
{
|
|
||||||
mSerial.write(extracted);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we are going to check if we have reached the end of the message
|
// Now we are going to check if we have reached the end of the message
|
||||||
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1))
|
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1))
|
||||||
{
|
{
|
||||||
// "FML" case: fall down here with an overflown SysEx..
|
// "FML" case: fall down here with an overflown SysEx..
|
||||||
// This means we received the last possible data byte that can fit
|
// This means we received the last possible data byte that can fit
|
||||||
|
|
@ -678,7 +712,7 @@ bool MidiInterface<SerialPort>::parse()
|
||||||
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
|
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
|
||||||
|
|
||||||
if (isChannelMessage(mMessage.type))
|
if (isChannelMessage(mMessage.type))
|
||||||
mMessage.channel = (mPendingMessage[0] & 0x0F) + 1;
|
mMessage.channel = (mPendingMessage[0] & 0x0F)+1;
|
||||||
else
|
else
|
||||||
mMessage.channel = 0;
|
mMessage.channel = 0;
|
||||||
|
|
||||||
|
|
@ -743,17 +777,30 @@ bool MidiInterface<SerialPort>::inputFilter(Channel inChannel)
|
||||||
{
|
{
|
||||||
// This method handles recognition of channel
|
// This method handles recognition of channel
|
||||||
// (to know if the message is destinated to the Arduino)
|
// (to know if the message is destinated to the Arduino)
|
||||||
|
|
||||||
if (mMessage.type == InvalidType)
|
if (mMessage.type == InvalidType)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (isChannelMessage(mMessage.type))
|
// First, check if the received message is Channel
|
||||||
|
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
|
||||||
{
|
{
|
||||||
return (mInputChannel == MIDI_CHANNEL_OMNI ||
|
// Then we need to know if we listen to it
|
||||||
mInputChannel == mMessage.channel);
|
if ((mMessage.channel == mInputChannel) ||
|
||||||
|
(mInputChannel == MIDI_CHANNEL_OMNI))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We don't listen to this channel
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// System messages are always received
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other message types are always received.
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private method: reset input attributes
|
// Private method: reset input attributes
|
||||||
|
|
@ -812,15 +859,15 @@ const byte* MidiInterface<SerialPort>::getSysExArray() const
|
||||||
return mMessage.sysexArray;
|
return mMessage.sysexArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! \brief Get the length of the System Exclusive array.
|
/*! \brief Get the lenght of the System Exclusive array.
|
||||||
|
|
||||||
It is coded using data1 as LSB and data2 as MSB.
|
It is coded using data1 as LSB and data2 as MSB.
|
||||||
\return The array's length, in bytes.
|
\return The array's length, in bytes.
|
||||||
*/
|
*/
|
||||||
template<class SerialPort>
|
template<class SerialPort>
|
||||||
unsigned MidiInterface<SerialPort>::getSysExArrayLength() const
|
unsigned int MidiInterface<SerialPort>::getSysExArrayLength() const
|
||||||
{
|
{
|
||||||
const unsigned size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1;
|
const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1;
|
||||||
return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size;
|
return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -857,7 +904,7 @@ void MidiInterface<SerialPort>::setInputChannel(Channel inChannel)
|
||||||
made public so you can handle MidiTypes more easily.
|
made public so you can handle MidiTypes more easily.
|
||||||
*/
|
*/
|
||||||
template<class SerialPort>
|
template<class SerialPort>
|
||||||
MidiType MidiInterface<SerialPort>::getTypeFromStatusByte(StatusByte inStatus)
|
MidiType MidiInterface<SerialPort>::getTypeFromStatusByte(byte inStatus)
|
||||||
{
|
{
|
||||||
if ((inStatus < 0x80) ||
|
if ((inStatus < 0x80) ||
|
||||||
(inStatus == 0xF4) ||
|
(inStatus == 0xF4) ||
|
||||||
|
|
@ -868,38 +915,6 @@ MidiType MidiInterface<SerialPort>::getTypeFromStatusByte(StatusByte inStatus)
|
||||||
else return (MidiType)inStatus;
|
else return (MidiType)inStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class SerialPort>
|
|
||||||
byte MidiInterface<SerialPort>::getMessageLength(StatusByte inStatus)
|
|
||||||
{
|
|
||||||
const MidiType type = getTypeFromStatusByte(inStatus);
|
|
||||||
if (isSystemRealtimeMessage(type) || type == TuneRequest)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if (type == NoteOn ||
|
|
||||||
type == NoteOff ||
|
|
||||||
type == ControlChange ||
|
|
||||||
type == PitchBend ||
|
|
||||||
type == AfterTouchPoly ||
|
|
||||||
type == SongPosition)
|
|
||||||
{
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
else if (type == ProgramChange ||
|
|
||||||
type == AfterTouchChannel ||
|
|
||||||
type == TimeCodeQuarterFrame ||
|
|
||||||
type == SongSelect)
|
|
||||||
{
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
else if (type == SystemExclusive)
|
|
||||||
{
|
|
||||||
return 0xff; // SysEx messages can have variable length
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; // Unknown message type
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class SerialPort>
|
template<class SerialPort>
|
||||||
bool MidiInterface<SerialPort>::isChannelMessage(MidiType inType)
|
bool MidiInterface<SerialPort>::isChannelMessage(MidiType inType)
|
||||||
{
|
{
|
||||||
|
|
@ -912,26 +927,6 @@ bool MidiInterface<SerialPort>::isChannelMessage(MidiType inType)
|
||||||
inType == ProgramChange);
|
inType == ProgramChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class SerialPort>
|
|
||||||
bool MidiInterface<SerialPort>::isSystemRealtimeMessage(MidiType inType)
|
|
||||||
{
|
|
||||||
return inType == Clock ||
|
|
||||||
inType == Start ||
|
|
||||||
inType == Stop ||
|
|
||||||
inType == Continue ||
|
|
||||||
inType == ActiveSensing ||
|
|
||||||
inType == SystemReset;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class SerialPort>
|
|
||||||
bool MidiInterface<SerialPort>::isSystemCommonMessage(MidiType inType)
|
|
||||||
{
|
|
||||||
return inType == TimeCodeQuarterFrame ||
|
|
||||||
inType == SongPosition ||
|
|
||||||
inType == SongSelect ||
|
|
||||||
inType == TuneRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
#if MIDI_USE_CALLBACKS
|
#if MIDI_USE_CALLBACKS
|
||||||
|
|
@ -949,7 +944,7 @@ template<class SerialPort> void MidiInterface<SerialPort>::setHandleAfterTouchCh
|
||||||
template<class SerialPort> void MidiInterface<SerialPort>::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; }
|
template<class SerialPort> void MidiInterface<SerialPort>::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; }
|
||||||
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSystemExclusive(void (*fptr)(byte* array, byte size)) { mSystemExclusiveCallback = fptr; }
|
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSystemExclusive(void (*fptr)(byte* array, byte size)) { mSystemExclusiveCallback = fptr; }
|
||||||
template<class SerialPort> void MidiInterface<SerialPort>::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; }
|
template<class SerialPort> void MidiInterface<SerialPort>::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; }
|
||||||
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSongPosition(void (*fptr)(unsigned beats)) { mSongPositionCallback = fptr; }
|
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; }
|
||||||
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; }
|
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; }
|
||||||
template<class SerialPort> void MidiInterface<SerialPort>::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; }
|
template<class SerialPort> void MidiInterface<SerialPort>::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; }
|
||||||
template<class SerialPort> void MidiInterface<SerialPort>::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; }
|
template<class SerialPort> void MidiInterface<SerialPort>::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; }
|
||||||
|
|
@ -1046,84 +1041,160 @@ void MidiInterface<SerialPort>::launchCallback()
|
||||||
// Thru
|
// Thru
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
#if MIDI_BUILD_THRU
|
#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU)
|
||||||
|
|
||||||
/*! \addtogroup thru
|
/*! \addtogroup thru
|
||||||
@{
|
@{
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*! \brief Set the filter for thru mirroring
|
||||||
|
\param inThruFilterMode a filter mode
|
||||||
|
|
||||||
|
@see MidiFilterMode
|
||||||
|
*/
|
||||||
template<class SerialPort>
|
template<class SerialPort>
|
||||||
void MidiInterface<SerialPort>::turnThruOn(ThruFlags inFlags)
|
void MidiInterface<SerialPort>::setThruFilterMode(MidiFilterMode inThruFilterMode)
|
||||||
{
|
{
|
||||||
mThruFlags = inFlags;
|
mThruFilterMode = inThruFilterMode;
|
||||||
|
if (mThruFilterMode != Off)
|
||||||
|
mThruActivated = true;
|
||||||
|
else
|
||||||
|
mThruActivated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class SerialPort>
|
||||||
|
MidiFilterMode MidiInterface<SerialPort>::getFilterMode() const
|
||||||
|
{
|
||||||
|
return mThruFilterMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class SerialPort>
|
||||||
|
bool MidiInterface<SerialPort>::getThruState() const
|
||||||
|
{
|
||||||
|
return mThruActivated;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class SerialPort>
|
||||||
|
void MidiInterface<SerialPort>::turnThruOn(MidiFilterMode inThruFilterMode)
|
||||||
|
{
|
||||||
|
mThruActivated = true;
|
||||||
|
mThruFilterMode = inThruFilterMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class SerialPort>
|
template<class SerialPort>
|
||||||
void MidiInterface<SerialPort>::turnThruOff()
|
void MidiInterface<SerialPort>::turnThruOff()
|
||||||
{
|
{
|
||||||
mThruFlags = ThruFilterFlags::off;
|
mThruActivated = false;
|
||||||
}
|
mThruFilterMode = Off;
|
||||||
|
|
||||||
/*! \brief Set the filter for thru mirroring
|
|
||||||
\param inThruFilterMode a filter mode
|
|
||||||
|
|
||||||
@see ThruFilterFlags
|
|
||||||
*/
|
|
||||||
template<class SerialPort>
|
|
||||||
void MidiInterface<SerialPort>::setThruFilterMode(ThruFlags inFlags)
|
|
||||||
{
|
|
||||||
mThruFlags = inFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class SerialPort>
|
|
||||||
ThruFlags MidiInterface<SerialPort>::getThruFilterMode() const
|
|
||||||
{
|
|
||||||
return mThruFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class SerialPort>
|
|
||||||
bool MidiInterface<SerialPort>::isThruOn() const
|
|
||||||
{
|
|
||||||
return mThruFlags != ThruFilterFlags::off;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class SerialPort>
|
|
||||||
bool MidiInterface<SerialPort>::hasThruFlag(byte inFlag) const
|
|
||||||
{
|
|
||||||
return mThruFlags & inFlag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! @} */ // End of doc group MIDI Thru
|
/*! @} */ // End of doc group MIDI Thru
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// This method is called upon reception of a message
|
||||||
|
// and takes care of Thru filtering and sending.
|
||||||
template<class SerialPort>
|
template<class SerialPort>
|
||||||
bool MidiInterface<SerialPort>::shouldMessageBeForwarded(StatusByte inStatus) const
|
void MidiInterface<SerialPort>::thruFilter(Channel inChannel)
|
||||||
{
|
{
|
||||||
const MidiType type = getTypeFromStatusByte(inStatus);
|
|
||||||
if (isChannelMessage(type))
|
/*
|
||||||
|
This method handles Soft-Thru filtering.
|
||||||
|
|
||||||
|
Soft-Thru filtering:
|
||||||
|
- 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
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If the feature is disabled, don't do anything.
|
||||||
|
if (!mThruActivated || (mThruFilterMode == Off))
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
// First, check if the received message is Channel
|
||||||
|
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
|
||||||
{
|
{
|
||||||
const Channel channel = (inStatus & 0x0F) + 1;
|
const bool filter_condition = ((mMessage.channel == mInputChannel) ||
|
||||||
const bool forwardSame = hasThruFlag(ThruFilterFlags::channelSame);
|
(mInputChannel == MIDI_CHANNEL_OMNI));
|
||||||
const bool forwardDiff = hasThruFlag(ThruFilterFlags::channelDifferent);
|
|
||||||
return (forwardSame && mInputChannel == channel) ||
|
// Now let's pass it to the output
|
||||||
(forwardDiff && mInputChannel != channel);
|
switch (mThruFilterMode)
|
||||||
}
|
{
|
||||||
else if (isSystemRealtimeMessage(type))
|
case Full:
|
||||||
{
|
send(mMessage.type,
|
||||||
return hasThruFlag(ThruFilterFlags::systemRealtime);
|
mMessage.data1,
|
||||||
}
|
mMessage.data2,
|
||||||
else if (isSystemCommonMessage(type))
|
mMessage.channel);
|
||||||
{
|
return;
|
||||||
return hasThruFlag(ThruFilterFlags::systemCommon);
|
break;
|
||||||
}
|
case SameChannel:
|
||||||
else if (type == SystemExclusive)
|
if (filter_condition)
|
||||||
{
|
{
|
||||||
return hasThruFlag(ThruFilterFlags::systemExclusive);
|
send(mMessage.type,
|
||||||
|
mMessage.data1,
|
||||||
|
mMessage.data2,
|
||||||
|
mMessage.channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DifferentChannel:
|
||||||
|
if (!filter_condition)
|
||||||
|
{
|
||||||
|
send(mMessage.type,
|
||||||
|
mMessage.data1,
|
||||||
|
mMessage.data2,
|
||||||
|
mMessage.channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Off:
|
||||||
|
// Do nothing.
|
||||||
|
// Technically it's impossible to get there because
|
||||||
|
// the case was already tested earlier.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Unknown message or junk
|
// Send the message to the output
|
||||||
return hasThruFlag(ThruFilterFlags::junk);
|
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);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SystemExclusive:
|
||||||
|
// Send SysEx (0xF0 and 0xF7 are included in the buffer)
|
||||||
|
sendSysEx(mMessage.data1,mMessage.sysexArray,true);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SongSelect:
|
||||||
|
sendSongSelect(mMessage.data1);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SongPosition:
|
||||||
|
sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7));
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeCodeQuarterFrame:
|
||||||
|
sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,31 +61,15 @@ enum MidiType
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
/*! Enumeration of Thru filter modes
|
/*! Enumeration of Thru filter modes */
|
||||||
@see setThruFilterMode
|
enum MidiFilterMode
|
||||||
*/
|
|
||||||
struct ThruFilterFlags
|
|
||||||
{
|
{
|
||||||
enum
|
Off = 0, ///< Thru disabled (nothing passes through).
|
||||||
{
|
Full = 1, ///< Fully enabled Thru (every incoming message is sent back).
|
||||||
off = 0 ///< Thru disabled (nothing passes through).
|
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.
|
||||||
, channelSame = (1 << 0) ///< Only the messages on the Input Channel will be sent back.
|
|
||||||
, channelDifferent = (1 << 1) ///< All the messages but the ones on the Input Channel will be sent back.
|
|
||||||
, channel = channelSame | channelDifferent
|
|
||||||
, systemExclusive = (1 << 2)
|
|
||||||
, systemCommon = (1 << 3)
|
|
||||||
, systemRealtime = (1 << 4)
|
|
||||||
, system = systemExclusive | systemCommon | systemRealtime
|
|
||||||
, junk = (1 << 7) ///< Send mis-formated data back (unadvisable)
|
|
||||||
|
|
||||||
/// Fully enabled Thru (every incoming message is sent back).
|
|
||||||
, all = channel | systemExclusive | systemCommon | systemRealtime
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef byte ThruFlags;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
/*! \brief Enumeration of Control Change command numbers.
|
/*! \brief Enumeration of Control Change command numbers.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue