diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 20bdce1..335748f 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -1,16 +1,16 @@ /*! - * @file MIDI.cpp - * Project MIDI Library - * @brief MIDI Library for the Arduino - * @version 3.2 - * @author Francois Best - * @date 24/02/11 - * license GPL Forty Seven Effects - 2011 + * @file MIDI.cpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @version 3.2 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 */ #include "MIDI.h" #include -#include "Arduino.h" // If using an old (pre-1.0) version of Arduino, use WConstants.h instead of Arduino.h +#include "Arduino.h" // If using an old (pre-1.0) version of Arduino, use WConstants.h instead of Arduino.h #include "HardwareSerial.h" @@ -34,31 +34,31 @@ MIDI_Class MIDI; /*! \brief Default constructor for MIDI_Class. */ MIDI_Class::MIDI_Class() { - + #if USE_CALLBACKS - - // Initialise callbacks to NULL pointer - mNoteOffCallback = NULL; - mNoteOnCallback = NULL; - mAfterTouchPolyCallback = NULL; - mControlChangeCallback = NULL; - mProgramChangeCallback = NULL; - mAfterTouchChannelCallback = NULL; - mPitchBendCallback = NULL; - mSystemExclusiveCallback = NULL; - mTimeCodeQuarterFrameCallback = NULL; - mSongPositionCallback = NULL; - mSongSelectCallback = NULL; - mTuneRequestCallback = NULL; - mClockCallback = NULL; - mStartCallback = NULL; - mContinueCallback = NULL; - mStopCallback = NULL; - mActiveSensingCallback = NULL; - mSystemResetCallback = NULL; - + + // Initialise callbacks to NULL pointer + mNoteOffCallback = NULL; + mNoteOnCallback = NULL; + mAfterTouchPolyCallback = NULL; + mControlChangeCallback = NULL; + mProgramChangeCallback = NULL; + mAfterTouchChannelCallback = NULL; + mPitchBendCallback = NULL; + mSystemExclusiveCallback = NULL; + mTimeCodeQuarterFrameCallback = NULL; + mSongPositionCallback = NULL; + mSongSelectCallback = NULL; + mTuneRequestCallback = NULL; + mClockCallback = NULL; + mStartCallback = NULL; + mContinueCallback = NULL; + mStopCallback = NULL; + mActiveSensingCallback = NULL; + mSystemResetCallback = NULL; + #endif - + } @@ -68,7 +68,7 @@ MIDI_Class::MIDI_Class() */ MIDI_Class::~MIDI_Class() { - + } @@ -80,45 +80,45 @@ MIDI_Class::~MIDI_Class() */ void MIDI_Class::begin(const byte inChannel) { - - // Initialise the Serial port - USE_SERIAL_PORT.begin(MIDI_BAUDRATE); - - + + // Initialise the Serial port + USE_SERIAL_PORT.begin(MIDI_BAUDRATE); + + #if COMPILE_MIDI_OUT - + #if USE_RUNNING_STATUS - - mRunningStatus_TX = InvalidType; - + + mRunningStatus_TX = InvalidType; + #endif // USE_RUNNING_STATUS - + #endif // COMPILE_MIDI_OUT - - + + #if COMPILE_MIDI_IN - - mInputChannel = inChannel; - mRunningStatus_RX = InvalidType; - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = false; - mMessage.type = InvalidType; - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - + + 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 // COMPILE_MIDI_IN - - + + #if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru - - mThruFilterMode = Full; - mThruActivated = true; - + + mThruFilterMode = Full; + mThruActivated = true; + #endif // Thru - + } @@ -126,240 +126,240 @@ void MIDI_Class::begin(const byte inChannel) // Private method for generating a status byte from channel and type const byte MIDI_Class::genstatus(const kMIDIType inType, - const byte inChannel) const + const byte inChannel) const { - - return ((byte)inType | ((inChannel-1) & 0x0F)); - + + return ((byte)inType | ((inChannel-1) & 0x0F)); + } /*! \brief Generate and send a MIDI message from the values given. - \param type The message type (see type defines for reference) - \param data1 The first data byte. - \param data2 The second data byte (if the message contains only 1 data byte, set this one to 0). - \param channel The output channel on which the message will be sent (values from 1 to 16). Note: you cannot send to OMNI. + \param type The message type (see type defines for reference) + \param data1 The first data byte. + \param data2 The second data byte (if the message contains only 1 data byte, set this one to 0). + \param channel 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. */ void MIDI_Class::send(kMIDIType type, - byte data1, - byte data2, - byte channel) + byte data1, + byte data2, + byte channel) { - - // Then test if channel is valid - if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { - -#if USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; + + // Then test if channel is valid + if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { + +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; #endif - - return; // Don't send anything - } - - if (type <= PitchBend) { - // Channel messages - - // Protection: remove MSBs on data - data1 &= 0x7F; - data2 &= 0x7F; - - byte statusbyte = genstatus(type,channel); - + + return; // Don't send anything + } + + if (type <= PitchBend) { + // Channel messages + + // Protection: remove MSBs on data + data1 &= 0x7F; + data2 &= 0x7F; + + byte statusbyte = genstatus(type,channel); + #if USE_RUNNING_STATUS - // Check Running Status - if (mRunningStatus_TX != statusbyte) { - // New message, memorise and send header - mRunningStatus_TX = statusbyte; - USE_SERIAL_PORT.write(mRunningStatus_TX); - } + // Check Running Status + if (mRunningStatus_TX != statusbyte) { + // New message, memorise and send header + mRunningStatus_TX = statusbyte; + USE_SERIAL_PORT.write(mRunningStatus_TX); + } #else - // Don't care about running status, send the Control byte. - USE_SERIAL_PORT.write(statusbyte); + // Don't care about running status, send the Control byte. + USE_SERIAL_PORT.write(statusbyte); #endif - - // Then send data - USE_SERIAL_PORT.write(data1); - if (type != ProgramChange && type != AfterTouchChannel) { - USE_SERIAL_PORT.write(data2); - } - return; - } - if (type >= TuneRequest && type <= SystemReset) { - // System Real-time and 1 byte. - sendRealTime(type); - } - + + // Then send data + USE_SERIAL_PORT.write(data1); + if (type != ProgramChange && type != AfterTouchChannel) { + USE_SERIAL_PORT.write(data2); + } + return; + } + if (type >= TuneRequest && type <= SystemReset) { + // System Real-time and 1 byte. + sendRealTime(type); + } + } /*! \brief Send a Note On message - \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n - \param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff. - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendNoteOn(byte NoteNumber, - byte Velocity, - byte Channel) + byte Velocity, + byte Channel) { - - send(NoteOn,NoteNumber,Velocity,Channel); - + + send(NoteOn,NoteNumber,Velocity,Channel); + } /*! \brief Send a Note Off message (a real Note Off, not a Note On with null velocity) - \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n - \param Velocity Release velocity (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Release velocity (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendNoteOff(byte NoteNumber, - byte Velocity, - byte Channel) + byte Velocity, + byte Channel) { - - send(NoteOff,NoteNumber,Velocity,Channel); - + + send(NoteOff,NoteNumber,Velocity,Channel); + } /*! \brief Send a Program Change message - \param ProgramNumber The Program to select (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param ProgramNumber The Program to select (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendProgramChange(byte ProgramNumber, - byte Channel) + byte Channel) { - - send(ProgramChange,ProgramNumber,0,Channel); - + + send(ProgramChange,ProgramNumber,0,Channel); + } /*! \brief Send a Control Change message - \param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums - \param ControlValue The value for the specified controller (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums + \param ControlValue The value for the specified controller (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendControlChange(byte ControlNumber, - byte ControlValue, - byte Channel) + byte ControlValue, + byte Channel) { - - send(ControlChange,ControlNumber,ControlValue,Channel); - + + send(ControlChange,ControlNumber,ControlValue,Channel); + } /*! \brief Send a Polyphonic AfterTouch message (applies to only one specified note) - \param NoteNumber The note to apply AfterTouch to (0 to 127). - \param Pressure The amount of AfterTouch to apply (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber The note to apply AfterTouch to (0 to 127). + \param Pressure The amount of AfterTouch to apply (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPolyPressure(byte NoteNumber, - byte Pressure, - byte Channel) + byte Pressure, + byte Channel) { - - send(AfterTouchPoly,NoteNumber,Pressure,Channel); - + + send(AfterTouchPoly,NoteNumber,Pressure,Channel); + } /*! \brief Send a MonoPhonic AfterTouch message (applies to all notes) - \param Pressure The amount of AfterTouch to apply to all notes. - \param Channel The channel on which the message will be sent (1 to 16). + \param Pressure The amount of AfterTouch to apply to all notes. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendAfterTouch(byte Pressure, - byte Channel) + byte Channel) { - - send(AfterTouchChannel,Pressure,0,Channel); - + + send(AfterTouchChannel,Pressure,0,Channel); + } /*! \brief Send a Pitch Bend message using a signed integer value. - \param PitchValue The amount of bend to send (in a signed integer format), between -8192 (maximum downwards bend) and 8191 (max upwards bend), center value is 0. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a signed integer format), between -8192 (maximum downwards bend) and 8191 (max upwards bend), center value is 0. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(int PitchValue, - byte Channel) + byte Channel) { - - unsigned int bend = PitchValue + 8192; - sendPitchBend(bend,Channel); - + + unsigned int bend = PitchValue + 8192; + sendPitchBend(bend,Channel); + } /*! \brief Send a Pitch Bend message using an unsigned integer value. - \param PitchValue The amount of bend to send (in a signed integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a signed integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(unsigned int PitchValue, - byte Channel) + byte Channel) { - - send(PitchBend,(PitchValue & 0x7F),(PitchValue >> 7) & 0x7F,Channel); - + + send(PitchBend,(PitchValue & 0x7F),(PitchValue >> 7) & 0x7F,Channel); + } /*! \brief Send a Pitch Bend message using a floating point value. - \param PitchValue 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 Channel The channel on which the message will be sent (1 to 16). + \param PitchValue 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 Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(double PitchValue, - byte Channel) + byte Channel) { - - unsigned int pitchval = (PitchValue+1.f)*8192; - if (pitchval > 16383) pitchval = 16383; // overflow protection - sendPitchBend(pitchval,Channel); - + + unsigned int pitchval = (PitchValue+1.f)*8192; + if (pitchval > 16383) pitchval = 16383; // overflow protection + sendPitchBend(pitchval,Channel); + } /*! \brief Generate and send a System Exclusive frame. - \param length The size of the array to send - \param array The byte array containing the data to send + \param length The size of the array to send + \param array The byte array containing the data to send \param ArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes (start & stop SysEx) will NOT be sent (and therefore must be included in the array). default value is set to 'false' for compatibility with previous versions of the library. */ void MIDI_Class::sendSysEx(int length, - const byte *const array, - bool ArrayContainsBoundaries) + const byte *const array, + bool ArrayContainsBoundaries) { - - if (ArrayContainsBoundaries == false) { - - USE_SERIAL_PORT.write(0xF0); - - for (int i=0;i> 7) & 0x7F); - + + USE_SERIAL_PORT.write((byte)SongPosition); + USE_SERIAL_PORT.write(Beats & 0x7F); + USE_SERIAL_PORT.write((Beats >> 7) & 0x7F); + #if USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; + mRunningStatus_TX = InvalidType; #endif - + } /*! \brief Send a Song Select message */ void MIDI_Class::sendSongSelect(byte SongNumber) { - - USE_SERIAL_PORT.write((byte)SongSelect); - USE_SERIAL_PORT.write(SongNumber & 0x7F); - + + USE_SERIAL_PORT.write((byte)SongSelect); + USE_SERIAL_PORT.write(SongNumber & 0x7F); + #if USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; + mRunningStatus_TX = InvalidType; #endif - + } @@ -447,27 +447,27 @@ void MIDI_Class::sendSongSelect(byte SongNumber) */ void MIDI_Class::sendRealTime(kMIDIType Type) { - switch (Type) { - case TuneRequest: // Not really real-time, but one byte anyway. - case Clock: - case Start: - case Stop: - case Continue: - case ActiveSensing: - case SystemReset: - USE_SERIAL_PORT.write((byte)Type); - 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, and as it is a System Common message, it must reset Running Status. + switch (Type) { + case TuneRequest: // Not really real-time, but one byte anyway. + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + USE_SERIAL_PORT.write((byte)Type); + 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, and as it is a System Common message, it must reset Running Status. #if USE_RUNNING_STATUS - if (Type == TuneRequest) mRunningStatus_TX = InvalidType; + if (Type == TuneRequest) mRunningStatus_TX = InvalidType; #endif - + } #endif // COMPILE_MIDI_OUT @@ -484,360 +484,360 @@ void MIDI_Class::sendRealTime(kMIDIType Type) */ bool MIDI_Class::read() { - - return read(mInputChannel); - + + return read(mInputChannel); + } /*! \brief Reading/thru-ing method, the same as read() with a given input channel to read on. */ bool MIDI_Class::read(const byte inChannel) { - - if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. - - if (parse(inChannel)) { - - if (input_filter(inChannel)) { - + + if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. + + if (parse(inChannel)) { + + if (input_filter(inChannel)) { + #if (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) - thru_filter(inChannel); + thru_filter(inChannel); #endif - + #if USE_CALLBACKS - launchCallback(); + launchCallback(); #endif - - return true; - } - - } - - return false; - + + return true; + } + + } + + return false; + } // Private method: MIDI parser bool MIDI_Class::parse(byte inChannel) { - - const int bytes_available = USE_SERIAL_PORT.available(); - - if (bytes_available <= 0) { - // No data available. - return false; - } - - // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. - if (bytes_available == 128) { - USE_SERIAL_PORT.flush(); - } - else { - - /* 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 = USE_SERIAL_PORT.read(); - - if (mPendingMessageIndex == 0) { // Start a new pending message - mPendingMessage[0] = extracted; - - // Check for running status first - switch (getTypeFromStatusByte(mRunningStatus_RX)) { - // Only these types allow Running Status: - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - - // 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. - - break; - - default: - // No running status - break; - } - - - switch (getTypeFromStatusByte(mPendingMessage[0])) { - - // 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. - //reset_input_attributes(); - - // 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: - mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes - mRunningStatus_RX = InvalidType; - break; - - case InvalidType: - default: - // This is obviously wrong. Let's get the hell out'a here. - reset_input_attributes(); - return false; - break; - } - - // Then update the index of the pending message. - mPendingMessageIndex++; - + + const int bytes_available = USE_SERIAL_PORT.available(); + + if (bytes_available <= 0) { + // No data available. + return false; + } + + // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. + if (bytes_available == 128) { + USE_SERIAL_PORT.flush(); + } + else { + + /* 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 = USE_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) { // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + switch (getTypeFromStatusByte(mRunningStatus_RX)) { + // Only these types allow Running Status: + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + + // 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. + + break; + + default: + // No running status + break; + } + + + switch (getTypeFromStatusByte(mPendingMessage[0])) { + + // 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. + //reset_input_attributes(); + + // 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: + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mRunningStatus_RX = InvalidType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + reset_input_attributes(); + return false; + break; + } + + // Then update the index of the pending message. + mPendingMessageIndex++; + #if USE_1BYTE_PARSING - // Message is not complete. - return false; + // Message is not complete. + return false; #else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); #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 - switch (extracted) { - case Clock: - case Start: - case Continue: - case Stop: - case ActiveSensing: - case SystemReset: - - /* - This is tricky. 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 = (kMIDIType)extracted; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.channel = 0; - mMessage.valid = true; - return true; - - break; - - // End of Exclusive - case 0xF7: - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - - // Store System Exclusive array in midimsg structure - for (byte i=0;i> 8; - - mMessage.channel = 0; - mMessage.valid = true; - - reset_input_attributes(); - - return true; - } - else { - // Well well well.. error. - reset_input_attributes(); - return false; - } - - break; - default: - break; - } - - - - } - - - // Add extracted data byte to pending message - 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 MIDI_SYSEX_ARRAY_SIZE. - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - reset_input_attributes(); - return false; - } - - - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message - - mMessage.data1 = mPendingMessage[1]; - - // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; - 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) { - 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++; - + + } + 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: + + /* + This is tricky. 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 = (kMIDIType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xF7: + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + + // Store System Exclusive array in midimsg structure + for (byte i=0;i> 8; + + mMessage.channel = 0; + mMessage.valid = true; + + reset_input_attributes(); + + return true; + } + else { + // Well well well.. error. + reset_input_attributes(); + return false; + } + + break; + default: + break; + } + + + + } + + + // Add extracted data byte to pending message + 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 MIDI_SYSEX_ARRAY_SIZE. + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + reset_input_attributes(); + return false; + } + + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; + 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) { + 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 USE_1BYTE_PARSING - // Message is not complete. - return false; + // Message is not complete. + return false; #else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); #endif - - } - - } - - } - - // What are our chances to fall here? - return false; + + } + + } + + } + + // What are our chances to fall here? + return false; } // Private method: check if the received message is on the listened channel bool MIDI_Class::input_filter(byte inChannel) { - - - // This method handles recognition of channel (to know if the message is destinated to the Arduino) - - - if (mMessage.type == InvalidType) return false; - - - // 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 == 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; - } - + + + // This method handles recognition of channel (to know if the message is destinated to the Arduino) + + + if (mMessage.type == InvalidType) return false; + + + // 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 == 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; + } + } // Private method: reset input attributes void MIDI_Class::reset_input_attributes() { - - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - mRunningStatus_RX = InvalidType; - + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; + } @@ -848,9 +848,9 @@ void MIDI_Class::reset_input_attributes() */ kMIDIType MIDI_Class::getType() const { - - return mMessage.type; - + + return mMessage.type; + } @@ -860,27 +860,27 @@ kMIDIType MIDI_Class::getType() const */ byte MIDI_Class::getChannel() const { - - return mMessage.channel; - + + return mMessage.channel; + } /*! \brief Get the first data byte of the last received message. */ byte MIDI_Class::getData1() const { - - return mMessage.data1; - + + return mMessage.data1; + } /*! \brief Get the second data byte of the last received message. */ byte MIDI_Class::getData2() const { - - return mMessage.data2; - + + return mMessage.data2; + } @@ -890,9 +890,9 @@ byte MIDI_Class::getData2() const */ const byte * MIDI_Class::getSysExArray() const { - - return mMessage.sysex_array; - + + return mMessage.sysex_array; + } /*! \brief Get the lenght of the System Exclusive array. @@ -902,20 +902,20 @@ const byte * MIDI_Class::getSysExArray() const */ unsigned int MIDI_Class::getSysExArrayLength() const { - - unsigned int coded_size = ((unsigned int)(mMessage.data2) << 8) | mMessage.data1; - - return (coded_size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : coded_size; - + + unsigned int coded_size = ((unsigned int)(mMessage.data2) << 8) | mMessage.data1; + + return (coded_size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : coded_size; + } /*! \brief Check if a valid message is stored in the structure. */ bool MIDI_Class::check() const { - - return mMessage.valid; - + + return mMessage.valid; + } @@ -926,107 +926,107 @@ bool MIDI_Class::check() const */ void MIDI_Class::setInputChannel(const byte Channel) { - - mInputChannel = Channel; - + + mInputChannel = Channel; + } #if USE_CALLBACKS -void MIDI_Class::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } -void MIDI_Class::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } -void MIDI_Class::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } -void MIDI_Class::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } -void MIDI_Class::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } -void MIDI_Class::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } -void MIDI_Class::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } -void MIDI_Class::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } -void MIDI_Class::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } -void MIDI_Class::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } -void MIDI_Class::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } -void MIDI_Class::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } -void MIDI_Class::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } -void MIDI_Class::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } -void MIDI_Class::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } -void MIDI_Class::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } -void MIDI_Class::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } -void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } +void MIDI_Class::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } +void MIDI_Class::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } +void MIDI_Class::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } +void MIDI_Class::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } +void MIDI_Class::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } +void MIDI_Class::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } +void MIDI_Class::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } +void MIDI_Class::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } +void MIDI_Class::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } +void MIDI_Class::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } +void MIDI_Class::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } +void MIDI_Class::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } +void MIDI_Class::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } +void MIDI_Class::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +void MIDI_Class::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +void MIDI_Class::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +void MIDI_Class::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +void MIDI_Class::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 Type The type of message to unbind. When a message of this type is received, no function will be called. + \param Type The type of message to unbind. When a message of this type is received, no function will be called. */ void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) { - - switch (Type) { - case NoteOff: mNoteOffCallback = NULL; break; - case NoteOn: mNoteOnCallback = NULL; break; - case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; - case ControlChange: mControlChangeCallback = NULL; break; - case ProgramChange: mProgramChangeCallback = NULL; break; - case AfterTouchChannel: mAfterTouchChannelCallback = NULL; break; - case PitchBend: mPitchBendCallback = NULL; break; - case SystemExclusive: mSystemExclusiveCallback = NULL; break; - case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = NULL; break; - case SongPosition: mSongPositionCallback = NULL; break; - case SongSelect: mSongSelectCallback = NULL; break; - case TuneRequest: mTuneRequestCallback = NULL; break; - case Clock: mClockCallback = NULL; break; - case Start: mStartCallback = NULL; break; - case Continue: mContinueCallback = NULL; break; - case Stop: mStopCallback = NULL; break; - case ActiveSensing: mActiveSensingCallback = NULL; break; - case SystemReset: mSystemResetCallback = NULL; break; - default: - break; - } - + + switch (Type) { + case NoteOff: mNoteOffCallback = NULL; break; + case NoteOn: mNoteOnCallback = NULL; break; + case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; + case ControlChange: mControlChangeCallback = NULL; break; + case ProgramChange: mProgramChangeCallback = NULL; break; + case AfterTouchChannel: mAfterTouchChannelCallback = NULL; break; + case PitchBend: mPitchBendCallback = NULL; break; + case SystemExclusive: mSystemExclusiveCallback = NULL; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = NULL; break; + case SongPosition: mSongPositionCallback = NULL; break; + case SongSelect: mSongSelectCallback = NULL; break; + case TuneRequest: mTuneRequestCallback = NULL; break; + case Clock: mClockCallback = NULL; break; + case Start: mStartCallback = NULL; break; + case Continue: mContinueCallback = NULL; break; + case Stop: mStopCallback = NULL; break; + case ActiveSensing: mActiveSensingCallback = NULL; break; + case SystemReset: mSystemResetCallback = NULL; break; + default: + break; + } + } // Private - launch callback function based on received type. void MIDI_Class::launchCallback() { - - // The order is mixed to allow frequent messages to trigger their callback faster. - - switch (mMessage.type) { - // Notes - case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - - // Real-time messages - case Clock: if (mClockCallback != NULL) mClockCallback(); break; - case Start: if (mStartCallback != NULL) mStartCallback(); break; - case Continue: if (mContinueCallback != NULL) mContinueCallback(); break; - case Stop: if (mStopCallback != NULL) mStopCallback(); break; - case ActiveSensing: if (mActiveSensingCallback != NULL) mActiveSensingCallback(); break; - - // Continuous controllers - case ControlChange: if (mControlChangeCallback != NULL) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) - 8192); break; // TODO: check this - case AfterTouchPoly: if (mAfterTouchPolyCallback != NULL) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case AfterTouchChannel: if (mAfterTouchChannelCallback != NULL) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; - - case ProgramChange: if (mProgramChangeCallback != NULL) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; - case SystemExclusive: if (mSystemExclusiveCallback != NULL) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; - - // Occasional messages - case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != NULL) mTimeCodeQuarterFrameCallback(mMessage.data1); break; - case SongPosition: if (mSongPositionCallback != NULL) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; - case SongSelect: if (mSongSelectCallback != NULL) mSongSelectCallback(mMessage.data1); break; - case TuneRequest: if (mTuneRequestCallback != NULL) mTuneRequestCallback(); break; - - case SystemReset: if (mSystemResetCallback != NULL) mSystemResetCallback(); break; - case InvalidType: - default: - break; - } - + + // The order is mixed to allow frequent messages to trigger their callback faster. + + switch (mMessage.type) { + // Notes + case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != NULL) mClockCallback(); break; + case Start: if (mStartCallback != NULL) mStartCallback(); break; + case Continue: if (mContinueCallback != NULL) mContinueCallback(); break; + case Stop: if (mStopCallback != NULL) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != NULL) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != NULL) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) - 8192); break; // TODO: check this + case AfterTouchPoly: if (mAfterTouchPolyCallback != NULL) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != NULL) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; + + case ProgramChange: if (mProgramChangeCallback != NULL) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != NULL) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; + + // Occasional messages + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != NULL) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != NULL) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; + case SongSelect: if (mSongSelectCallback != NULL) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != NULL) mTuneRequestCallback(); break; + + case SystemReset: if (mSystemResetCallback != NULL) mSystemResetCallback(); break; + case InvalidType: + default: + break; + } + } @@ -1047,126 +1047,126 @@ void MIDI_Class::launchCallback() */ void MIDI_Class::setThruFilterMode(kThruFilterMode inThruFilterMode) { - - mThruFilterMode = inThruFilterMode; - if (mThruFilterMode != Off) mThruActivated = true; - else mThruActivated = false; - + + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) mThruActivated = true; + else mThruActivated = false; + } /*! \brief Setter method: turn message mirroring on. */ void MIDI_Class::turnThruOn(kThruFilterMode inThruFilterMode) { - - mThruActivated = true; - mThruFilterMode = inThruFilterMode; - + + mThruActivated = true; + mThruFilterMode = inThruFilterMode; + } /*! \brief Setter method: turn message mirroring off. */ void MIDI_Class::turnThruOff() { - - mThruActivated = false; - mThruFilterMode = Off; - + + mThruActivated = false; + mThruFilterMode = Off; + } // This method is called upon reception of a message and takes care of Thru filtering and sending. void MIDI_Class::thru_filter(byte inChannel) { - - /* - 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 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 { - - // 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.sysex_array,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; - - } - - } - + + /* + 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 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 { + + // 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.sysex_array,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.h b/src/MIDI.h index 0ca4ec6..993c1d6 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -1,11 +1,11 @@ /*! - * @file MIDI.h - * Project MIDI Library - * @brief MIDI Library for the Arduino - * Version 3.2 - * @author Francois Best - * @date 24/02/11 - * License GPL Forty Seven Effects - 2011 + * @file MIDI.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @version 3.2 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 */ #ifndef LIB_MIDI_H_ @@ -15,40 +15,40 @@ /* - ############################################################### - # # - # CONFIGURATION AREA # - # # - # Here are a few settings you can change to customize # - # the library for your own project. You can for example # - # choose to compile only parts of it so you gain flash # - # space and optimise the speed of your sketch. # - # # - ############################################################### + ############################################################### + # # + # CONFIGURATION AREA # + # # + # Here are a few settings you can change to customize # + # the library for your own project. You can for example # + # choose to compile only parts of it so you gain flash # + # space and optimise the speed of your sketch. # + # # + ############################################################### */ #define COMPILE_MIDI_IN 1 // Set this setting to 1 to use the MIDI input. #define COMPILE_MIDI_OUT 1 // Set this setting to 1 to use the MIDI output. #define COMPILE_MIDI_THRU 1 // Set this setting to 1 to use the MIDI Soft Thru feature - // Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. +// Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. #define USE_SERIAL_PORT Serial // Change the number (to Serial1 for example) if you want - // to use a different serial port for MIDI I/O. +// to use a different serial port for MIDI I/O. #define USE_SOFTWARE_SERIAL 1 // Set to 1 to use SoftwareSerial instead of native serial ports. #define SOFTSERIAL_RX_PIN 1 // This pin number will be used for MIDI Input #define SOFTSERIAL_TX_PIN 2 // This pin number will be used for MIDI Output. -#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values - // of the same type and channel. - // Set to 0 if you have troubles with controlling you hardware. +#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values +// of the same type and channel. +// Set to 0 if you have troubles with controlling you hardware. #define USE_CALLBACKS 1 // Set this to 1 if you want to use callback handlers (to bind your functions to the library). - // To use the callbacks, you need to have COMPILE_MIDI_IN set to 1 +// To use the callbacks, you need to have COMPILE_MIDI_IN set to 1 #define USE_1BYTE_PARSING 1 // Each call to MIDI.read will only parse one byte (might be faster). @@ -56,12 +56,12 @@ // END OF CONFIGURATION AREA // (do not modify anything under this line unless you know what you are doing) -#define MIDI_BAUDRATE 31250 +#define MIDI_BAUDRATE 31250 -#define MIDI_CHANNEL_OMNI 0 -#define MIDI_CHANNEL_OFF 17 // and over +#define MIDI_CHANNEL_OMNI 0 +#define MIDI_CHANNEL_OFF 17 // and over -#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. +#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. /*! Type definition for practical use (because "unsigned char" is a bit long to write.. )*/ typedef uint8_t byte; @@ -69,249 +69,249 @@ typedef uint16_t word; /*! Enumeration of MIDI types */ enum kMIDIType { - 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 - 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 - 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 - 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 + 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 + 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 + InvalidType = 0x00 ///< For notifying errors }; /*! Enumeration of Thru filter modes */ enum kThruFilterMode { - 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. + 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. }; /*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). \n */ struct midimsg { - /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ - byte channel; - /*! The type of the message (see the define section for types reference) */ - kMIDIType type; - /*! The first data byte.\n Value goes from 0 to 127.\n */ - byte data1; - /*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */ - byte data2; - /*! System Exclusive dedicated byte array. \n Array length is stocked on 16 bits, in data1 (LSB) and data2 (MSB) */ - byte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; - /*! 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; + /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ + byte channel; + /*! The type of the message (see the define section for types reference) */ + kMIDIType type; + /*! The first data byte.\n Value goes from 0 to 127.\n */ + byte data1; + /*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */ + byte data2; + /*! System Exclusive dedicated byte array. \n Array length is stocked on 16 bits, in data1 (LSB) and data2 (MSB) */ + byte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; + /*! 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; }; /*! \brief The main class for MIDI handling.\n - See member descriptions to know how to use it, - or check out the examples supplied with the library. + See member descriptions to know how to use it, + or check out the examples supplied with the library. */ class MIDI_Class { - - + + public: - // Constructor and Destructor - MIDI_Class(); - ~MIDI_Class(); - - - void begin(const byte inChannel = 1); - - - - -/* ####### OUTPUT COMPILATION BLOCK ####### */ + // Constructor and Destructor + MIDI_Class(); + ~MIDI_Class(); + + + void begin(const byte inChannel = 1); + + + + + /* ####### OUTPUT COMPILATION BLOCK ####### */ #if COMPILE_MIDI_OUT - -public: - - void sendNoteOn(byte NoteNumber,byte Velocity,byte Channel); - void sendNoteOff(byte NoteNumber,byte Velocity,byte Channel); - void sendProgramChange(byte ProgramNumber,byte Channel); - void sendControlChange(byte ControlNumber, byte ControlValue,byte Channel); - void sendPitchBend(int PitchValue,byte Channel); - void sendPitchBend(unsigned int PitchValue,byte Channel); - void sendPitchBend(double PitchValue,byte Channel); - void sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel); - void sendAfterTouch(byte Pressure,byte Channel); - void sendSysEx(int length, const byte *const array,bool ArrayContainsBoundaries = false); - void sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble); - void sendTimeCodeQuarterFrame(byte data); - void sendSongPosition(unsigned int Beats); - void sendSongSelect(byte SongNumber); - void sendTuneRequest(); - void sendRealTime(kMIDIType Type); - - void send(kMIDIType type, byte param1, byte param2, byte channel); - + +public: + + void sendNoteOn(byte NoteNumber,byte Velocity,byte Channel); + void sendNoteOff(byte NoteNumber,byte Velocity,byte Channel); + void sendProgramChange(byte ProgramNumber,byte Channel); + void sendControlChange(byte ControlNumber, byte ControlValue,byte Channel); + void sendPitchBend(int PitchValue,byte Channel); + void sendPitchBend(unsigned int PitchValue,byte Channel); + void sendPitchBend(double PitchValue,byte Channel); + void sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel); + void sendAfterTouch(byte Pressure,byte Channel); + void sendSysEx(int length, const byte *const array,bool ArrayContainsBoundaries = false); + void sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble); + void sendTimeCodeQuarterFrame(byte data); + void sendSongPosition(unsigned int Beats); + void sendSongSelect(byte SongNumber); + void sendTuneRequest(); + void sendRealTime(kMIDIType Type); + + void send(kMIDIType type, byte param1, byte param2, byte channel); + private: - - const byte genstatus(const kMIDIType inType,const byte inChannel) const; - - - // Attributes + + const byte genstatus(const kMIDIType inType,const byte inChannel) const; + + + // Attributes #if USE_RUNNING_STATUS - byte mRunningStatus_TX; + byte mRunningStatus_TX; #endif // USE_RUNNING_STATUS - -#endif // COMPILE_MIDI_OUT - - - -/* ####### INPUT COMPILATION BLOCK ####### */ -#if COMPILE_MIDI_IN - + +#endif // COMPILE_MIDI_OUT + + + + /* ####### INPUT COMPILATION BLOCK ####### */ +#if COMPILE_MIDI_IN + public: - - bool read(); - bool read(const byte Channel); - - // Getters - kMIDIType getType() const; - byte getChannel() const; - byte getData1() const; - byte getData2() const; - const byte * getSysExArray() const; - unsigned int getSysExArrayLength() const; - bool check() const; - - byte getInputChannel() const + + bool read(); + bool read(const byte Channel); + + // Getters + kMIDIType getType() const; + byte getChannel() const; + byte getData1() const; + byte getData2() const; + const byte * getSysExArray() const; + unsigned int getSysExArrayLength() const; + bool check() const; + + byte getInputChannel() const { return mInputChannel; } - - // Setters - void setInputChannel(const byte Channel); - - /*! \brief Extract an enumerated MIDI type from a status byte. - - This is a utility static method, used internally, made public so you can handle kMIDITypes more easily. - */ - static inline const kMIDIType getTypeFromStatusByte(const byte inStatus) + + // Setters + void setInputChannel(const byte Channel); + + /*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, made public so you can handle kMIDITypes more easily. + */ + static inline const kMIDIType getTypeFromStatusByte(const byte inStatus) { - if ((inStatus < 0x80) - || (inStatus == 0xF4) - || (inStatus == 0xF5) - || (inStatus == 0xF9) - || (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. - if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble. - else return (kMIDIType)inStatus; - } - - + if ((inStatus < 0x80) + || (inStatus == 0xF4) + || (inStatus == 0xF5) + || (inStatus == 0xF9) + || (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. + if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble. + else return (kMIDIType)inStatus; + } + + #if USE_CALLBACKS - - void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); - void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); - void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); - void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); - void setHandleProgramChange(void (*fptr)(byte channel, byte number)); - void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); - void setHandlePitchBend(void (*fptr)(byte channel, int bend)); - void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); - void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); - void setHandleSongPosition(void (*fptr)(unsigned int beats)); - void setHandleSongSelect(void (*fptr)(byte songnumber)); - void setHandleTuneRequest(void (*fptr)(void)); - void setHandleClock(void (*fptr)(void)); - void setHandleStart(void (*fptr)(void)); - void setHandleContinue(void (*fptr)(void)); - void setHandleStop(void (*fptr)(void)); - void setHandleActiveSensing(void (*fptr)(void)); - void setHandleSystemReset(void (*fptr)(void)); - - void disconnectCallbackFromType(kMIDIType Type); - + + void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); + void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); + void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); + void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); + void setHandleProgramChange(void (*fptr)(byte channel, byte number)); + void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); + void setHandlePitchBend(void (*fptr)(byte channel, int bend)); + void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); + void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); + void setHandleSongPosition(void (*fptr)(unsigned int beats)); + void setHandleSongSelect(void (*fptr)(byte songnumber)); + void setHandleTuneRequest(void (*fptr)(void)); + void setHandleClock(void (*fptr)(void)); + void setHandleStart(void (*fptr)(void)); + void setHandleContinue(void (*fptr)(void)); + void setHandleStop(void (*fptr)(void)); + void setHandleActiveSensing(void (*fptr)(void)); + void setHandleSystemReset(void (*fptr)(void)); + + void disconnectCallbackFromType(kMIDIType Type); + #endif // USE_CALLBACKS - - + + private: - - bool input_filter(byte inChannel); - bool parse(byte inChannel); - void reset_input_attributes(); - - // Attributes - byte mRunningStatus_RX; - byte mInputChannel; - - byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; - unsigned int mPendingMessageExpectedLenght; - unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. - - midimsg mMessage; - + + bool input_filter(byte inChannel); + bool parse(byte inChannel); + void reset_input_attributes(); + + // Attributes + byte mRunningStatus_RX; + byte mInputChannel; + + byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; + unsigned int mPendingMessageExpectedLenght; + unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. + + midimsg mMessage; + #if USE_CALLBACKS - - 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, byte size); - void (*mTimeCodeQuarterFrameCallback)(byte data); - void (*mSongPositionCallback)(unsigned int beats); - void (*mSongSelectCallback)(byte songnumber); - void (*mTuneRequestCallback)(void); - void (*mClockCallback)(void); - void (*mStartCallback)(void); - void (*mContinueCallback)(void); - void (*mStopCallback)(void); - void (*mActiveSensingCallback)(void); - void (*mSystemResetCallback)(void); - + + void 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, byte size); + void (*mTimeCodeQuarterFrameCallback)(byte data); + void (*mSongPositionCallback)(unsigned int 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); + #endif // USE_CALLBACKS - - + + #endif // COMPILE_MIDI_IN - - -/* ####### THRU COMPILATION BLOCK ####### */ + + + /* ####### THRU COMPILATION BLOCK ####### */ #if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru - + public: - - // Getters - kThruFilterMode getFilterMode() const { return mThruFilterMode; } - bool getThruState() const { return mThruActivated; } - - - // Setters - void turnThruOn(kThruFilterMode inThruFilterMode = Full); - void turnThruOff(); - - void setThruFilterMode(const kThruFilterMode inThruFilterMode); - - + + // Getters + kThruFilterMode getFilterMode() const { return mThruFilterMode; } + bool getThruState() const { return mThruActivated; } + + + // Setters + void turnThruOn(kThruFilterMode inThruFilterMode = Full); + void turnThruOff(); + + void setThruFilterMode(const kThruFilterMode inThruFilterMode); + + private: - - void thru_filter(byte inChannel); - - bool mThruActivated; - kThruFilterMode mThruFilterMode; - + + void thru_filter(byte inChannel); + + bool mThruActivated; + kThruFilterMode mThruFilterMode; + #endif // Thru - + }; extern MIDI_Class MIDI;