diff --git a/src/MIDI.cpp b/src/MIDI.cpp index a59ae71..24bb14c 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -36,779 +36,21 @@ # endif # undef MIDI_SERIAL_PORT # define MIDI_SERIAL_PORT softSerialClass + MIDI_NAMESPACE::MidiInterface MIDI(MIDI_SERIAL_PORT); + # else # ifdef FSE_AVR # include # else # include "HardwareSerial.h" # endif + MIDI_NAMESPACE::MidiInterface MIDI((HardwareSerial&)Serial); # endif // MIDI_USE_SOFTWARE_SERIAL - - MIDI_NAMESPACE::MidiInterface MIDI; - #endif // MIDI_AUTO_INSTANCIATE // ----------------------------------------------------------------------------- BEGIN_MIDI_NAMESPACE -// ----------------------------------------------------------------------------- - -/*! \brief Constructor for MidiInterface. */ -MidiInterface::MidiInterface() -{ -#if MIDI_BUILD_INPUT && MIDI_USE_CALLBACKS - mNoteOffCallback = 0; - mNoteOnCallback = 0; - mAfterTouchPolyCallback = 0; - mControlChangeCallback = 0; - mProgramChangeCallback = 0; - mAfterTouchChannelCallback = 0; - mPitchBendCallback = 0; - mSystemExclusiveCallback = 0; - mTimeCodeQuarterFrameCallback = 0; - mSongPositionCallback = 0; - mSongSelectCallback = 0; - mTuneRequestCallback = 0; - mClockCallback = 0; - mStartCallback = 0; - mContinueCallback = 0; - mStopCallback = 0; - mActiveSensingCallback = 0; - mSystemResetCallback = 0; -#endif -} - -/*! \brief Destructor for MidiInterface. - - This is not really useful for the Arduino, as it is never called... - */ -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 - */ -void MidiInterface::begin(Channel inChannel) -{ - // Initialise the Serial port - MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); - -#if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS - - mRunningStatus_TX = InvalidType; - -#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 // MIDI_BUILD_INPUT - - -#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru - - mThruFilterMode = Full; - mThruActivated = true; - -#endif // Thru - -} - - -// ----------------------------------------------------------------------------- -// Output -// ----------------------------------------------------------------------------- - -#if MIDI_BUILD_OUTPUT - -/*! \brief Generate and send a MIDI message from the values given. - \param inType The message type (see type defines for reference) - \param inData1 The first data byte. - \param inData2 The second data byte (if the message contains only 1 data byte, - set this one to 0). - \param inChannel The output channel on which the message will be sent - (values from 1 to 16). Note: you cannot send to OMNI. - - This is an internal method, use it only if you need to send raw data - from your code, at your own risks. - */ -void MidiInterface::send(MidiType inType, - DataByte inData1, - DataByte inData2, - Channel inChannel) -{ - // Then test if channel is valid - if (inChannel >= MIDI_CHANNEL_OFF || - inChannel == MIDI_CHANNEL_OMNI || - inType < NoteOff) - { - -#if MIDI_USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; -#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) - { - // New message, memorise and send header - mRunningStatus_TX = status; - MIDI_SERIAL_PORT.write(mRunningStatus_TX); - } -#else - // Don't care about running status, send the status byte. - MIDI_SERIAL_PORT.write(status); -#endif - - // Then send data - MIDI_SERIAL_PORT.write(inData1); - if (inType != ProgramChange && inType != AfterTouchChannel) - MIDI_SERIAL_PORT.write(inData2); - - return; - } - else if (inType >= TuneRequest && inType <= SystemReset) - sendRealTime(inType); // System Real-time and 1 byte. -} - -/*! \brief Encode System Exclusive messages. - SysEx messages are encoded to guarantee transmission of data bytes higher than - 127 without breaking the MIDI protocol. Use this static method to convert the - data you want to send. - \param inData The data to encode. - \param outSysEx The output buffer where to store the encoded message. - \param inLength The lenght of the input buffer. - \return The lenght of the encoded output buffer. - @see decodeSysEx - */ -byte MidiInterface::encodeSysEx(const byte* inData, - byte* outSysEx, - byte inLength) -{ - byte retlen = 0; - byte compteur; - byte count7 = 0; - - outSysEx[0] = 0; - for (compteur = 0; compteur < inLength; compteur++) { - byte c = inData[compteur] & 0x7F; - byte msb = inData[compteur] >> 7; - outSysEx[0] |= msb << count7; - outSysEx[1 + count7] = c; - - if (count7++ == 6) { - outSysEx += 8; - retlen += 8; - outSysEx[0] = 0; - count7 = 0; - } - } - return retlen + count7 + ((count7 != 0)?1:0); -} - -#endif // MIDI_BUILD_OUTPUT - - -// ----------------------------------------------------------------------------- -// Input -// ----------------------------------------------------------------------------- - -#if MIDI_BUILD_INPUT - -/*! \brief Read a MIDI message from the serial port - using the main input channel (see setInputChannel() for reference). - - \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 messages matches the filter, - it is sent back on the MIDI output. - */ -bool MidiInterface::read() -{ - return read(mInputChannel); -} - -/*! \brief Reading/thru-ing method, the same as read() - with a given input channel to read on. - */ -bool MidiInterface::read(Channel inChannel) -{ - if (inChannel >= MIDI_CHANNEL_OFF) - return false; // MIDI Input disabled. - - if (parse(inChannel)) - { - if (inputFilter(inChannel)) - { - -#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) - thruFilter(inChannel); -#endif - -#if MIDI_USE_CALLBACKS - launchCallback(); -#endif - return true; - } - } - - return false; -} - -// ----------------------------------------------------------------------------- - -// Private method: MIDI parser -bool MidiInterface::parse(Channel inChannel) -{ - const byte bytes_available = MIDI_SERIAL_PORT.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, - 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 = MIDI_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. - - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) - { - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; - mMessage.data1 = mPendingMessage[1]; - - // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) - mMessage.data2 = mPendingMessage[2]; - else - mMessage.data2 = 0; - - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - mMessage.valid = true; - return true; - } - 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. - //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; - break; - - case InvalidType: - default: - // This is obviously wrong. Let's get the hell out'a here. - resetInput(); - return false; - break; - } - - // Then update the index of the pending message. - mPendingMessageIndex++; - -#if USE_1BYTE_PARSING - // Message is not complete. - return false; -#else - // 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: - - // 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; - - 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; - - resetInput(); - return true; - } - else - { - // Well well well.. error. - resetInput(); - 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) - { - resetInput(); - return false; - } - - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - // Don't check if it is a Channel Message - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; - - 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; -#else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); -#endif - } - } - - // What are our chances to fall here? - return false; -} - -// Private method: check if the received message is on the listened channel -bool MidiInterface::inputFilter(Channel 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; - } -} - -// Private method: reset input attributes -void MidiInterface::resetInput() -{ - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - mRunningStatus_RX = InvalidType; -} - -// ----------------------------------------------------------------------------- - -#if MIDI_USE_CALLBACKS - -// Private - launch callback function based on received type. -void MidiInterface::launchCallback() -{ - // The order is mixed to allow frequent messages to trigger their callback faster. - switch (mMessage.type) - { - // Notes - case NoteOff: if (mNoteOffCallback != 0) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case NoteOn: if (mNoteOnCallback != 0) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - - // Real-time messages - case Clock: if (mClockCallback != 0) mClockCallback(); break; - case Start: if (mStartCallback != 0) mStartCallback(); break; - case Continue: if (mContinueCallback != 0) mContinueCallback(); break; - case Stop: if (mStopCallback != 0) mStopCallback(); break; - case ActiveSensing: if (mActiveSensingCallback != 0) mActiveSensingCallback(); break; - - // Continuous controllers - case ControlChange: if (mControlChangeCallback != 0) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case PitchBend: if (mPitchBendCallback != 0) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) + MIDI_PITCHBEND_MIN); break; // 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.sysex_array,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: - break; - } -} - -#endif // MIDI_USE_CALLBACKS - -// ----------------------------------------------------------------------------- - -/*! \brief Decode System Exclusive messages. - SysEx messages are encoded to guarantee transmission of data bytes higher than - 127 without breaking the MIDI protocol. Use this static method to reassemble - your received message. - \param inSysEx The SysEx data received from MIDI in. - \param outData The output buffer where to store the decrypted message. - \param inLength The lenght of the input buffer. - \return The lenght of the output buffer. - @see encodeSysEx @see getSysExArrayLength - */ -byte MidiInterface::decodeSysEx(const byte* inSysEx, - byte* outData, - byte inLength) -{ - - byte cnt; - byte cnt2 = 0; - byte bits = 0; - - for (cnt = 0; cnt < inLength; ++cnt) { - - if ((cnt % 8) == 0) { - bits = inSysEx[cnt]; - } - else { - outData[cnt2++] = inSysEx[cnt] | ((bits & 1) << 7); - bits >>= 1; - } - - } - return cnt2; -} - -#endif // MIDI_BUILD_INPUT - - -// ----------------------------------------------------------------------------- -// Thru -// ----------------------------------------------------------------------------- - -#if MIDI_BUILD_THRU - -// This method is called upon reception of a message -// and takes care of Thru filtering and sending. - -void MidiInterface::thruFilter(Channel 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; - } - } -} - -#endif // MIDI_BUILD_THRU END_MIDI_NAMESPACE diff --git a/src/MIDI.h b/src/MIDI.h index 77adca0..b672808 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -13,22 +13,17 @@ #include "midi_Settings.h" #include "midi_Defs.h" -#ifdef FSE_AVR -# include "hardware_Serial.h" -#else -# include "Arduino.h" -#endif - // ----------------------------------------------------------------------------- BEGIN_MIDI_NAMESPACE /*! \brief The main class for MIDI handling. */ +template class MidiInterface { public: - MidiInterface(); + MidiInterface(SerialPort& inSerial); ~MidiInterface(); public: @@ -221,6 +216,8 @@ private: #endif // MIDI_USE_RUNNING_STATUS +private: + SerialPort& mSerial; }; END_MIDI_NAMESPACE @@ -228,7 +225,7 @@ END_MIDI_NAMESPACE // ----------------------------------------------------------------------------- #if MIDI_AUTO_INSTANCIATE - extern MIDI_NAMESPACE::MidiInterface MIDI; + extern MIDI_NAMESPACE::MidiInterface MIDI; #endif // MIDI_AUTO_INSTANCIATE // ----------------------------------------------------------------------------- diff --git a/src/midi_Defs.h b/src/midi_Defs.h index 249aec5..c88c3f9 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -194,4 +194,9 @@ struct Message }; +// ----------------------------------------------------------------------------- + +#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \ + midi::MidiInterface Name((Type&)SerialPort); + END_MIDI_NAMESPACE diff --git a/src/midi_Inline.hpp b/src/midi_Inline.hpp index f26c3c8..d8d919d 100644 --- a/src/midi_Inline.hpp +++ b/src/midi_Inline.hpp @@ -12,6 +12,786 @@ BEGIN_MIDI_NAMESPACE + +/*! \brief Constructor for MidiInterface. */ +template +MidiInterface::MidiInterface(SerialPort& inSerial) + : mSerial(inSerial) +{ +#if MIDI_BUILD_INPUT && MIDI_USE_CALLBACKS + mNoteOffCallback = 0; + mNoteOnCallback = 0; + mAfterTouchPolyCallback = 0; + mControlChangeCallback = 0; + mProgramChangeCallback = 0; + mAfterTouchChannelCallback = 0; + mPitchBendCallback = 0; + mSystemExclusiveCallback = 0; + mTimeCodeQuarterFrameCallback = 0; + mSongPositionCallback = 0; + mSongSelectCallback = 0; + mTuneRequestCallback = 0; + mClockCallback = 0; + mStartCallback = 0; + mContinueCallback = 0; + mStopCallback = 0; + mActiveSensingCallback = 0; + mSystemResetCallback = 0; +#endif +} + +/*! \brief Destructor for MidiInterface. + + This is not really useful for the Arduino, as it is never called... + */ +template +MidiInterface::~MidiInterface() +{ +} + +// ----------------------------------------------------------------------------- + +/*! \brief Call the begin method in the setup() function of the Arduino. + + All parameters are set to their default values: + - Input channel set to 1 if no value is specified + - Full thru mirroring + */ +template +void MidiInterface::begin(Channel inChannel) +{ + // Initialise the Serial port + mSerial.begin(MIDI_BAUDRATE); + +#if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS + + mRunningStatus_TX = InvalidType; + +#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 // MIDI_BUILD_INPUT + + +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru + + mThruFilterMode = Full; + mThruActivated = true; + +#endif // Thru + +} + + +// ----------------------------------------------------------------------------- +// Output +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_OUTPUT + +/*! \brief Generate and send a MIDI message from the values given. + \param inType The message type (see type defines for reference) + \param inData1 The first data byte. + \param inData2 The second data byte (if the message contains only 1 data byte, + set this one to 0). + \param inChannel The output channel on which the message will be sent + (values from 1 to 16). Note: you cannot send to OMNI. + + This is an internal method, use it only if you need to send raw data + from your code, at your own risks. + */ +template +void MidiInterface::send(MidiType inType, + DataByte inData1, + DataByte inData2, + Channel inChannel) +{ + // Then test if channel is valid + if (inChannel >= MIDI_CHANNEL_OFF || + inChannel == MIDI_CHANNEL_OMNI || + inType < NoteOff) + { + +#if MIDI_USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#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) + { + // New message, memorise and send header + mRunningStatus_TX = status; + mSerial.write(mRunningStatus_TX); + } +#else + // 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) + sendRealTime(inType); // System Real-time and 1 byte. +} + +/*! \brief Encode System Exclusive messages. + SysEx messages are encoded to guarantee transmission of data bytes higher than + 127 without breaking the MIDI protocol. Use this static method to convert the + data you want to send. + \param inData The data to encode. + \param outSysEx The output buffer where to store the encoded message. + \param inLength The lenght of the input buffer. + \return The lenght of the encoded output buffer. + @see decodeSysEx + */ +template +byte MidiInterface::encodeSysEx(const byte* inData, + byte* outSysEx, + byte inLength) +{ + byte retlen = 0; + byte compteur; + byte count7 = 0; + + outSysEx[0] = 0; + for (compteur = 0; compteur < inLength; compteur++) { + byte c = inData[compteur] & 0x7F; + byte msb = inData[compteur] >> 7; + outSysEx[0] |= msb << count7; + outSysEx[1 + count7] = c; + + if (count7++ == 6) { + outSysEx += 8; + retlen += 8; + outSysEx[0] = 0; + count7 = 0; + } + } + return retlen + count7 + ((count7 != 0)?1:0); +} + +#endif // MIDI_BUILD_OUTPUT + + +// ----------------------------------------------------------------------------- +// Input +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_INPUT + +/*! \brief Read a MIDI message from the serial port + using the main input channel (see setInputChannel() for reference). + + \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 messages matches the filter, + it is sent back on the MIDI output. + */ +template +bool MidiInterface::read() +{ + return read(mInputChannel); +} + +/*! \brief Reading/thru-ing method, the same as read() + with a given input channel to read on. + */ +template +bool MidiInterface::read(Channel inChannel) +{ + if (inChannel >= MIDI_CHANNEL_OFF) + return false; // MIDI Input disabled. + + if (parse(inChannel)) + { + if (inputFilter(inChannel)) + { + +#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) + thruFilter(inChannel); +#endif + +#if MIDI_USE_CALLBACKS + launchCallback(); +#endif + return true; + } + } + + return false; +} + +// ----------------------------------------------------------------------------- + +// Private method: MIDI parser +template +bool MidiInterface::parse(Channel inChannel) +{ + 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, + 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 = mSerial.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. + + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) + { + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mMessage.valid = true; + return true; + } + 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. + //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; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + resetInput(); + return false; + break; + } + + // Then update the index of the pending message. + mPendingMessageIndex++; + +#if USE_1BYTE_PARSING + // Message is not complete. + return false; +#else + // 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: + + // 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; + + 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; + + resetInput(); + return true; + } + else + { + // Well well well.. error. + resetInput(); + 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) + { + resetInput(); + return false; + } + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + // Don't check if it is a Channel Message + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; + + 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; +#else + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); +#endif + } + } + + // What are our chances to fall here? + return false; +} + +// Private method: check if the received message is on the listened channel +template +bool MidiInterface::inputFilter(Channel 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; + } +} + +// Private method: reset input attributes +template +void MidiInterface::resetInput() +{ + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; +} + +// ----------------------------------------------------------------------------- + +#if MIDI_USE_CALLBACKS + +// Private - launch callback function based on received type. +template +void MidiInterface::launchCallback() +{ + // The order is mixed to allow frequent messages to trigger their callback faster. + switch (mMessage.type) + { + // Notes + case NoteOff: if (mNoteOffCallback != 0) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != 0) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != 0) mClockCallback(); break; + case Start: if (mStartCallback != 0) mStartCallback(); break; + case Continue: if (mContinueCallback != 0) mContinueCallback(); break; + case Stop: if (mStopCallback != 0) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != 0) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != 0) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != 0) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) + MIDI_PITCHBEND_MIN); break; // 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.sysex_array,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: + break; + } +} + +#endif // MIDI_USE_CALLBACKS + +// ----------------------------------------------------------------------------- + +/*! \brief Decode System Exclusive messages. + SysEx messages are encoded to guarantee transmission of data bytes higher than + 127 without breaking the MIDI protocol. Use this static method to reassemble + your received message. + \param inSysEx The SysEx data received from MIDI in. + \param outData The output buffer where to store the decrypted message. + \param inLength The lenght of the input buffer. + \return The lenght of the output buffer. + @see encodeSysEx @see getSysExArrayLength + */ +template +byte MidiInterface::decodeSysEx(const byte* inSysEx, + byte* outData, + byte inLength) +{ + + byte cnt; + byte cnt2 = 0; + byte bits = 0; + + for (cnt = 0; cnt < inLength; ++cnt) { + + if ((cnt % 8) == 0) { + bits = inSysEx[cnt]; + } + else { + outData[cnt2++] = inSysEx[cnt] | ((bits & 1) << 7); + bits >>= 1; + } + + } + return cnt2; +} + +#endif // MIDI_BUILD_INPUT + + +// ----------------------------------------------------------------------------- +// Thru +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_THRU + +// This method is called upon reception of a message +// and takes care of Thru filtering and sending. + +template +void MidiInterface::thruFilter(Channel 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; + } + } +} + +#endif // MIDI_BUILD_THRU + + + +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- + + // ----------------------------------------------------------------------------- // Output // ----------------------------------------------------------------------------- @@ -27,7 +807,8 @@ BEGIN_MIDI_NAMESPACE Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html */ -void MidiInterface::sendNoteOn(DataByte inNoteNumber, +template +void MidiInterface::sendNoteOn(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel) { @@ -45,7 +826,8 @@ void MidiInterface::sendNoteOn(DataByte inNoteNumber, Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html */ -void MidiInterface::sendNoteOff(DataByte inNoteNumber, +template +void MidiInterface::sendNoteOff(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel) { @@ -56,7 +838,8 @@ void MidiInterface::sendNoteOff(DataByte inNoteNumber, \param inProgramNumber The Program to select (0 to 127). \param inChannel The channel on which the message will be sent (1 to 16). */ -void MidiInterface::sendProgramChange(DataByte inProgramNumber, +template +void MidiInterface::sendProgramChange(DataByte inProgramNumber, Channel inChannel) { send(ProgramChange, inProgramNumber, 0, inChannel); @@ -70,7 +853,8 @@ void MidiInterface::sendProgramChange(DataByte inProgramNumber, See the detailed controllers numbers & description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums */ -void MidiInterface::sendControlChange(DataByte inControlNumber, +template +void MidiInterface::sendControlChange(DataByte inControlNumber, DataByte inControlValue, Channel inChannel) { @@ -82,7 +866,8 @@ void MidiInterface::sendControlChange(DataByte inControlNumber, \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 MidiInterface::sendPolyPressure(DataByte inNoteNumber, +template +void MidiInterface::sendPolyPressure(DataByte inNoteNumber, DataByte inPressure, Channel inChannel) { @@ -93,7 +878,8 @@ void MidiInterface::sendPolyPressure(DataByte inNoteNumber, \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 MidiInterface::sendAfterTouch(DataByte inPressure, +template +void MidiInterface::sendAfterTouch(DataByte inPressure, Channel inChannel) { send(AfterTouchChannel, inPressure, 0, inChannel); @@ -105,7 +891,8 @@ void MidiInterface::sendAfterTouch(DataByte inPressure, center value is 0. \param Channel The channel on which the message will be sent (1 to 16). */ -void MidiInterface::sendPitchBend(int inPitchValue, +template +void MidiInterface::sendPitchBend(int inPitchValue, Channel inChannel) { const unsigned int bend = inPitchValue - MIDI_PITCHBEND_MIN; @@ -119,7 +906,8 @@ void MidiInterface::sendPitchBend(int inPitchValue, 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 MidiInterface::sendPitchBend(double inPitchValue, +template +void MidiInterface::sendPitchBend(double inPitchValue, Channel inChannel) { const int value = inPitchValue * MIDI_PITCHBEND_MAX; @@ -135,23 +923,24 @@ void MidiInterface::sendPitchBend(double inPitchValue, default value for ArrayContainsBoundaries is set to 'false' for compatibility with previous versions of the library. */ -void MidiInterface::sendSysEx(unsigned int inLength, +template +void MidiInterface::sendSysEx(unsigned int inLength, const byte* inArray, bool inArrayContainsBoundaries) { if (inArrayContainsBoundaries == false) { - MIDI_SERIAL_PORT.write(0xF0); + mSerial.write(0xF0); for (unsigned int i=0;i +void MidiInterface::sendTuneRequest() { sendRealTime(TuneRequest); } @@ -175,7 +965,8 @@ void MidiInterface::sendTuneRequest() \param ValuesNibble MTC data See MIDI Specification for more information. */ -void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, +template +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, DataByte inValuesNibble) { const byte data = ( ((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0F) ); @@ -188,10 +979,11 @@ void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, \param data if you want to encode directly the nibbles in your program, you can send the byte here. */ -void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) +template +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) { - MIDI_SERIAL_PORT.write((byte)TimeCodeQuarterFrame); - MIDI_SERIAL_PORT.write(inData); + mSerial.write((byte)TimeCodeQuarterFrame); + mSerial.write(inData); #if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; @@ -201,11 +993,12 @@ void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) /*! \brief Send a Song Position Pointer message. \param Beats The number of beats since the start of the song. */ -void MidiInterface::sendSongPosition(unsigned int inBeats) +template +void MidiInterface::sendSongPosition(unsigned int inBeats) { - MIDI_SERIAL_PORT.write((byte)SongPosition); - MIDI_SERIAL_PORT.write(inBeats & 0x7F); - MIDI_SERIAL_PORT.write((inBeats >> 7) & 0x7F); + mSerial.write((byte)SongPosition); + mSerial.write(inBeats & 0x7F); + mSerial.write((inBeats >> 7) & 0x7F); #if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; @@ -213,10 +1006,11 @@ void MidiInterface::sendSongPosition(unsigned int inBeats) } /*! \brief Send a Song Select message */ -void MidiInterface::sendSongSelect(DataByte inSongNumber) +template +void MidiInterface::sendSongSelect(DataByte inSongNumber) { - MIDI_SERIAL_PORT.write((byte)SongSelect); - MIDI_SERIAL_PORT.write(inSongNumber & 0x7F); + mSerial.write((byte)SongSelect); + mSerial.write(inSongNumber & 0x7F); #if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; @@ -230,7 +1024,8 @@ void MidiInterface::sendSongSelect(DataByte inSongNumber) You can also send a Tune Request with this method. @see MidiType */ -void MidiInterface::sendRealTime(MidiType inType) +template +void MidiInterface::sendRealTime(MidiType inType) { switch (inType) { @@ -241,7 +1036,7 @@ void MidiInterface::sendRealTime(MidiType inType) case Continue: case ActiveSensing: case SystemReset: - MIDI_SERIAL_PORT.write((byte)inType); + mSerial.write((byte)inType); break; default: // Invalid Real Time marker @@ -258,7 +1053,8 @@ void MidiInterface::sendRealTime(MidiType inType) // ----------------------------------------------------------------------------- -StatusByte MidiInterface::getStatus(MidiType inType, +template +StatusByte MidiInterface::getStatus(MidiType inType, Channel inChannel) const { return ((byte)inType | ((inChannel - 1) & 0x0F)); @@ -277,7 +1073,8 @@ StatusByte MidiInterface::getStatus(MidiType inType, Returns an enumerated type. @see MidiType */ -MidiType MidiInterface::getType() const +template +MidiType MidiInterface::getType() const { return mMessage.type; } @@ -287,19 +1084,22 @@ MidiType MidiInterface::getType() const \return Channel range is 1 to 16. For non-channel messages, this will return 0. */ -Channel MidiInterface::getChannel() const +template +Channel MidiInterface::getChannel() const { return mMessage.channel; } /*! \brief Get the first data byte of the last received message. */ -DataByte MidiInterface::getData1() const +template +DataByte MidiInterface::getData1() const { return mMessage.data1; } /*! \brief Get the second data byte of the last received message. */ -DataByte MidiInterface::getData2() const +template +DataByte MidiInterface::getData2() const { return mMessage.data2; } @@ -308,7 +1108,8 @@ DataByte MidiInterface::getData2() const @see getSysExArrayLength to get the array's length in bytes. */ -const byte* MidiInterface::getSysExArray() const +template +const byte* MidiInterface::getSysExArray() const { return mMessage.sysex_array; } @@ -318,21 +1119,24 @@ const byte* MidiInterface::getSysExArray() const It is coded using data1 as LSB and data2 as MSB. \return The array's length, in bytes. */ -unsigned int MidiInterface::getSysExArrayLength() const +template +unsigned int MidiInterface::getSysExArrayLength() const { 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. */ -bool MidiInterface::check() const +template +bool MidiInterface::check() const { return mMessage.valid; } // ----------------------------------------------------------------------------- -Channel MidiInterface::getInputChannel() const +template +Channel MidiInterface::getInputChannel() const { return mInputChannel; } @@ -341,7 +1145,8 @@ Channel MidiInterface::getInputChannel() const \param Channel 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. */ -void MidiInterface::setInputChannel(Channel inChannel) +template +void MidiInterface::setInputChannel(Channel inChannel) { mInputChannel = inChannel; } @@ -353,7 +1158,8 @@ void MidiInterface::setInputChannel(Channel inChannel) This is a utility static method, used internally, made public so you can handle MidiTypes more easily. */ -MidiType MidiInterface::getTypeFromStatusByte(const byte inStatus) +template +MidiType MidiInterface::getTypeFromStatusByte(const byte inStatus) { if ((inStatus < 0x80) || (inStatus == 0xF4) || @@ -368,24 +1174,24 @@ MidiType MidiInterface::getTypeFromStatusByte(const byte inStatus) #if MIDI_USE_CALLBACKS -void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } -void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } -void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } -void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } -void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } -void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } -void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } -void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte* array, byte size)) { mSystemExclusiveCallback = fptr; } -void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } -void MidiInterface::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } -void MidiInterface::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } -void MidiInterface::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } -void MidiInterface::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } -void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } -void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } -void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } -void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } -void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } +template void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } +template void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } +template void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } +template void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } +template void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } +template void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } +template void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } +template void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte* array, byte size)) { mSystemExclusiveCallback = fptr; } +template void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = 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; } +template void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +template void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +template void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +template void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +template void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } /*! \brief Detach an external function from the given type. @@ -393,7 +1199,8 @@ void MidiInterface::setHandleSystemReset(void (*fptr)(void)) \param Type The type of message to unbind. When a message of this type is received, no function will be called. */ -void MidiInterface::disconnectCallbackFromType(MidiType inType) +template +void MidiInterface::disconnectCallbackFromType(MidiType inType) { switch (inType) { @@ -431,18 +1238,21 @@ void MidiInterface::disconnectCallbackFromType(MidiType inType) #if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) -MidiFilterMode MidiInterface::getFilterMode() const +template +MidiFilterMode MidiInterface::getFilterMode() const { return mThruFilterMode; } -bool MidiInterface::getThruState() const +template +bool MidiInterface::getThruState() const { return mThruActivated; } /*! \brief Setter method: turn message mirroring on. */ -void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) +template +void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) { mThruActivated = true; mThruFilterMode = inThruFilterMode; @@ -450,7 +1260,8 @@ void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) /*! \brief Setter method: turn message mirroring off. */ -void MidiInterface::turnThruOff() +template +void MidiInterface::turnThruOff() { mThruActivated = false; mThruFilterMode = Off; @@ -461,7 +1272,8 @@ void MidiInterface::turnThruOff() @see MidiFilterMode */ -void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode) +template +void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode) { mThruFilterMode = inThruFilterMode; if (mThruFilterMode != Off) diff --git a/src/midi_Settings.h b/src/midi_Settings.h index debb00f..4d27797 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -12,8 +12,6 @@ #include "midi_Namespace.h" -BEGIN_MIDI_NAMESPACE - // ----------------------------------------------------------------------------- // Here are a few settings you can change to customize @@ -33,21 +31,26 @@ BEGIN_MIDI_NAMESPACE #define MIDI_USE_CALLBACKS 1 +// ----------------------------------------------------------------------------- + // Create a MIDI object automatically on the port defined with MIDI_SERIAL_PORT. -#define MIDI_AUTO_INSTANCIATE 1 +// You can turn this off by adding #define MIDI_AUTO_INSTANCIATE 0 just before +// including in your sketch. +#ifndef MIDI_AUTO_INSTANCIATE +# ifndef FSE_AVR +# define MIDI_AUTO_INSTANCIATE 1 +# endif +#endif // ----------------------------------------------------------------------------- -// Serial port configuration +// Default serial port configuration (if MIDI_AUTO_INSTANCIATE is set) // Set the default port to use for MIDI. -#define MIDI_SERIAL_PORT Serial - -// Software serial options -#define MIDI_USE_SOFTWARE_SERIAL 0 - -#if MIDI_USE_SOFTWARE_SERIAL - #define MIDI_SOFTSERIAL_RX_PIN 1 // Pin number to use for MIDI Input - #define MIDI_SOFTSERIAL_TX_PIN 2 // Pin number to use for MIDI Output +#if MIDI_AUTO_INSTANCIATE +# define MIDI_DEFAULT_SERIAL_PORT Serial +# define MIDI_SERIAL_CLASS HardwareSerial +# include "Arduino.h" +# include "HardwareSerial.h" #endif // ----------------------------------------------------------------------------- @@ -62,4 +65,8 @@ BEGIN_MIDI_NAMESPACE #define MIDI_BAUDRATE 31250 #define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. +// ----------------------------------------------------------------------------- + +BEGIN_MIDI_NAMESPACE + END_MIDI_NAMESPACE