From 64a3ff548761db3c6ebf5461f3dfd75dee85d9cb Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 21:47:35 +0200 Subject: [PATCH 01/10] Added avr_core sources. --- src/MIDI.cpp | 1161 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/MIDI.h | 313 ++++++++++++++ 2 files changed, 1474 insertions(+) create mode 100644 src/MIDI.cpp create mode 100644 src/MIDI.h diff --git a/src/MIDI.cpp b/src/MIDI.cpp new file mode 100644 index 0000000..177b660 --- /dev/null +++ b/src/MIDI.cpp @@ -0,0 +1,1161 @@ +/*! + * @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 + */ + +#include "MIDI.h" +#include "Serial.h" +#include + + +/*! \brief Main instance (the class comes pre-instantiated). */ +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; + +#endif + +} + + +/*! \brief Default destructor for MIDI_Class. + + This is not really useful for the Arduino, as it is never called... + */ +MIDI_Class::~MIDI_Class() +{ + +} + + +/*! \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 MIDI_Class::begin(const byte inChannel) +{ + + // Initialise the Serial port + USE_SERIAL_PORT.begin(MIDI_BAUDRATE); + + +#if COMPILE_MIDI_OUT + +#if USE_RUNNING_STATUS + + 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; + +#endif // COMPILE_MIDI_IN + + +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + + mThruFilterMode = Full; + mThruActivated = true; + +#endif // Thru + +} + + +#if COMPILE_MIDI_OUT + +// Private method for generating a status byte from channel and type +const byte MIDI_Class::genstatus(const kMIDIType inType, + const byte inChannel) const +{ + + 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. + + 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) +{ + + // 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); + +#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); + } +#else + // 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); + } + +} + + +/*! \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). + */ +void MIDI_Class::sendNoteOn(byte NoteNumber, + byte Velocity, + byte 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). + */ +void MIDI_Class::sendNoteOff(byte NoteNumber, + byte Velocity, + byte 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). + */ +void MIDI_Class::sendProgramChange(byte ProgramNumber, + byte 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). + */ +void MIDI_Class::sendControlChange(byte ControlNumber, + byte ControlValue, + byte 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). + */ +void MIDI_Class::sendPolyPressure(byte NoteNumber, + byte Pressure, + byte 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). + */ +void MIDI_Class::sendAfterTouch(byte Pressure, + byte 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). + */ +void MIDI_Class::sendPitchBend(int PitchValue, + byte 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). + */ +void MIDI_Class::sendPitchBend(unsigned int PitchValue, + byte 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). + */ +void MIDI_Class::sendPitchBend(double PitchValue, + byte 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 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) +{ + + if (ArrayContainsBoundaries == false) { + + USE_SERIAL_PORT.write(0xF0); + + for (int i=0;i> 7) & 0x7F); + +#if USE_RUNNING_STATUS + 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); + +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif + +} + + +/*! \brief Send a Real Time (one byte) message. + + \param Type The available Real Time types are: Start, Stop, Continue, Clock, ActiveSensing and SystemReset. + You can also send a Tune Request with this method. + @see kMIDIType + */ +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. +#if USE_RUNNING_STATUS + if (Type == TuneRequest) mRunningStatus_TX = InvalidType; +#endif + +} + +#endif // COMPILE_MIDI_OUT + + + +#if COMPILE_MIDI_IN + +/*! \brief Read a MIDI message from the serial port using the main input channel (see setInputChannel() for reference). + + Returned value: true if any 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 MIDI_Class::read() +{ + + 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 (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) + thru_filter(inChannel); +#endif + +#if USE_CALLBACKS + launchCallback(); +#endif + + 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 == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; + 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; +#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: + + /* + 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; +#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 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; + } + +} + + +// Private method: reset input attributes +void MIDI_Class::reset_input_attributes() +{ + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; + +} + + +// Getters +/*! \brief Get the last received message's type + + Returns an enumerated type. @see kMIDIType + */ +kMIDIType MIDI_Class::getType() const +{ + + return mMessage.type; + +} + + +/*! \brief Get the channel of the message stored in the structure. + + Channel range is 1 to 16. For non-channel messages, this will return 0. + */ +byte MIDI_Class::getChannel() const +{ + + return mMessage.channel; + +} + + +/*! \brief Get the first data byte of the last received message. */ +byte MIDI_Class::getData1() const +{ + + return mMessage.data1; + +} + + +/*! \brief Get the second data byte of the last received message. */ +byte MIDI_Class::getData2() const +{ + + return mMessage.data2; + +} + + +/*! \brief Get the System Exclusive byte array. + + @see getSysExArrayLength to get the array's length in bytes. + */ +const byte * MIDI_Class::getSysExArray() const +{ + + return mMessage.sysex_array; + +} + +/*! \brief Get the lenght of the System Exclusive array. + + It is coded using data1 as LSB and data2 as MSB. + \return The array's length, in bytes. + */ +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; + +} + + +/*! \brief Check if a valid message is stored in the structure. */ +bool MIDI_Class::check() const +{ + + return mMessage.valid; + +} + + +// Setters +/*! \brief Set the value for the input MIDI channel + \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 MIDI input. + */ +void MIDI_Class::setInputChannel(const byte 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; } + + +/*! \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. + */ +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; + } + +} + + +// 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; + } + +} + + +#endif // USE_CALLBACKS + + +#endif // COMPILE_MIDI_IN + + + + +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + +/*! \brief Set the filter for thru mirroring + \param inThruFilterMode a filter mode + + @see kThruFilterMode + */ +void MIDI_Class::setThruFilterMode(kThruFilterMode inThruFilterMode) +{ + + 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; + +} + + +/*! \brief Setter method: turn message mirroring off. */ +void MIDI_Class::turnThruOff() +{ + + 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; + + } + + } + +} + + +#endif // Thru + + diff --git a/src/MIDI.h b/src/MIDI.h new file mode 100644 index 0000000..ff20f8a --- /dev/null +++ b/src/MIDI.h @@ -0,0 +1,313 @@ +/*! + * @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 + */ + +#ifndef LIB_MIDI_H_ +#define LIB_MIDI_H_ + +#include "Types.h" // Include all the types we need. + + +/* + ############################################################### + # # + # 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. + + +#define USE_SERIAL_PORT Serial1 // Change the number (to Serial1 for example) if you want + // to use a different serial port for MIDI I/O. + + +#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 + +#define USE_1BYTE_PARSING 1 // Each call to MIDI.read will only parse one byte (might be faster). + + +// END OF CONFIGURATION AREA +// (do not modify anything under this line unless you know what you are doing) + + +#define MIDI_BAUDRATE 31250 + +#define MIDI_CHANNEL_OMNI 0 +#define MIDI_CHANNEL_OFF 17 // and over + +#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. + + +/*! 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 +}; + +/*! 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. +}; + + +/*! 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; +}; + + + + +/*! \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. + */ +class MIDI_Class { + + +public: + // 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); + +private: + + const byte genstatus(const kMIDIType inType,const byte inChannel) const; + + + // Attributes +#if USE_RUNNING_STATUS + byte mRunningStatus_TX; +#endif // USE_RUNNING_STATUS + +#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 + { + 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) + { + 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); + +#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; + +#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); + +#endif // USE_CALLBACKS + + +#endif // COMPILE_MIDI_IN + + +/* ####### 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); + + +private: + + void thru_filter(byte inChannel); + + bool mThruActivated; + kThruFilterMode mThruFilterMode; + +#endif // Thru + +}; + +extern MIDI_Class MIDI; + +#endif // LIB_MIDI_H_ From 2dd633816d36be390a1141998b6452537f539765 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 22:09:41 +0200 Subject: [PATCH 02/10] Cosmetics. --- src/MIDI.cpp | 1568 +++++++++++++++++++++++++------------------------- src/MIDI.h | 438 +++++++------- 2 files changed, 1003 insertions(+), 1003 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 177b660..71fec37 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -1,11 +1,11 @@ /*! - * @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 AVR Core MIDI Library + * @brief MIDI Library for the AVR Core + * @version 3.2 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 */ #include "MIDI.h" @@ -20,31 +20,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 - + } @@ -54,7 +54,7 @@ MIDI_Class::MIDI_Class() */ MIDI_Class::~MIDI_Class() { - + } @@ -66,45 +66,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 - + } @@ -112,240 +112,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 - + } @@ -433,27 +433,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 @@ -470,360 +470,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 == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; - 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 == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; + 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; + } @@ -834,9 +834,9 @@ void MIDI_Class::reset_input_attributes() */ kMIDIType MIDI_Class::getType() const { - - return mMessage.type; - + + return mMessage.type; + } @@ -846,27 +846,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; + } @@ -876,9 +876,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. @@ -888,20 +888,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; + } @@ -912,107 +912,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; + } + } @@ -1033,126 +1033,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 ff20f8a..d1c4ef8 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -1,17 +1,17 @@ /*! - * @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 AVR Core MIDI Library + * @brief MIDI Library for the AVR Core + * @version 3.2 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 */ #ifndef LIB_MIDI_H_ #define LIB_MIDI_H_ -#include "Types.h" // Include all the types we need. +#include "Types.h" // Include all the types we need. /* @@ -38,7 +38,7 @@ // to use a different serial port for MIDI I/O. -#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values +#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. @@ -53,259 +53,259 @@ // (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. /*! 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; From a1bea899ce5f3c08adfe428f880fcaa777771de9 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 23:33:58 +0200 Subject: [PATCH 03/10] Changed macro name. --- src/MIDI.cpp | 40 ++++++++++++++++++++-------------------- src/MIDI.h | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 71fec37..28f8b19 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -68,7 +68,7 @@ void MIDI_Class::begin(const byte inChannel) { // Initialise the Serial port - USE_SERIAL_PORT.begin(MIDI_BAUDRATE); + MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); #if COMPILE_MIDI_OUT @@ -158,17 +158,17 @@ void MIDI_Class::send(kMIDIType type, if (mRunningStatus_TX != statusbyte) { // New message, memorise and send header mRunningStatus_TX = statusbyte; - USE_SERIAL_PORT.write(mRunningStatus_TX); + MIDI_SERIAL_PORT.write(mRunningStatus_TX); } #else // Don't care about running status, send the Control byte. - USE_SERIAL_PORT.write(statusbyte); + MIDI_SERIAL_PORT.write(statusbyte); #endif // Then send data - USE_SERIAL_PORT.write(data1); + MIDI_SERIAL_PORT.write(data1); if (type != ProgramChange && type != AfterTouchChannel) { - USE_SERIAL_PORT.write(data2); + MIDI_SERIAL_PORT.write(data2); } return; } @@ -321,22 +321,22 @@ void MIDI_Class::sendSysEx(int length, if (ArrayContainsBoundaries == false) { - USE_SERIAL_PORT.write(0xF0); + MIDI_SERIAL_PORT.write(0xF0); for (int i=0;i> 7) & 0x7F); + MIDI_SERIAL_PORT.write((byte)SongPosition); + MIDI_SERIAL_PORT.write(Beats & 0x7F); + MIDI_SERIAL_PORT.write((Beats >> 7) & 0x7F); #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; @@ -415,8 +415,8 @@ void MIDI_Class::sendSongPosition(unsigned int Beats) void MIDI_Class::sendSongSelect(byte SongNumber) { - USE_SERIAL_PORT.write((byte)SongSelect); - USE_SERIAL_PORT.write(SongNumber & 0x7F); + MIDI_SERIAL_PORT.write((byte)SongSelect); + MIDI_SERIAL_PORT.write(SongNumber & 0x7F); #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; @@ -441,7 +441,7 @@ void MIDI_Class::sendRealTime(kMIDIType Type) case Continue: case ActiveSensing: case SystemReset: - USE_SERIAL_PORT.write((byte)Type); + MIDI_SERIAL_PORT.write((byte)Type); break; default: // Invalid Real Time marker @@ -508,7 +508,7 @@ bool MIDI_Class::read(const byte inChannel) bool MIDI_Class::parse(byte inChannel) { - const int bytes_available = USE_SERIAL_PORT.available(); + const int bytes_available = MIDI_SERIAL_PORT.available(); if (bytes_available <= 0) { // No data available. @@ -517,7 +517,7 @@ bool MIDI_Class::parse(byte inChannel) // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. if (bytes_available == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; - USE_SERIAL_PORT.flush(); + MIDI_SERIAL_PORT.flush(); } else { @@ -530,7 +530,7 @@ bool MIDI_Class::parse(byte inChannel) */ - const byte extracted = USE_SERIAL_PORT.read(); + const byte extracted = MIDI_SERIAL_PORT.read(); if (mPendingMessageIndex == 0) { // Start a new pending message mPendingMessage[0] = extracted; diff --git a/src/MIDI.h b/src/MIDI.h index d1c4ef8..6fb3184 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -34,7 +34,7 @@ // Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. -#define USE_SERIAL_PORT Serial1 // Change the number (to Serial1 for example) if you want +#define MIDI_SERIAL_PORT Serial1 // Change the number (to Serial1 for example) if you want // to use a different serial port for MIDI I/O. From eadffc8222b3659476fb4689b624e8d267932cf2 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 23:40:08 +0200 Subject: [PATCH 04/10] Applied cosmetic patch from Arduino branch. --- src/MIDI.cpp | 125 ++++++++++++++++++++++++++++++++++----------------- src/MIDI.h | 124 +++++++++++++++++++++++++++++++------------------- 2 files changed, 162 insertions(+), 87 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 28f8b19..7ffc5b0 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -108,6 +108,10 @@ void MIDI_Class::begin(const byte inChannel) } +// ============================================================================= +// MIDI Output +// ============================================================================= + #if COMPILE_MIDI_OUT // Private method for generating a status byte from channel and type @@ -122,11 +126,14 @@ const byte MIDI_Class::genstatus(const kMIDIType inType, /*! \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 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. + 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, @@ -181,9 +188,13 @@ void MIDI_Class::send(kMIDIType 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). + \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). + + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html */ void MIDI_Class::sendNoteOn(byte NoteNumber, byte Velocity, @@ -196,9 +207,12 @@ void MIDI_Class::sendNoteOn(byte NoteNumber, /*! \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 NoteNumber Pitch value in the MIDI format (0 to 127). \param Velocity Release velocity (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param Channel The channel on which the message will be sent (1 to 16). + + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html */ void MIDI_Class::sendNoteOff(byte NoteNumber, byte Velocity, @@ -211,8 +225,8 @@ void MIDI_Class::sendNoteOff(byte NoteNumber, /*! \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) @@ -224,9 +238,12 @@ void MIDI_Class::sendProgramChange(byte ProgramNumber, /*! \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 ControlNumber The controller number (0 to 127). \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 Channel The channel on which the message will be sent (1 to 16). + + See the detailed controllers numbers & description here: + http://www.somascape.org/midi/tech/spec.html#ctrlnums */ void MIDI_Class::sendControlChange(byte ControlNumber, byte ControlValue, @@ -239,9 +256,9 @@ void MIDI_Class::sendControlChange(byte ControlNumber, /*! \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, @@ -254,8 +271,8 @@ void MIDI_Class::sendPolyPressure(byte NoteNumber, /*! \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) @@ -267,8 +284,10 @@ void MIDI_Class::sendAfterTouch(byte Pressure, /*! \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) @@ -281,8 +300,10 @@ void MIDI_Class::sendPitchBend(int PitchValue, /*! \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) @@ -294,8 +315,10 @@ void MIDI_Class::sendPitchBend(unsigned int PitchValue, /*! \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) @@ -309,10 +332,13 @@ void MIDI_Class::sendPitchBend(double PitchValue, /*! \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 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. + \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 for ArrayContainsBoundaries is set to 'false' for compatibility + with previous versions of the library. */ void MIDI_Class::sendSysEx(int length, const byte *const array, @@ -351,7 +377,8 @@ void MIDI_Class::sendSysEx(int length, /*! \brief Send a Tune Request message. - When a MIDI unit receives this message, it should tune its oscillators (if equipped with any) + When a MIDI unit receives this message, + it should tune its oscillators (if equipped with any). */ void MIDI_Class::sendTuneRequest() { @@ -363,9 +390,9 @@ void MIDI_Class::sendTuneRequest() /*! \brief Send a MIDI Time Code Quarter Frame. - See MIDI Specification for more information. - \param TypeNibble MTC type + \param TypeNibble MTC type \param ValuesNibble MTC data + See MIDI Specification for more information. */ void MIDI_Class::sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble) { @@ -427,7 +454,8 @@ void MIDI_Class::sendSongSelect(byte SongNumber) /*! \brief Send a Real Time (one byte) message. - \param Type The available Real Time types are: Start, Stop, Continue, Clock, ActiveSensing and SystemReset. + \param Type The available Real Time types are: + Start, Stop, Continue, Clock, ActiveSensing and SystemReset. You can also send a Tune Request with this method. @see kMIDIType */ @@ -459,14 +487,19 @@ void MIDI_Class::sendRealTime(kMIDIType Type) #endif // COMPILE_MIDI_OUT +// ============================================================================= +// MIDI Input +// ============================================================================= #if COMPILE_MIDI_IN -/*! \brief Read a MIDI message from the serial port using the main input channel (see setInputChannel() for reference). +/*! \brief Read a MIDI message from the serial port + using the main input channel (see setInputChannel() for reference). - Returned value: true if any valid message has been stored in the structure, false if not. + \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. + If the Thru is enabled and the messages matches the filter, + it is sent back on the MIDI output. */ bool MIDI_Class::read() { @@ -476,7 +509,9 @@ bool MIDI_Class::read() } -/*! \brief Reading/thru-ing method, the same as read() with a given input channel to read on. */ +/*! \brief Reading/thru-ing method, the same as read() + with a given input channel to read on. + */ bool MIDI_Class::read(const byte inChannel) { @@ -842,7 +877,8 @@ kMIDIType MIDI_Class::getType() const /*! \brief Get the channel of the message stored in the structure. - Channel range is 1 to 16. For non-channel messages, this will return 0. + \return Channel range is 1 to 16. + For non-channel messages, this will return 0. */ byte MIDI_Class::getChannel() const { @@ -908,7 +944,8 @@ bool MIDI_Class::check() const // Setters /*! \brief Set the value for the input MIDI channel \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 MIDI input. + MIDI_CHANNEL_OMNI if you want to listen to all channels, + and MIDI_CHANNEL_OFF to disable MIDI input. */ void MIDI_Class::setInputChannel(const byte Channel) { @@ -943,7 +980,8 @@ void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) /*! \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) { @@ -1022,9 +1060,11 @@ void MIDI_Class::launchCallback() #endif // COMPILE_MIDI_IN +// ============================================================================= +// MIDI Soft Thru +// ============================================================================= - -#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) /*! \brief Set the filter for thru mirroring \param inThruFilterMode a filter mode @@ -1061,7 +1101,8 @@ void MIDI_Class::turnThruOff() } -// This method is called upon reception of a message and takes care of Thru filtering and sending. +// This method is called upon reception of a message +// and takes care of Thru filtering and sending. void MIDI_Class::thru_filter(byte inChannel) { diff --git a/src/MIDI.h b/src/MIDI.h index 6fb3184..4a375bd 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -93,20 +93,46 @@ enum kThruFilterMode { }; -/*! 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. */ +/*! The midimsg structure contains decoded data + of a MIDI message read from the serial port + with read() or thru(). + */ +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) */ + + /*! 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 */ + + /*! The first data byte. + \n Value goes from 0 to 127. + */ 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. */ + + /*! 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) */ + + /*! 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. */ + + /*! 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; + }; @@ -116,22 +142,24 @@ struct midimsg { See member descriptions to know how to use it, or check out the examples supplied with the library. */ -class MIDI_Class { - +class MIDI_Class +{ public: + + // ========================================================================= // Constructor and Destructor + MIDI_Class(); ~MIDI_Class(); - void begin(const byte inChannel = 1); + // ========================================================================= + // MIDI Output - - /* ####### OUTPUT COMPILATION BLOCK ####### */ -#if COMPILE_MIDI_OUT +#if COMPILE_MIDI_OUT // Start compilation block public: @@ -164,12 +192,14 @@ private: byte mRunningStatus_TX; #endif // USE_RUNNING_STATUS -#endif // COMPILE_MIDI_OUT +#endif // COMPILE_MIDI_OUT - /* ####### INPUT COMPILATION BLOCK ####### */ -#if COMPILE_MIDI_IN + // ========================================================================= + // MIDI Input + +#if COMPILE_MIDI_IN // Start compilation block public: @@ -199,18 +229,40 @@ public: */ 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 < 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; } +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; + + + // ========================================================================= + // Input Callbacks + #if USE_CALLBACKS +public: + 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)); @@ -232,27 +284,8 @@ public: 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; - -#if USE_CALLBACKS - void launchCallback(); void (*mNoteOffCallback)(byte channel, byte note, byte velocity); @@ -274,14 +307,15 @@ private: void (*mActiveSensingCallback)(void); void (*mSystemResetCallback)(void); -#endif // USE_CALLBACKS - +#endif // USE_CALLBACKS #endif // COMPILE_MIDI_IN - /* ####### THRU COMPILATION BLOCK ####### */ -#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + // ========================================================================= + // MIDI Soft Thru + +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) public: From 3edb9c15ea59fbaf442720f04c40ddc74e47a22d Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 16 Jun 2012 17:36:34 +0200 Subject: [PATCH 05/10] Refactoring to build on the new AVR core. --- src/MIDI.cpp | 20 +++++++++----------- src/MIDI.h | 23 ++++++++++++----------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index c66f4fe..bb4265e 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -9,7 +9,8 @@ */ #include "MIDI.h" -#include "Serial.h" +#include "core.h" +#include "hardware_Serial.h" #include @@ -21,7 +22,7 @@ MIDI_Class MIDI; MIDI_Class::MIDI_Class() { -#if USE_CALLBACKS +#if COMPILE_MIDI_IN && USE_CALLBACKS // Initialise callbacks to NULL pointer mNoteOffCallback = NULL; @@ -310,8 +311,7 @@ void MIDI_Class::sendPitchBend(double PitchValue, byte Channel) { - unsigned int pitchval = (PitchValue+1.f)*8192; - if (pitchval > 16383) pitchval = 16383; // overflow protection + int pitchval = PitchValue * MIDI_PITCHBEND_MAX; sendPitchBend(pitchval,Channel); } @@ -528,16 +528,16 @@ bool MIDI_Class::read(const byte inChannel) // Private method: MIDI parser bool MIDI_Class::parse(byte inChannel) { + const byte bytes_available = MIDI_SERIAL_PORT.available(); - const int bytes_available = MIDI_SERIAL_PORT.available(); - - if (bytes_available <= 0) { + 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 == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; + if (bytes_available == UART::bufferSize) + { + PRINT_DEBUG("MIDI Overflow"); MIDI_SERIAL_PORT.flush(); } else { @@ -1184,5 +1184,3 @@ void MIDI_Class::thru_filter(byte inChannel) #endif // Thru - - diff --git a/src/MIDI.h b/src/MIDI.h index 0c1327c..81cd69e 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -8,11 +8,10 @@ * license GPL Forty Seven Effects - 2011 */ -#ifndef LIB_MIDI_H_ -#define LIB_MIDI_H_ - -#include "Types.h" // Include all the types we need. +#ifndef _FSE_LIB_MIDI_H_ +#define _FSE_LIB_MIDI_H_ +#include "core_Types.h" // Include all the types we need. /* ############################################################### @@ -58,7 +57,7 @@ #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 128 // Maximum size is 65535 bytes. #define MIDI_PITCHBEND_MIN -8192 #define MIDI_PITCHBEND_MAX 8191 @@ -66,7 +65,8 @@ /*! Enumeration of MIDI types */ -enum kMIDIType { +enum kMIDIType +{ NoteOff = 0x80, ///< Note Off NoteOn = 0x90, ///< Note On AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch @@ -146,7 +146,7 @@ struct midimsg See member descriptions to know how to use it, or check out the examples supplied with the library. */ -class MIDI_Class +class MIDI_Class { public: @@ -252,9 +252,9 @@ private: byte mRunningStatus_RX; byte mInputChannel; - byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; + byte mPendingMessage[3]; // SysEx are dumped into mMessage directly. unsigned int mPendingMessageExpectedLenght; - unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. + unsigned int mPendingMessageIndex; // Extended to unsigned int for larger SysEx payloads. midimsg mMessage; @@ -339,7 +339,7 @@ private: void thru_filter(byte inChannel); bool mThruActivated; - kThruFilterMode mThruFilterMode; + kThruFilterMode mThruFilterMode; #endif // Thru @@ -347,4 +347,5 @@ private: extern MIDI_Class MIDI; -#endif // LIB_MIDI_H_ + +#endif // _FSE_LIB_MIDI_H_ From e4c8205dc397cdc60ac7f868b74dc7e61ae3dffb Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sun, 17 Jun 2012 22:36:51 +0200 Subject: [PATCH 07/10] Don't check for overflow (the serial class should do it). --- src/MIDI.cpp | 449 +++++++++++++++++++++++++-------------------------- 1 file changed, 220 insertions(+), 229 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index bb4265e..c233e7d 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -534,31 +534,226 @@ bool MIDI_Class::parse(byte inChannel) // No data available. return false; - // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. - if (bytes_available == UART::bufferSize) - { - PRINT_DEBUG("MIDI Overflow"); - MIDI_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 = MIDI_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) { // Start a new pending message + mPendingMessage[0] = extracted; - /* 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. - */ + // 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; + } - const byte extracted = MIDI_SERIAL_PORT.read(); + 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; + } - if (mPendingMessageIndex == 0) { // Start a new pending message - mPendingMessage[0] = extracted; + // 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) { - // Check for running status first - switch (getTypeFromStatusByte(mRunningStatus_RX)) { - // Only these types allow Running Status: + // 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: @@ -566,83 +761,18 @@ bool MIDI_Class::parse(byte inChannel) 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. - + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; 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; } - + return true; + } + else { // Then update the index of the pending message. mPendingMessageIndex++; @@ -656,145 +786,6 @@ bool MIDI_Class::parse(byte 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++; - -#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 - - } - - } } From bdd7d15b8abd03c5ebf99f5ed55f96d88524cb63 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 20 Jun 2012 18:40:57 +0200 Subject: [PATCH 08/10] Added control change enumeration. --- src/MIDI.h | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/MIDI.h b/src/MIDI.h index 81cd69e..1a51c57 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -97,6 +97,74 @@ enum kThruFilterMode { }; +enum eMIDICCNumber +{ + // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- + BankSelect = 0, + ModulationWheel = 1, + BreathController = 2, + // CC3 undefined + FootController = 4, + PortamentoTime = 5, + DataEntry = 6, + ChannelVolume = 7, + Balance = 8, + // CC9 undefined + Pan = 10, + ExpressionController = 11, + EffectControl1 = 12, + EffectControl2 = 13, + // CC14 undefined + // CC15 undefined + GeneralPurposeController1 = 16, + GeneralPurposeController2 = 17, + GeneralPurposeController3 = 18, + GeneralPurposeController4 = 19, + + // Switches ---------------------------------------------------------------- + Sustain = 64, + Portamento = 65, + Sostenuto = 66, + SoftPedal = 67, + Legato = 68, + Hold2 = 69, + + // Low resolution continuous controllers ----------------------------------- + SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off + SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off + SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off + SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off + SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off + SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off + SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off + SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off + SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off + SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off + GeneralPurposeController5 = 80, + GeneralPurposeController6 = 81, + GeneralPurposeController7 = 82, + GeneralPurposeController8 = 83, + PortamentoControl = 84, + // CC85 to CC90 undefined + Effects1 = 91, ///< Reverb send level + Effects2 = 92, ///< Tremolo depth + Effects3 = 93, ///< Chorus send level + Effects4 = 94, ///< Celeste depth + Effects5 = 95, ///< Phaser depth + + // Channel Mode messages --------------------------------------------------- + AllSoundOff = 120, + ResetAllControllers = 121, + LocalControl = 122, + AllNotesOff = 123, + OmniModeOff = 124, + OmniModeOn = 125, + MonoModeOn = 126, + PolyModeOn = 127 +}; + + + /*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). From 0422aa23dfc14b5ebafce58b90aa492e54f7f97f Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 3 Jul 2012 08:01:50 +0200 Subject: [PATCH 10/10] Coding style. --- src/MIDI.cpp | 361 +++++++++++++++++++++------------------------------ 1 file changed, 150 insertions(+), 211 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index c233e7d..f919b42 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -21,7 +21,6 @@ MIDI_Class MIDI; /*! \brief Default constructor for MIDI_Class. */ MIDI_Class::MIDI_Class() { - #if COMPILE_MIDI_IN && USE_CALLBACKS // Initialise callbacks to NULL pointer @@ -45,7 +44,6 @@ MIDI_Class::MIDI_Class() mSystemResetCallback = NULL; #endif - } @@ -67,20 +65,14 @@ MIDI_Class::~MIDI_Class() */ void MIDI_Class::begin(const byte inChannel) { - // Initialise the Serial port MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); - -#if COMPILE_MIDI_OUT - -#if USE_RUNNING_STATUS +#if COMPILE_MIDI_OUT && USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; - -#endif // USE_RUNNING_STATUS - -#endif // COMPILE_MIDI_OUT + +#endif // COMPILE_MIDI_OUT && USE_RUNNING_STATUS #if COMPILE_MIDI_IN @@ -119,9 +111,7 @@ void MIDI_Class::begin(const byte inChannel) const byte MIDI_Class::genstatus(const kMIDIType inType, const byte inChannel) const { - return ((byte)inType | ((inChannel-1) & 0x0F)); - } @@ -141,9 +131,11 @@ void MIDI_Class::send(kMIDIType type, byte data2, byte channel) { - // Then test if channel is valid - if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { + if (channel >= MIDI_CHANNEL_OFF || + channel == MIDI_CHANNEL_OMNI || + type < NoteOff) + { #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; @@ -152,9 +144,8 @@ void MIDI_Class::send(kMIDIType type, return; // Don't send anything } - if (type <= PitchBend) { - // Channel messages - + if (type <= PitchBend) // Channel messages + { // Protection: remove MSBs on data data1 &= 0x7F; data2 &= 0x7F; @@ -163,7 +154,8 @@ void MIDI_Class::send(kMIDIType type, #if USE_RUNNING_STATUS // Check Running Status - if (mRunningStatus_TX != statusbyte) { + if (mRunningStatus_TX != statusbyte) + { // New message, memorise and send header mRunningStatus_TX = statusbyte; MIDI_SERIAL_PORT.write(mRunningStatus_TX); @@ -175,16 +167,13 @@ void MIDI_Class::send(kMIDIType type, // Then send data MIDI_SERIAL_PORT.write(data1); - if (type != ProgramChange && type != AfterTouchChannel) { + if (type != ProgramChange && type != AfterTouchChannel) MIDI_SERIAL_PORT.write(data2); - } + return; } - if (type >= TuneRequest && type <= SystemReset) { - // System Real-time and 1 byte. - sendRealTime(type); - } - + if (type >= TuneRequest && type <= SystemReset) + sendRealTime(type); // System Real-time and 1 byte. } @@ -201,17 +190,18 @@ void MIDI_Class::sendNoteOn(byte NoteNumber, byte Velocity, byte Channel) { - send(NoteOn,NoteNumber,Velocity,Channel); - } -/*! \brief Send a Note Off message (a real Note Off, not a Note On with null velocity) +/*! \brief Send a Note Off message \param NoteNumber Pitch value in the MIDI format (0 to 127). \param Velocity Release velocity (0 to 127). \param Channel The channel on which the message will be sent (1 to 16). + Note: you can send NoteOn with zero velocity to make a NoteOff, this is based + on the Running Status principle, to avoid sending status messages and thus + sending only NoteOn data. This method will always send a real NoteOff message. Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html */ @@ -219,9 +209,7 @@ void MIDI_Class::sendNoteOff(byte NoteNumber, byte Velocity, byte Channel) { - - send(NoteOff,NoteNumber,Velocity,Channel); - + send(NoteOff,NoteNumber,Velocity,Channel); } @@ -232,9 +220,7 @@ void MIDI_Class::sendNoteOff(byte NoteNumber, void MIDI_Class::sendProgramChange(byte ProgramNumber, byte Channel) { - send(ProgramChange,ProgramNumber,0,Channel); - } @@ -250,13 +236,11 @@ void MIDI_Class::sendControlChange(byte ControlNumber, byte ControlValue, byte Channel) { - send(ControlChange,ControlNumber,ControlValue,Channel); - } -/*! \brief Send a Polyphonic AfterTouch message (applies to only one specified note) +/*! \brief Send a Polyphonic AfterTouch message (applies to a 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). @@ -265,9 +249,7 @@ void MIDI_Class::sendPolyPressure(byte NoteNumber, byte Pressure, byte Channel) { - send(AfterTouchPoly,NoteNumber,Pressure,Channel); - } @@ -278,9 +260,7 @@ void MIDI_Class::sendPolyPressure(byte NoteNumber, void MIDI_Class::sendAfterTouch(byte Pressure, byte Channel) { - send(AfterTouchChannel,Pressure,0,Channel); - } @@ -293,11 +273,8 @@ void MIDI_Class::sendAfterTouch(byte Pressure, void MIDI_Class::sendPitchBend(int PitchValue, byte Channel) { - - unsigned int bend = PitchValue - MIDI_PITCHBEND_MIN; - + const unsigned int bend = PitchValue - MIDI_PITCHBEND_MIN; send(PitchBend,(bend & 0x7F),(bend >> 7) & 0x7F,Channel); - } @@ -310,10 +287,8 @@ void MIDI_Class::sendPitchBend(int PitchValue, void MIDI_Class::sendPitchBend(double PitchValue, byte Channel) { - - int pitchval = PitchValue * MIDI_PITCHBEND_MAX; + const int pitchval = PitchValue * MIDI_PITCHBEND_MAX; sendPitchBend(pitchval,Channel); - } @@ -330,34 +305,24 @@ void MIDI_Class::sendSysEx(int length, const byte *const array, bool ArrayContainsBoundaries) { - - if (ArrayContainsBoundaries == false) { - + if (ArrayContainsBoundaries == false) + { MIDI_SERIAL_PORT.write(0xF0); - for (int i=0;i> 7) & 0x7F); @@ -420,21 +379,18 @@ void MIDI_Class::sendSongPosition(unsigned int Beats) #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif - } /*! \brief Send a Song Select message */ void MIDI_Class::sendSongSelect(byte SongNumber) { - MIDI_SERIAL_PORT.write((byte)SongSelect); MIDI_SERIAL_PORT.write(SongNumber & 0x7F); #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif - } @@ -447,7 +403,8 @@ void MIDI_Class::sendSongSelect(byte SongNumber) */ void MIDI_Class::sendRealTime(kMIDIType Type) { - switch (Type) { + switch (Type) + { case TuneRequest: // Not really real-time, but one byte anyway. case Clock: case Start: @@ -462,12 +419,12 @@ void MIDI_Class::sendRealTime(kMIDIType Type) 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. + // 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; #endif - } #endif // COMPILE_MIDI_OUT @@ -489,9 +446,7 @@ void MIDI_Class::sendRealTime(kMIDIType Type) */ bool MIDI_Class::read() { - return read(mInputChannel); - } @@ -500,12 +455,13 @@ bool MIDI_Class::read() */ bool MIDI_Class::read(const byte inChannel) { + if (inChannel >= MIDI_CHANNEL_OFF) + return false; // MIDI Input disabled. - if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. - - if (parse(inChannel)) { - - if (input_filter(inChannel)) { + if (parse(inChannel)) + { + if (input_filter(inChannel)) + { #if (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) thru_filter(inChannel); @@ -514,14 +470,11 @@ bool MIDI_Class::read(const byte inChannel) #if USE_CALLBACKS launchCallback(); #endif - return true; } - } return false; - } @@ -538,19 +491,23 @@ bool MIDI_Class::parse(byte inChannel) /* 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. + - 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 + if (mPendingMessageIndex == 0) + { + // Start a new pending message mPendingMessage[0] = extracted; // Check for running status first - switch (getTypeFromStatusByte(mRunningStatus_RX)) { + switch (getTypeFromStatusByte(mRunningStatus_RX)) + { // Only these types allow Running Status: case NoteOff: case NoteOn: @@ -560,13 +517,16 @@ bool MIDI_Class::parse(byte inChannel) case AfterTouchChannel: case PitchBend: - // If the status byte is not received, prepend it to the pending message - if (extracted < 0x80) { + // 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. + // 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; @@ -577,8 +537,8 @@ bool MIDI_Class::parse(byte inChannel) } - switch (getTypeFromStatusByte(mPendingMessage[0])) { - + switch (getTypeFromStatusByte(mPendingMessage[0])) + { // 1 byte messages case Start: case Continue: @@ -624,7 +584,9 @@ bool MIDI_Class::parse(byte inChannel) break; case SystemExclusive: - mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + // The message can be any lenght + // between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; mRunningStatus_RX = InvalidType; break; @@ -649,14 +611,15 @@ bool MIDI_Class::parse(byte inChannel) #endif } - else { - + else + { // First, test if this is a status byte - if (extracted >= 0x80) { - + 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) { + switch (extracted) + { case Clock: case Start: case Continue: @@ -664,15 +627,12 @@ bool MIDI_Class::parse(byte inChannel) 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. - */ + // 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; @@ -685,27 +645,25 @@ bool MIDI_Class::parse(byte inChannel) // End of Exclusive case 0xF7: - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - + 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 { + else + { // Well well well.. error. reset_input_attributes(); return false; @@ -715,36 +673,34 @@ bool MIDI_Class::parse(byte inChannel) 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)) { - + 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) { + // 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 + // 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; + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; // Reset local variables mPendingMessageIndex = 0; @@ -753,7 +709,8 @@ bool MIDI_Class::parse(byte inChannel) mMessage.valid = true; // Activate running status (if enabled for the received type) - switch (mMessage.type) { + switch (mMessage.type) + { case NoteOff: case NoteOn: case AfterTouchPoly: @@ -772,7 +729,8 @@ bool MIDI_Class::parse(byte inChannel) } return true; } - else { + else + { // Then update the index of the pending message. mPendingMessageIndex++; @@ -784,9 +742,7 @@ bool MIDI_Class::parse(byte inChannel) // to parse the rest of the message. return parse(inChannel); #endif - } - } // What are our chances to fall here? @@ -797,45 +753,41 @@ bool MIDI_Class::parse(byte inChannel) // 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) - - // This method handles recognition of channel (to know if the message is destinated to the Arduino) - - - if (mMessage.type == InvalidType) return false; - + if (mMessage.type == InvalidType) + return false; // First, check if the received message is Channel - if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { - + 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)) { + if ((mMessage.channel == mInputChannel) || + (mInputChannel == MIDI_CHANNEL_OMNI)) + { return true; - } - else { + else + { // We don't listen to this channel return false; } - } - else { - + 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; - } @@ -846,9 +798,7 @@ void MIDI_Class::reset_input_attributes() */ kMIDIType MIDI_Class::getType() const { - return mMessage.type; - } @@ -859,27 +809,21 @@ kMIDIType MIDI_Class::getType() const */ byte MIDI_Class::getChannel() const { - return mMessage.channel; - } /*! \brief Get the first data byte of the last received message. */ byte MIDI_Class::getData1() const { - return mMessage.data1; - } /*! \brief Get the second data byte of the last received message. */ byte MIDI_Class::getData2() const { - return mMessage.data2; - } @@ -889,9 +833,7 @@ byte MIDI_Class::getData2() const */ const byte * MIDI_Class::getSysExArray() const { - return mMessage.sysex_array; - } /*! \brief Get the lenght of the System Exclusive array. @@ -901,20 +843,15 @@ 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; - + 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 MIDI_Class::check() const { - return mMessage.valid; - } @@ -926,9 +863,7 @@ bool MIDI_Class::check() const */ void MIDI_Class::setInputChannel(const byte Channel) { - mInputChannel = Channel; - } @@ -962,8 +897,8 @@ void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) */ void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) { - - switch (Type) { + switch (Type) + { case NoteOff: mNoteOffCallback = NULL; break; case NoteOn: mNoteOnCallback = NULL; break; case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; @@ -985,17 +920,15 @@ void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) 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) { + 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; @@ -1027,7 +960,6 @@ void MIDI_Class::launchCallback() default: break; } - } @@ -1050,31 +982,27 @@ void MIDI_Class::launchCallback() */ void MIDI_Class::setThruFilterMode(kThruFilterMode inThruFilterMode) { - mThruFilterMode = inThruFilterMode; - if (mThruFilterMode != Off) mThruActivated = true; - else mThruActivated = false; - + 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; - } /*! \brief Setter method: turn message mirroring off. */ void MIDI_Class::turnThruOff() { - mThruActivated = false; mThruFilterMode = Off; - } @@ -1093,45 +1021,60 @@ void MIDI_Class::thru_filter(byte inChannel) */ // If the feature is disabled, don't do anything. - if (!mThruActivated || (mThruFilterMode == Off)) return; + 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)); + 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) { + switch (mThruFilterMode) + { case Full: - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + 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); + 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); + 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. + // Technically it's impossible to get there because + // the case was already tested earlier. break; default: break; } - } - else { - + else + { // Send the message to the output - switch (mMessage.type) { + switch (mMessage.type) + { // Real Time and 1 byte case Clock: case Start: @@ -1166,12 +1109,8 @@ void MIDI_Class::thru_filter(byte inChannel) break; default: break; - } - } - } - #endif // Thru