From 601ddb3773db8db864637b59bd6b025237a93116 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sun, 7 Jul 2013 15:10:13 +0200 Subject: [PATCH] Reverted Thru changes on branch release/4.0 (breaking thread/merge-safety). --- src/MIDI.h | 40 ++- src/MIDI.hpp | 807 ++++++++++++++++++++++++++---------------------- src/midi_Defs.h | 28 +- 3 files changed, 464 insertions(+), 411 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index 9660cad..68a3dfd 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -68,7 +68,7 @@ public: inline void sendAfterTouch(DataByte inPressure, Channel inChannel); - inline void sendSysEx(unsigned inLength, + inline void sendSysEx(unsigned int inLength, const byte* inArray, bool inArrayContainsBoundaries = false); @@ -76,7 +76,7 @@ public: DataByte inValuesNibble); inline void sendTimeCodeQuarterFrame(DataByte inData); - inline void sendSongPosition(unsigned inBeats); + inline void sendSongPosition(unsigned int inBeats); inline void sendSongSelect(DataByte inSongNumber); inline void sendTuneRequest(); inline void sendRealTime(MidiType inType); @@ -109,7 +109,7 @@ public: inline DataByte getData1() const; inline DataByte getData2() const; inline const byte* getSysExArray() const; - inline unsigned getSysExArrayLength() const; + inline unsigned int getSysExArrayLength() const; inline bool check() const; public: @@ -117,12 +117,9 @@ public: inline void setInputChannel(Channel inChannel); public: - static inline MidiType getTypeFromStatusByte(StatusByte inStatus); - static inline byte getMessageLength(StatusByte inStatus); + static inline MidiType getTypeFromStatusByte(byte inStatus); static inline bool isChannelMessage(MidiType inType); - static inline bool isSystemRealtimeMessage(MidiType inType); - static inline bool isSystemCommonMessage(MidiType inType); - + private: bool inputFilter(Channel inChannel); bool parse(); @@ -133,8 +130,8 @@ private: Channel mInputChannel; byte mPendingMessage[3]; // SysEx are dumped into mMessage directly. - unsigned mPendingMessageExpectedLenght; - unsigned mPendingMessageIndex; // Extended to unsigned for larger SysEx payloads. + unsigned int mPendingMessageExpectedLenght; + unsigned int mPendingMessageIndex; // Extended to unsigned int for larger SysEx payloads. Message mMessage; @@ -153,7 +150,7 @@ public: inline void setHandlePitchBend(void (*fptr)(byte channel, int bend)); inline void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); 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 setHandleTuneRequest(void (*fptr)(void)); inline void setHandleClock(void (*fptr)(void)); @@ -178,7 +175,7 @@ private: void (*mPitchBendCallback)(byte channel, int); void (*mSystemExclusiveCallback)(byte * array, byte size); void (*mTimeCodeQuarterFrameCallback)(byte data); - void (*mSongPositionCallback)(unsigned beats); + void (*mSongPositionCallback)(unsigned int beats); void (*mSongSelectCallback)(byte songnumber); void (*mTuneRequestCallback)(void); void (*mClockCallback)(void); @@ -199,19 +196,20 @@ private: #if MIDI_BUILD_THRU 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 setThruFilterMode(ThruFlags inFlags); - inline ThruFlags getThruFilterMode() const; - inline bool isThruOn() const; - inline bool hasThruFlag(byte inFlag) const; + inline void setThruFilterMode(MidiFilterMode inThruFilterMode); + private: - inline bool shouldMessageBeForwarded(StatusByte inStatus) const; - + void thruFilter(byte inChannel); + private: - ThruFlags mThruFlags; + bool mThruActivated : 1; + MidiFilterMode mThruFilterMode : 7; #endif // MIDI_BUILD_THRU diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 60dddd8..f6b02b4 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -3,7 +3,7 @@ * Project Arduino MIDI Library * @brief MIDI Library for the Arduino - Inline implementations * @version 4.0 - * @author Francois Best + * @author Francois Best * @date 24/02/11 * license GPL Forty Seven Effects - 2011 */ @@ -40,7 +40,7 @@ MidiInterface::MidiInterface(SerialPort& inSerial) } /*! \brief Destructor for MidiInterface. - + This is not really useful for the Arduino, as it is never called... */ template @@ -51,7 +51,7 @@ 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 @@ -65,35 +65,37 @@ void MidiInterface::begin(Channel inChannel) #elif defined(FSE_AVR) mSerial. template open(); #endif - + #if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS - + mRunningStatus_TX = InvalidType; - -#endif - - + +#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS + + #if MIDI_BUILD_INPUT - + mInputChannel = inChannel; mRunningStatus_RX = InvalidType; mPendingMessageIndex = 0; mPendingMessageExpectedLenght = 0; - + mMessage.valid = false; mMessage.type = InvalidType; mMessage.channel = 0; mMessage.data1 = 0; mMessage.data2 = 0; - -#endif - - -#if MIDI_BUILD_THRU - - mThruFlags = ThruFilterFlags::all; - -#endif + +#endif // MIDI_BUILD_INPUT + + +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru + + mThruFilterMode = Full; + mThruActivated = true; + +#endif // Thru + } @@ -112,9 +114,9 @@ void MidiInterface::begin(Channel inChannel) \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 + \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. */ @@ -125,26 +127,26 @@ void MidiInterface::send(MidiType inType, Channel inChannel) { // Then test if channel is valid - if (inChannel >= MIDI_CHANNEL_OFF || - inChannel == MIDI_CHANNEL_OMNI || + if (inChannel >= MIDI_CHANNEL_OFF || + inChannel == MIDI_CHANNEL_OMNI || inType < NoteOff) { - -#if MIDI_USE_RUNNING_STATUS + +#if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; -#endif - +#endif + 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 MIDI_USE_RUNNING_STATUS // Check Running Status if (mRunningStatus_TX != status) @@ -157,12 +159,12 @@ void MidiInterface::send(MidiType inType, // Don't care about running status, send the status byte. mSerial.write(status); #endif - + // Then send data mSerial.write(inData1); if (inType != ProgramChange && inType != AfterTouchChannel) mSerial.write(inData2); - + return; } else if (inType >= TuneRequest && inType <= SystemReset) @@ -171,32 +173,32 @@ void MidiInterface::send(MidiType inType, // ----------------------------------------------------------------------------- -/*! \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 +/*! \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: + \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 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. This method will always send a real NoteOff message. - Take a look at the values, names and frequencies of notes here: + Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html */ template @@ -207,7 +209,7 @@ void MidiInterface::sendNoteOff(DataByte inNoteNumber, send(NoteOff, inNoteNumber, inVelocity, inChannel); } -/*! \brief Send a Program Change message +/*! \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). */ @@ -218,10 +220,10 @@ void MidiInterface::sendProgramChange(DataByte inProgramNumber, send(ProgramChange, inProgramNumber, 0, inChannel); } -/*! \brief Send a Control Change message - \param inControlNumber The controller number (0 to 127). +/*! \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). + \param inChannel The channel on which the message will be sent (1 to 16). @see MidiControlChangeNumber */ template @@ -235,7 +237,7 @@ void MidiInterface::sendControlChange(DataByte inControlNumber, /*! \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). + \param inChannel The channel on which the message will be sent (1 to 16). */ template void MidiInterface::sendPolyPressure(DataByte inNoteNumber, @@ -247,7 +249,7 @@ void MidiInterface::sendPolyPressure(DataByte inNoteNumber, /*! \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). + \param inChannel The channel on which the message will be sent (1 to 16). */ template void MidiInterface::sendAfterTouch(DataByte inPressure, @@ -257,8 +259,8 @@ void MidiInterface::sendAfterTouch(DataByte inPressure, } /*! \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, + \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). */ @@ -266,13 +268,14 @@ template void MidiInterface::sendPitchBend(int inPitchValue, 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); } + /*! \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) + \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). */ @@ -290,36 +293,36 @@ void MidiInterface::sendPitchBend(double inPitchValue, \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 inArrayContainsBoundaries is set to 'false' for compatibility + default value for ArrayContainsBoundaries is set to 'false' for compatibility with previous versions of the library. */ template -void MidiInterface::sendSysEx(unsigned inLength, +void MidiInterface::sendSysEx(unsigned int inLength, const byte* inArray, bool inArrayContainsBoundaries) { if (inArrayContainsBoundaries == false) { mSerial.write(0xF0); - - for (unsigned i = 0; i < inLength; ++i) + + for (unsigned int i = 0; i < inLength; ++i) mSerial.write(inArray[i]); - + mSerial.write(0xF7); } else { - for (unsigned i = 0; i < inLength; ++i) + for (unsigned int i = 0; i < inLength; ++i) mSerial.write(inArray[i]); } - + #if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif } -/*! \brief Send a Tune Request message. - +/*! \brief Send a Tune Request message. + When a MIDI unit receives this message, it should tune its oscillators (if equipped with any). */ @@ -329,24 +332,24 @@ void MidiInterface::sendTuneRequest() sendRealTime(TuneRequest); } -/*! \brief Send a MIDI Time Code Quarter Frame. - +/*! \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, +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, DataByte inValuesNibble) { const byte data = ( ((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0F) ); sendTimeCodeQuarterFrame(data); } -/*! \brief Send a MIDI Time Code Quarter Frame. - +/*! \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, + \param inData if you want to encode directly the nibbles in your program, you can send the byte here. */ template @@ -354,7 +357,7 @@ void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) { mSerial.write((byte)TimeCodeQuarterFrame); mSerial.write(inData); - + #if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif @@ -364,12 +367,12 @@ void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) \param inBeats The number of beats since the start of the song. */ template -void MidiInterface::sendSongPosition(unsigned inBeats) +void MidiInterface::sendSongPosition(unsigned int inBeats) { mSerial.write((byte)SongPosition); mSerial.write(inBeats & 0x7F); mSerial.write((inBeats >> 7) & 0x7F); - + #if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif @@ -381,15 +384,15 @@ void MidiInterface::sendSongSelect(DataByte inSongNumber) { mSerial.write((byte)SongSelect); mSerial.write(inSongNumber & 0x7F); - + #if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif } -/*! \brief Send a Real Time (one byte) message. - - \param inType The available Real Time types are: +/*! \brief Send a Real Time (one byte) message. + + \param inType The available Real Time types are: Start, Stop, Continue, Clock, ActiveSensing and SystemReset. You can also send a Tune Request with this method. @see MidiType @@ -397,13 +400,24 @@ void MidiInterface::sendSongSelect(DataByte inSongNumber) template void MidiInterface::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 - // interleaved within any message. Though, TuneRequest can be sent here, + + // Do not cancel Running Status for real-time messages as they can be + // interleaved within any message. Though, TuneRequest can be sent here, // and as it is a System Common message, it must reset Running Status. #if MIDI_USE_RUNNING_STATUS if (inType == TuneRequest) mRunningStatus_TX = InvalidType; @@ -435,7 +449,7 @@ StatusByte MidiInterface::getStatus(MidiType inType, */ /*! \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, @@ -455,14 +469,23 @@ bool MidiInterface::read(Channel inChannel) { if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. - - if (parse() && inputFilter(inChannel)) + + if (parse()) { -#if MIDI_USE_CALLBACKS - launchCallback(); + if (inputFilter(inChannel)) + { + +#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) + thruFilter(inChannel); #endif - return true; + +#if MIDI_USE_CALLBACKS + launchCallback(); +#endif + return true; + } } + return false; } @@ -473,33 +496,33 @@ template bool MidiInterface::parse() { const byte bytes_available = mSerial.available(); - + if (bytes_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, + // - 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 recomposed, notify the caller. - + // Else, add the extracted byte to the pending message, and check validity. + // When the message is done, store it. + const byte extracted = mSerial.read(); - - if (mPendingMessageIndex == 0) - { + + 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 + + // If the status byte is not received, prepend it // to the pending message if (extracted < 0x80) { @@ -510,77 +533,91 @@ bool MidiInterface::parse() // Else: well, we received another status byte, // so the running status does not apply here. // It will be updated upon completion of this message. - - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) + + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = (mPendingMessage[0] & 0x0F) + 1; + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; mMessage.data1 = mPendingMessage[1]; - + // Save data2 only if applicable if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; - else + else mMessage.data2 = 0; - + mPendingMessageIndex = 0; mPendingMessageExpectedLenght = 0; mMessage.valid = true; return true; } } - - mPendingMessageExpectedLenght = getMessageLength(mPendingMessage[0]); - - if (mPendingMessageExpectedLenght == 1) + + switch (getTypeFromStatusByte(mPendingMessage[0])) { - // RealTime and 1 byte messages -> send & handle rightaway - if (shouldMessageBeForwarded(mPendingMessage[0])) - { - mSerial.write(mPendingMessage[0]); - } - - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.valid = true; - - // \fix Running Status broken when receiving Clock messages. - // Do not reset all input attributes, Running Status must remain unchanged. - //resetInput(); - - // We still need to reset these - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - return true; + // 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 = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //resetInput(); + + // 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 SystemExclusive: + // The message can be any lenght + // between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; + mRunningStatus_RX = InvalidType; + mMessage.sysexArray[0] = SystemExclusive; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + resetInput(); + return false; + break; } - else if (mPendingMessageExpectedLenght == 0xff) - { - // SysEx: The message can be any length - // between 3 and MIDI_SYSEX_ARRAY_SIZE bytes - mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; - mRunningStatus_RX = InvalidType; - mMessage.sysexArray[0] = SystemExclusive; - - - } - else if (mPendingMessageExpectedLenght == 0) - { - // Parse error - resetInput(); - return false; - } - + // Then update the index of the pending message. mPendingMessageIndex++; - - if (shouldMessageBeForwarded(mPendingMessage[0])) - { - mSerial.write(mPendingMessage[0]); - } - + #if USE_1BYTE_PARSING // Message is not complete. return false; @@ -589,113 +626,110 @@ bool MidiInterface::parse() // to parse the rest of the message. return parse(); #endif + } 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 - if (isSystemRealtimeMessage(getTypeFromStatusByte(extracted))) + switch (extracted) { - if (shouldMessageBeForwarded(extracted)) - { - mSerial.write(extracted); - } - - // 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; - } - 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; + 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; - - resetInput(); return true; - } + + break; + + // End of Exclusive + 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; } - else - { - // Parse error: unexpected status byte - } - } - - // Add extracted data byte to pending message - if (mPendingMessage[0] == SystemExclusive) - { - mMessage.sysexArray[mPendingMessageIndex] = extracted; - } - else - { - mPendingMessage[mPendingMessageIndex] = extracted; } - if (shouldMessageBeForwarded(mPendingMessage[0])) - { - mSerial.write(extracted); - } - + // Add extracted data byte to pending message + if (mPendingMessage[0] == SystemExclusive) + mMessage.sysexArray[mPendingMessageIndex] = extracted; + else + mPendingMessage[mPendingMessageIndex] = extracted; + // Now we are going to check if we have reached the end of the message - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { // "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 // the buffer. If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. if (mPendingMessage[0] == SystemExclusive) { resetInput(); return false; } - + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - + if (isChannelMessage(mMessage.type)) - mMessage.channel = (mPendingMessage[0] & 0x0F) + 1; + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; else mMessage.channel = 0; - + mMessage.data1 = mPendingMessage[1]; - + // Save data2 only if applicable if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; - else + else mMessage.data2 = 0; - + // Reset local variables mPendingMessageIndex = 0; mPendingMessageExpectedLenght = 0; - + mMessage.valid = true; - + // Activate running status (if enabled for the received type) switch (mMessage.type) { @@ -705,11 +739,11 @@ bool MidiInterface::parse() case ControlChange: case ProgramChange: case AfterTouchChannel: - case PitchBend: + case PitchBend: // Running status enabled: store it from received message mRunningStatus_RX = mPendingMessage[0]; break; - + default: // No running status mRunningStatus_RX = InvalidType; @@ -721,7 +755,7 @@ bool MidiInterface::parse() { // Then update the index of the pending message. mPendingMessageIndex++; - + #if USE_1BYTE_PARSING // Message is not complete. return false; @@ -732,7 +766,7 @@ bool MidiInterface::parse() #endif } } - + // What are our chances to fall here? return false; } @@ -741,19 +775,32 @@ bool MidiInterface::parse() template bool MidiInterface::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) + if (mMessage.type == InvalidType) 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 || - mInputChannel == mMessage.channel); + // Then we need to know if we listen to it + 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 @@ -768,7 +815,7 @@ void MidiInterface::resetInput() // ----------------------------------------------------------------------------- /*! \brief Get the last received message's type - + Returns an enumerated type. @see MidiType */ template @@ -778,8 +825,8 @@ MidiType MidiInterface::getType() const } /*! \brief Get the channel of the message stored in the structure. - - \return Channel range is 1 to 16. + + \return Channel range is 1 to 16. For non-channel messages, this will return 0. */ template @@ -798,66 +845,66 @@ DataByte MidiInterface::getData1() const /*! \brief Get the second data byte of the last received message. */ template DataByte MidiInterface::getData2() const -{ +{ return mMessage.data2; } -/*! \brief Get the System Exclusive byte array. - +/*! \brief Get the System Exclusive byte array. + @see getSysExArrayLength to get the array's length in bytes. */ template const byte* MidiInterface::getSysExArray() const -{ +{ 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. \return The array's length, in bytes. */ template -unsigned MidiInterface::getSysExArrayLength() const +unsigned int MidiInterface::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; } /*! \brief Check if a valid message is stored in the structure. */ template bool MidiInterface::check() const -{ +{ return mMessage.valid; } // ----------------------------------------------------------------------------- template -Channel MidiInterface::getInputChannel() const +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 +/*! \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 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, + + This is a utility static method, used internally, made public so you can handle MidiTypes more easily. */ template -MidiType MidiInterface::getTypeFromStatusByte(StatusByte inStatus) +MidiType MidiInterface::getTypeFromStatusByte(byte inStatus) { if ((inStatus < 0x80) || (inStatus == 0xF4) || @@ -868,38 +915,6 @@ MidiType MidiInterface::getTypeFromStatusByte(StatusByte inStatus) else return (MidiType)inStatus; } -template -byte MidiInterface::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 bool MidiInterface::isChannelMessage(MidiType inType) { @@ -912,26 +927,6 @@ bool MidiInterface::isChannelMessage(MidiType inType) inType == ProgramChange); } -template -bool MidiInterface::isSystemRealtimeMessage(MidiType inType) -{ - return inType == Clock || - inType == Start || - inType == Stop || - inType == Continue || - inType == ActiveSensing || - inType == SystemReset; -} - -template -bool MidiInterface::isSystemCommonMessage(MidiType inType) -{ - return inType == TimeCodeQuarterFrame || - inType == SongPosition || - inType == SongSelect || - inType == TuneRequest; -} - // ----------------------------------------------------------------------------- #if MIDI_USE_CALLBACKS @@ -949,7 +944,7 @@ template void MidiInterface::setHandleAfterTouchCh template void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } template void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte* array, byte 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::setHandleSongPosition(void (*fptr)(unsigned int 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; } @@ -960,12 +955,12 @@ template void MidiInterface::setHandleActiveSensin 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. + \param inType The type of message to unbind. When a message of this type is received, no function will be called. */ -template +template void MidiInterface::disconnectCallbackFromType(MidiType inType) { switch (inType) @@ -1005,29 +1000,29 @@ void MidiInterface::launchCallback() // 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 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; // TODO: check this case AfterTouchPoly: if (mAfterTouchPolyCallback != 0) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; case AfterTouchChannel: if (mAfterTouchChannelCallback != 0) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; - + case ProgramChange: if (mProgramChangeCallback != 0) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; case SystemExclusive: if (mSystemExclusiveCallback != 0) mSystemExclusiveCallback(mMessage.sysexArray,mMessage.data1); break; - + // Occasional messages case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != 0) mTimeCodeQuarterFrameCallback(mMessage.data1); break; case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; case 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: @@ -1046,84 +1041,160 @@ void MidiInterface::launchCallback() // Thru // ----------------------------------------------------------------------------- -#if MIDI_BUILD_THRU +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) /*! \addtogroup thru @{ */ -template -void MidiInterface::turnThruOn(ThruFlags inFlags) -{ - mThruFlags = inFlags; -} - -template -void MidiInterface::turnThruOff() -{ - mThruFlags = ThruFilterFlags::off; -} - /*! \brief Set the filter for thru mirroring \param inThruFilterMode a filter mode - - @see ThruFilterFlags + + @see MidiFilterMode */ -template -void MidiInterface::setThruFilterMode(ThruFlags inFlags) -{ - mThruFlags = inFlags; +template +void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode) +{ + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) + mThruActivated = true; + else + mThruActivated = false; } -template -ThruFlags MidiInterface::getThruFilterMode() const +template +MidiFilterMode MidiInterface::getFilterMode() const { - return mThruFlags; + return mThruFilterMode; } -template -bool MidiInterface::isThruOn() const +template +bool MidiInterface::getThruState() const { - return mThruFlags != ThruFilterFlags::off; + return mThruActivated; } -template -bool MidiInterface::hasThruFlag(byte inFlag) const +template +void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) +{ + mThruActivated = true; + mThruFilterMode = inThruFilterMode; +} + +template +void MidiInterface::turnThruOff() { - return mThruFlags & inFlag; + mThruActivated = false; + mThruFilterMode = Off; } /*! @} */ // End of doc group MIDI Thru -// ----------------------------------------------------------------------------- +// This method is called upon reception of a message +// and takes care of Thru filtering and sending. template -bool MidiInterface::shouldMessageBeForwarded(StatusByte inStatus) const +void MidiInterface::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 forwardSame = hasThruFlag(ThruFilterFlags::channelSame); - const bool forwardDiff = hasThruFlag(ThruFilterFlags::channelDifferent); - return (forwardSame && mInputChannel == channel) || - (forwardDiff && mInputChannel != channel); - } - else if (isSystemRealtimeMessage(type)) - { - return hasThruFlag(ThruFilterFlags::systemRealtime); - } - else if (isSystemCommonMessage(type)) - { - return hasThruFlag(ThruFilterFlags::systemCommon); - } - else if (type == SystemExclusive) - { - return hasThruFlag(ThruFilterFlags::systemExclusive); + const bool filter_condition = ((mMessage.channel == mInputChannel) || + (mInputChannel == MIDI_CHANNEL_OMNI)); + + // Now let's pass it to the output + switch (mThruFilterMode) + { + case Full: + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + return; + break; + case SameChannel: + if (filter_condition) + { + 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 { - // Unknown message or junk - return hasThruFlag(ThruFilterFlags::junk); + // 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); + 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; + } } } diff --git a/src/midi_Defs.h b/src/midi_Defs.h index 9dcd3bc..2f69a05 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -61,31 +61,15 @@ enum MidiType // ----------------------------------------------------------------------------- -/*! Enumeration of Thru filter modes - @see setThruFilterMode -*/ -struct ThruFilterFlags +/*! Enumeration of Thru filter modes */ +enum MidiFilterMode { - enum - { - off = 0 ///< Thru disabled (nothing passes through). - - , 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 - }; + 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. }; -typedef byte ThruFlags; - // ----------------------------------------------------------------------------- /*! \brief Enumeration of Control Change command numbers.