From 5c5069dcac5e88796578ee1d629c70ae6281656e Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 21:45:40 +0200 Subject: [PATCH 1/6] Added Arduino examples. --- res/Examples/MIDI_Basic_IO/MIDI_Basic_IO.ino | 24 ++++++ res/Examples/MIDI_Bench/MIDI_Bench.ino | 75 +++++++++++++++++++ .../MIDI_Callbacks/MIDI_Callbacks.ino | 36 +++++++++ res/Examples/MIDI_Input/MIDI_Input.ino | 43 +++++++++++ 4 files changed, 178 insertions(+) create mode 100644 res/Examples/MIDI_Basic_IO/MIDI_Basic_IO.ino create mode 100644 res/Examples/MIDI_Bench/MIDI_Bench.ino create mode 100644 res/Examples/MIDI_Callbacks/MIDI_Callbacks.ino create mode 100644 res/Examples/MIDI_Input/MIDI_Input.ino diff --git a/res/Examples/MIDI_Basic_IO/MIDI_Basic_IO.ino b/res/Examples/MIDI_Basic_IO/MIDI_Basic_IO.ino new file mode 100644 index 0000000..161b67f --- /dev/null +++ b/res/Examples/MIDI_Basic_IO/MIDI_Basic_IO.ino @@ -0,0 +1,24 @@ +#include +/* + Basic I/O MIDI tutorial + by Franky + 28/07/2009 +*/ + +#define LED 13 // LED pin on Arduino board + +void setup() { + pinMode(LED, OUTPUT); + MIDI.begin(4); // Launch MIDI with default options + // input channel is set to 4 +} + +void loop() { + if (MIDI.read()) { + digitalWrite(LED,HIGH); // Blink the LED + MIDI.sendNoteOn(42,127,1); // Send a Note (pitch 42, velo 127 on channel 1) + delay(1000); // Wait for a second + MIDI.sendNoteOff(42,0,1); // Stop the note + digitalWrite(LED,LOW); + } +} diff --git a/res/Examples/MIDI_Bench/MIDI_Bench.ino b/res/Examples/MIDI_Bench/MIDI_Bench.ino new file mode 100644 index 0000000..301b395 --- /dev/null +++ b/res/Examples/MIDI_Bench/MIDI_Bench.ino @@ -0,0 +1,75 @@ +#include + +unsigned long gTime_start = 0; +unsigned long gTime_stop = 0; +unsigned gCounter = 0; +unsigned long gTime_sum = 0; +unsigned long gTime_min = -1; +unsigned long gTime_max = 0; + + +void handleNoteOn(byte inChannel,byte inNote,byte inVelocity) +{ + + gTime_stop = micros(); + + const unsigned long diff = gTime_stop - gTime_start; + gTime_sum += diff; + + if (diff > gTime_max) gTime_max = diff; + if (diff < gTime_min) gTime_min = diff; + + gCounter++; + + if (gCounter >= 100) { + + const unsigned long average = gTime_sum / (float)gCounter; + + Serial.println("Time to receive NoteOn: "); + + Serial.print("Average: "); + Serial.print(average); + Serial.println(" microsecs"); + + Serial.print("Min: "); + Serial.print(gTime_min); + Serial.println(" microsecs"); + + Serial.print("Max: "); + Serial.print(gTime_max); + Serial.println(" microsecs"); + + gCounter = 0; + gTime_sum = 0; + gTime_max = 0; + gTime_min = -1; + + MIDI.turnThruOff(); + + } + +} + + +void setup() +{ + + MIDI.begin(); + + Serial.begin(38400); + Serial.print("MCU Ready"); + + MIDI.sendNoteOn(69,127,1); + +} + + +void loop() +{ + + gTime_start = micros(); + + MIDI.read(); + +} + diff --git a/res/Examples/MIDI_Callbacks/MIDI_Callbacks.ino b/res/Examples/MIDI_Callbacks/MIDI_Callbacks.ino new file mode 100644 index 0000000..36d980e --- /dev/null +++ b/res/Examples/MIDI_Callbacks/MIDI_Callbacks.ino @@ -0,0 +1,36 @@ +#include + + +// This function will be automatically called when a NoteOn is received. +// It must be a void-returning function with the correct parameters, +// see documentation here: +// http://arduinomidilib.sourceforge.net/class_m_i_d_i___class.html + +void HandleNoteOn(byte channel, byte pitch, byte velocity) { + + // Do whatever you want when you receive a Note On. + + if (velocity == 0) { + // This acts like a NoteOff. + } + + // Try to keep your callbacks short (no delays ect) as the contrary would slow down the loop() + // and have a bad impact on real-time performance. +} + +void setup() { + // Initiate MIDI communications, listen to all channels + MIDI.begin(MIDI_CHANNEL_OMNI); + + // Connect the HandleNoteOn function to the library, so it is called upon reception of a NoteOn. + MIDI.setHandleNoteOn(HandleNoteOn); // Put only the name of the function + +} + + +void loop() { + // Call MIDI.read the fastest you can for real-time performance. + MIDI.read(); + + // There is no need to check if there are messages incoming if they are bound to a Callback function. +} diff --git a/res/Examples/MIDI_Input/MIDI_Input.ino b/res/Examples/MIDI_Input/MIDI_Input.ino new file mode 100644 index 0000000..5ecb07d --- /dev/null +++ b/res/Examples/MIDI_Input/MIDI_Input.ino @@ -0,0 +1,43 @@ +#include +/* + MIDI Input tutorial + by Franky + 28/07/2009 + + NOTE: for easier MIDI input reading, + take a look a the Callbacks example. + +*/ + +#define LED 13 // LED pin on Arduino board + +void BlinkLed(byte num) { // Basic blink function + for (byte i=0;i Date: Tue, 22 May 2012 21:46:42 +0200 Subject: [PATCH 2/6] Added Arduino sources. --- src/MIDI.cpp | 1175 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/MIDI.h | 319 ++++++++++++++ 2 files changed, 1494 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..20bdce1 --- /dev/null +++ b/src/MIDI.cpp @@ -0,0 +1,1175 @@ +/*! + * @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 +#include "Arduino.h" // If using an old (pre-1.0) version of Arduino, use WConstants.h instead of Arduino.h +#include "HardwareSerial.h" + + +#if USE_SOFTWARE_SERIAL + +// Note: Make sure the following relative path is correct. +#include "../SoftwareSerial/SoftwareSerial.h" +SoftwareSerial softSerialClass(SOFTSERIAL_RX_PIN,SOFTSERIAL_TX_PIN); + +#undef USE_SERIAL_PORT +#define USE_SERIAL_PORT softSerialClass + +#endif // USE_SOFTWARE_SERIAL + + + +/*! \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 == 128) { + USE_SERIAL_PORT.flush(); + } + else { + + /* Parsing algorithm: + Get a byte from the serial buffer. + * If there is no pending message to be recomposed, start a new one. + - Find type and channel (if pertinent) + - Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty. + * Else, add the extracted byte to the pending message, and check validity. When the message is done, store it. + */ + + + const byte extracted = USE_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) { // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + switch (getTypeFromStatusByte(mRunningStatus_RX)) { + // Only these types allow Running Status: + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + + // If the status byte is not received, prepend it to the pending message + if (extracted < 0x80) { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, so the running status does not apply here. + // It will be updated upon completion of this message. + + break; + + default: + // No running status + break; + } + + + switch (getTypeFromStatusByte(mPendingMessage[0])) { + + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //reset_input_attributes(); + + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLenght = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLenght = 3; + break; + + case SystemExclusive: + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mRunningStatus_RX = InvalidType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + reset_input_attributes(); + return false; + break; + } + + // Then update the index of the pending message. + mPendingMessageIndex++; + +#if USE_1BYTE_PARSING + // Message is not complete. + return false; +#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..0ca4ec6 --- /dev/null +++ b/src/MIDI.h @@ -0,0 +1,319 @@ +/*! + * @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 + + +/* + ############################################################### + # # + # 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 Serial // Change the number (to Serial1 for example) if you want + // to use a different serial port for MIDI I/O. + +#define USE_SOFTWARE_SERIAL 1 // Set to 1 to use SoftwareSerial instead of native serial ports. +#define SOFTSERIAL_RX_PIN 1 // This pin number will be used for MIDI Input +#define SOFTSERIAL_TX_PIN 2 // This pin number will be used for MIDI Output. + + +#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values + // of the same type and channel. + // Set to 0 if you have troubles with controlling you hardware. + + +#define USE_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. + +/*! Type definition for practical use (because "unsigned char" is a bit long to write.. )*/ +typedef uint8_t byte; +typedef uint16_t word; + +/*! Enumeration of MIDI types */ +enum kMIDIType { + NoteOff = 0x80, ///< Note Off + NoteOn = 0x90, ///< Note On + AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch + ControlChange = 0xB0, ///< Control Change / Channel Mode + ProgramChange = 0xC0, ///< Program Change + AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch + PitchBend = 0xE0, ///< Pitch Bend + SystemExclusive = 0xF0, ///< System Exclusive + TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame + SongPosition = 0xF2, ///< System Common - Song Position Pointer + SongSelect = 0xF3, ///< System Common - Song Select + TuneRequest = 0xF6, ///< System Common - Tune Request + Clock = 0xF8, ///< System Real Time - Timing Clock + Start = 0xFA, ///< System Real Time - Start + Continue = 0xFB, ///< System Real Time - Continue + Stop = 0xFC, ///< System Real Time - Stop + ActiveSensing = 0xFE, ///< System Real Time - Active Sensing + SystemReset = 0xFF, ///< System Real Time - System Reset + InvalidType = 0x00 ///< For notifying errors +}; + +/*! 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 21b1e76e958eb597adc77167bbfc1671ebdfe2a9 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 22:00:50 +0200 Subject: [PATCH 3/6] Cosmetics. --- src/MIDI.cpp | 1570 +++++++++++++++++++++++++------------------------- src/MIDI.h | 466 +++++++-------- 2 files changed, 1018 insertions(+), 1018 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 20bdce1..335748f 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -1,16 +1,16 @@ /*! - * @file MIDI.cpp - * Project MIDI Library - * @brief MIDI Library for the Arduino - * @version 3.2 - * @author Francois Best - * @date 24/02/11 - * license GPL Forty Seven Effects - 2011 + * @file MIDI.cpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @version 3.2 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 */ #include "MIDI.h" #include -#include "Arduino.h" // If using an old (pre-1.0) version of Arduino, use WConstants.h instead of Arduino.h +#include "Arduino.h" // If using an old (pre-1.0) version of Arduino, use WConstants.h instead of Arduino.h #include "HardwareSerial.h" @@ -34,31 +34,31 @@ MIDI_Class MIDI; /*! \brief Default constructor for MIDI_Class. */ MIDI_Class::MIDI_Class() { - + #if USE_CALLBACKS - - // Initialise callbacks to NULL pointer - mNoteOffCallback = NULL; - mNoteOnCallback = NULL; - mAfterTouchPolyCallback = NULL; - mControlChangeCallback = NULL; - mProgramChangeCallback = NULL; - mAfterTouchChannelCallback = NULL; - mPitchBendCallback = NULL; - mSystemExclusiveCallback = NULL; - mTimeCodeQuarterFrameCallback = NULL; - mSongPositionCallback = NULL; - mSongSelectCallback = NULL; - mTuneRequestCallback = NULL; - mClockCallback = NULL; - mStartCallback = NULL; - mContinueCallback = NULL; - mStopCallback = NULL; - mActiveSensingCallback = NULL; - mSystemResetCallback = NULL; - + + // Initialise callbacks to NULL pointer + mNoteOffCallback = NULL; + mNoteOnCallback = NULL; + mAfterTouchPolyCallback = NULL; + mControlChangeCallback = NULL; + mProgramChangeCallback = NULL; + mAfterTouchChannelCallback = NULL; + mPitchBendCallback = NULL; + mSystemExclusiveCallback = NULL; + mTimeCodeQuarterFrameCallback = NULL; + mSongPositionCallback = NULL; + mSongSelectCallback = NULL; + mTuneRequestCallback = NULL; + mClockCallback = NULL; + mStartCallback = NULL; + mContinueCallback = NULL; + mStopCallback = NULL; + mActiveSensingCallback = NULL; + mSystemResetCallback = NULL; + #endif - + } @@ -68,7 +68,7 @@ MIDI_Class::MIDI_Class() */ MIDI_Class::~MIDI_Class() { - + } @@ -80,45 +80,45 @@ MIDI_Class::~MIDI_Class() */ void MIDI_Class::begin(const byte inChannel) { - - // Initialise the Serial port - USE_SERIAL_PORT.begin(MIDI_BAUDRATE); - - + + // Initialise the Serial port + USE_SERIAL_PORT.begin(MIDI_BAUDRATE); + + #if COMPILE_MIDI_OUT - + #if USE_RUNNING_STATUS - - mRunningStatus_TX = InvalidType; - + + mRunningStatus_TX = InvalidType; + #endif // USE_RUNNING_STATUS - + #endif // COMPILE_MIDI_OUT - - + + #if COMPILE_MIDI_IN - - mInputChannel = inChannel; - mRunningStatus_RX = InvalidType; - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = false; - mMessage.type = InvalidType; - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - + + mInputChannel = inChannel; + mRunningStatus_RX = InvalidType; + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = false; + mMessage.type = InvalidType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + #endif // COMPILE_MIDI_IN - - + + #if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru - - mThruFilterMode = Full; - mThruActivated = true; - + + mThruFilterMode = Full; + mThruActivated = true; + #endif // Thru - + } @@ -126,240 +126,240 @@ void MIDI_Class::begin(const byte inChannel) // Private method for generating a status byte from channel and type const byte MIDI_Class::genstatus(const kMIDIType inType, - const byte inChannel) const + const byte inChannel) const { - - return ((byte)inType | ((inChannel-1) & 0x0F)); - + + return ((byte)inType | ((inChannel-1) & 0x0F)); + } /*! \brief Generate and send a MIDI message from the values given. - \param type The message type (see type defines for reference) - \param data1 The first data byte. - \param data2 The second data byte (if the message contains only 1 data byte, set this one to 0). - \param channel The output channel on which the message will be sent (values from 1 to 16). Note: you cannot send to OMNI. + \param type The message type (see type defines for reference) + \param data1 The first data byte. + \param data2 The second data byte (if the message contains only 1 data byte, set this one to 0). + \param channel The output channel on which the message will be sent (values from 1 to 16). Note: you cannot send to OMNI. This is an internal method, use it only if you need to send raw data from your code, at your own risks. */ void MIDI_Class::send(kMIDIType type, - byte data1, - byte data2, - byte channel) + byte data1, + byte data2, + byte channel) { - - // Then test if channel is valid - if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { - -#if USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; + + // Then test if channel is valid + if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { + +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; #endif - - return; // Don't send anything - } - - if (type <= PitchBend) { - // Channel messages - - // Protection: remove MSBs on data - data1 &= 0x7F; - data2 &= 0x7F; - - byte statusbyte = genstatus(type,channel); - + + return; // Don't send anything + } + + if (type <= PitchBend) { + // Channel messages + + // Protection: remove MSBs on data + data1 &= 0x7F; + data2 &= 0x7F; + + byte statusbyte = genstatus(type,channel); + #if USE_RUNNING_STATUS - // Check Running Status - if (mRunningStatus_TX != statusbyte) { - // New message, memorise and send header - mRunningStatus_TX = statusbyte; - USE_SERIAL_PORT.write(mRunningStatus_TX); - } + // Check Running Status + if (mRunningStatus_TX != statusbyte) { + // New message, memorise and send header + mRunningStatus_TX = statusbyte; + USE_SERIAL_PORT.write(mRunningStatus_TX); + } #else - // Don't care about running status, send the Control byte. - USE_SERIAL_PORT.write(statusbyte); + // Don't care about running status, send the Control byte. + USE_SERIAL_PORT.write(statusbyte); #endif - - // Then send data - USE_SERIAL_PORT.write(data1); - if (type != ProgramChange && type != AfterTouchChannel) { - USE_SERIAL_PORT.write(data2); - } - return; - } - if (type >= TuneRequest && type <= SystemReset) { - // System Real-time and 1 byte. - sendRealTime(type); - } - + + // Then send data + USE_SERIAL_PORT.write(data1); + if (type != ProgramChange && type != AfterTouchChannel) { + USE_SERIAL_PORT.write(data2); + } + return; + } + if (type >= TuneRequest && type <= SystemReset) { + // System Real-time and 1 byte. + sendRealTime(type); + } + } /*! \brief Send a Note On message - \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n - \param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff. - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendNoteOn(byte NoteNumber, - byte Velocity, - byte Channel) + byte Velocity, + byte Channel) { - - send(NoteOn,NoteNumber,Velocity,Channel); - + + send(NoteOn,NoteNumber,Velocity,Channel); + } /*! \brief Send a Note Off message (a real Note Off, not a Note On with null velocity) - \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n - \param Velocity Release velocity (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Release velocity (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendNoteOff(byte NoteNumber, - byte Velocity, - byte Channel) + byte Velocity, + byte Channel) { - - send(NoteOff,NoteNumber,Velocity,Channel); - + + send(NoteOff,NoteNumber,Velocity,Channel); + } /*! \brief Send a Program Change message - \param ProgramNumber The Program to select (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param ProgramNumber The Program to select (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendProgramChange(byte ProgramNumber, - byte Channel) + byte Channel) { - - send(ProgramChange,ProgramNumber,0,Channel); - + + send(ProgramChange,ProgramNumber,0,Channel); + } /*! \brief Send a Control Change message - \param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums - \param ControlValue The value for the specified controller (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums + \param ControlValue The value for the specified controller (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendControlChange(byte ControlNumber, - byte ControlValue, - byte Channel) + byte ControlValue, + byte Channel) { - - send(ControlChange,ControlNumber,ControlValue,Channel); - + + send(ControlChange,ControlNumber,ControlValue,Channel); + } /*! \brief Send a Polyphonic AfterTouch message (applies to only one specified note) - \param NoteNumber The note to apply AfterTouch to (0 to 127). - \param Pressure The amount of AfterTouch to apply (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber The note to apply AfterTouch to (0 to 127). + \param Pressure The amount of AfterTouch to apply (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPolyPressure(byte NoteNumber, - byte Pressure, - byte Channel) + byte Pressure, + byte Channel) { - - send(AfterTouchPoly,NoteNumber,Pressure,Channel); - + + send(AfterTouchPoly,NoteNumber,Pressure,Channel); + } /*! \brief Send a MonoPhonic AfterTouch message (applies to all notes) - \param Pressure The amount of AfterTouch to apply to all notes. - \param Channel The channel on which the message will be sent (1 to 16). + \param Pressure The amount of AfterTouch to apply to all notes. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendAfterTouch(byte Pressure, - byte Channel) + byte Channel) { - - send(AfterTouchChannel,Pressure,0,Channel); - + + send(AfterTouchChannel,Pressure,0,Channel); + } /*! \brief Send a Pitch Bend message using a signed integer value. - \param PitchValue The amount of bend to send (in a signed integer format), between -8192 (maximum downwards bend) and 8191 (max upwards bend), center value is 0. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a signed integer format), between -8192 (maximum downwards bend) and 8191 (max upwards bend), center value is 0. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(int PitchValue, - byte Channel) + byte Channel) { - - unsigned int bend = PitchValue + 8192; - sendPitchBend(bend,Channel); - + + unsigned int bend = PitchValue + 8192; + sendPitchBend(bend,Channel); + } /*! \brief Send a Pitch Bend message using an unsigned integer value. - \param PitchValue The amount of bend to send (in a signed integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a signed integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(unsigned int PitchValue, - byte Channel) + byte Channel) { - - send(PitchBend,(PitchValue & 0x7F),(PitchValue >> 7) & 0x7F,Channel); - + + send(PitchBend,(PitchValue & 0x7F),(PitchValue >> 7) & 0x7F,Channel); + } /*! \brief Send a Pitch Bend message using a floating point value. - \param PitchValue The amount of bend to send (in a floating point format), between -1.0f (maximum downwards bend) and +1.0f (max upwards bend), center value is 0.0f. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a floating point format), between -1.0f (maximum downwards bend) and +1.0f (max upwards bend), center value is 0.0f. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(double PitchValue, - byte Channel) + byte Channel) { - - unsigned int pitchval = (PitchValue+1.f)*8192; - if (pitchval > 16383) pitchval = 16383; // overflow protection - sendPitchBend(pitchval,Channel); - + + unsigned int pitchval = (PitchValue+1.f)*8192; + if (pitchval > 16383) pitchval = 16383; // overflow protection + sendPitchBend(pitchval,Channel); + } /*! \brief Generate and send a System Exclusive frame. - \param length The size of the array to send - \param array The byte array containing the data to send + \param length The size of the array to send + \param array The byte array containing the data to send \param ArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes (start & stop SysEx) will NOT be sent (and therefore must be included in the array). default value is set to 'false' for compatibility with previous versions of the library. */ void MIDI_Class::sendSysEx(int length, - const byte *const array, - bool ArrayContainsBoundaries) + const byte *const array, + bool ArrayContainsBoundaries) { - - if (ArrayContainsBoundaries == false) { - - USE_SERIAL_PORT.write(0xF0); - - for (int i=0;i> 7) & 0x7F); - + + USE_SERIAL_PORT.write((byte)SongPosition); + USE_SERIAL_PORT.write(Beats & 0x7F); + USE_SERIAL_PORT.write((Beats >> 7) & 0x7F); + #if USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; + mRunningStatus_TX = InvalidType; #endif - + } /*! \brief Send a Song Select message */ void MIDI_Class::sendSongSelect(byte SongNumber) { - - USE_SERIAL_PORT.write((byte)SongSelect); - USE_SERIAL_PORT.write(SongNumber & 0x7F); - + + USE_SERIAL_PORT.write((byte)SongSelect); + USE_SERIAL_PORT.write(SongNumber & 0x7F); + #if USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; + mRunningStatus_TX = InvalidType; #endif - + } @@ -447,27 +447,27 @@ void MIDI_Class::sendSongSelect(byte SongNumber) */ void MIDI_Class::sendRealTime(kMIDIType Type) { - switch (Type) { - case TuneRequest: // Not really real-time, but one byte anyway. - case Clock: - case Start: - case Stop: - case Continue: - case ActiveSensing: - case SystemReset: - USE_SERIAL_PORT.write((byte)Type); - break; - default: - // Invalid Real Time marker - break; - } - - // Do not cancel Running Status for real-time messages as they can be interleaved within any message. - // Though, TuneRequest can be sent here, and as it is a System Common message, it must reset Running Status. + switch (Type) { + case TuneRequest: // Not really real-time, but one byte anyway. + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + USE_SERIAL_PORT.write((byte)Type); + break; + default: + // Invalid Real Time marker + break; + } + + // Do not cancel Running Status for real-time messages as they can be interleaved within any message. + // Though, TuneRequest can be sent here, and as it is a System Common message, it must reset Running Status. #if USE_RUNNING_STATUS - if (Type == TuneRequest) mRunningStatus_TX = InvalidType; + if (Type == TuneRequest) mRunningStatus_TX = InvalidType; #endif - + } #endif // COMPILE_MIDI_OUT @@ -484,360 +484,360 @@ void MIDI_Class::sendRealTime(kMIDIType Type) */ bool MIDI_Class::read() { - - return read(mInputChannel); - + + return read(mInputChannel); + } /*! \brief Reading/thru-ing method, the same as read() with a given input channel to read on. */ bool MIDI_Class::read(const byte inChannel) { - - if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. - - if (parse(inChannel)) { - - if (input_filter(inChannel)) { - + + if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. + + if (parse(inChannel)) { + + if (input_filter(inChannel)) { + #if (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) - thru_filter(inChannel); + thru_filter(inChannel); #endif - + #if USE_CALLBACKS - launchCallback(); + launchCallback(); #endif - - return true; - } - - } - - return false; - + + return true; + } + + } + + return false; + } // Private method: MIDI parser bool MIDI_Class::parse(byte inChannel) { - - const int bytes_available = USE_SERIAL_PORT.available(); - - if (bytes_available <= 0) { - // No data available. - return false; - } - - // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. - if (bytes_available == 128) { - USE_SERIAL_PORT.flush(); - } - else { - - /* Parsing algorithm: - Get a byte from the serial buffer. - * If there is no pending message to be recomposed, start a new one. - - Find type and channel (if pertinent) - - Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty. - * Else, add the extracted byte to the pending message, and check validity. When the message is done, store it. - */ - - - const byte extracted = USE_SERIAL_PORT.read(); - - if (mPendingMessageIndex == 0) { // Start a new pending message - mPendingMessage[0] = extracted; - - // Check for running status first - switch (getTypeFromStatusByte(mRunningStatus_RX)) { - // Only these types allow Running Status: - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - - // If the status byte is not received, prepend it to the pending message - if (extracted < 0x80) { - mPendingMessage[0] = mRunningStatus_RX; - mPendingMessage[1] = extracted; - mPendingMessageIndex = 1; - } - // Else: well, we received another status byte, so the running status does not apply here. - // It will be updated upon completion of this message. - - break; - - default: - // No running status - break; - } - - - switch (getTypeFromStatusByte(mPendingMessage[0])) { - - // 1 byte messages - case Start: - case Continue: - case Stop: - case Clock: - case ActiveSensing: - case SystemReset: - case TuneRequest: - // Handle the message type directly here. - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.valid = true; - - // \fix Running Status broken when receiving Clock messages. - // Do not reset all input attributes, Running Status must remain unchanged. - //reset_input_attributes(); - - // We still need to reset these - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - return true; - break; - - // 2 bytes messages - case ProgramChange: - case AfterTouchChannel: - case TimeCodeQuarterFrame: - case SongSelect: - mPendingMessageExpectedLenght = 2; - break; - - // 3 bytes messages - case NoteOn: - case NoteOff: - case ControlChange: - case PitchBend: - case AfterTouchPoly: - case SongPosition: - mPendingMessageExpectedLenght = 3; - break; - - case SystemExclusive: - mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes - mRunningStatus_RX = InvalidType; - break; - - case InvalidType: - default: - // This is obviously wrong. Let's get the hell out'a here. - reset_input_attributes(); - return false; - break; - } - - // Then update the index of the pending message. - mPendingMessageIndex++; - + + const int bytes_available = USE_SERIAL_PORT.available(); + + if (bytes_available <= 0) { + // No data available. + return false; + } + + // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. + if (bytes_available == 128) { + USE_SERIAL_PORT.flush(); + } + else { + + /* Parsing algorithm: + Get a byte from the serial buffer. + * If there is no pending message to be recomposed, start a new one. + - Find type and channel (if pertinent) + - Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty. + * Else, add the extracted byte to the pending message, and check validity. When the message is done, store it. + */ + + + const byte extracted = USE_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) { // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + switch (getTypeFromStatusByte(mRunningStatus_RX)) { + // Only these types allow Running Status: + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + + // If the status byte is not received, prepend it to the pending message + if (extracted < 0x80) { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, so the running status does not apply here. + // It will be updated upon completion of this message. + + break; + + default: + // No running status + break; + } + + + switch (getTypeFromStatusByte(mPendingMessage[0])) { + + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //reset_input_attributes(); + + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLenght = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLenght = 3; + break; + + case SystemExclusive: + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mRunningStatus_RX = InvalidType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + reset_input_attributes(); + return false; + break; + } + + // Then update the index of the pending message. + mPendingMessageIndex++; + #if USE_1BYTE_PARSING - // Message is not complete. - return false; + // Message is not complete. + return false; #else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); #endif - - } - else { - - // First, test if this is a status byte - if (extracted >= 0x80) { - - // Reception of status bytes in the middle of an uncompleted message - // are allowed only for interleaved Real Time message or EOX - switch (extracted) { - case Clock: - case Start: - case Continue: - case Stop: - case ActiveSensing: - case SystemReset: - - /* - This is tricky. Here we will have to extract the one-byte message, - pass it to the structure for being read outside the MIDI class, - and recompose the message it was interleaved into. - - Oh, and without killing the running status.. - - This is done by leaving the pending message as is, it will be completed on next calls. - */ - - mMessage.type = (kMIDIType)extracted; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.channel = 0; - mMessage.valid = true; - return true; - - break; - - // End of Exclusive - case 0xF7: - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - - // Store System Exclusive array in midimsg structure - for (byte i=0;i> 8; - - mMessage.channel = 0; - mMessage.valid = true; - - reset_input_attributes(); - - return true; - } - else { - // Well well well.. error. - reset_input_attributes(); - return false; - } - - break; - default: - break; - } - - - - } - - - // Add extracted data byte to pending message - mPendingMessage[mPendingMessageIndex] = extracted; - - - // Now we are going to check if we have reached the end of the message - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { - - // "FML" case: fall down here with an overflown SysEx.. - // This means we received the last possible data byte that can fit the buffer. - // If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - reset_input_attributes(); - return false; - } - - - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message - - mMessage.data1 = mPendingMessage[1]; - - // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; - else mMessage.data2 = 0; - - // Reset local variables - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = true; - - // Activate running status (if enabled for the received type) - switch (mMessage.type) { - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - // Running status enabled: store it from received message - mRunningStatus_RX = mPendingMessage[0]; - break; - - default: - // No running status - mRunningStatus_RX = InvalidType; - break; - } - return true; - } - else { - // Then update the index of the pending message. - mPendingMessageIndex++; - + + } + else { + + // First, test if this is a status byte + if (extracted >= 0x80) { + + // Reception of status bytes in the middle of an uncompleted message + // are allowed only for interleaved Real Time message or EOX + switch (extracted) { + case Clock: + case Start: + case Continue: + case Stop: + case ActiveSensing: + case SystemReset: + + /* + This is tricky. Here we will have to extract the one-byte message, + pass it to the structure for being read outside the MIDI class, + and recompose the message it was interleaved into. + + Oh, and without killing the running status.. + + This is done by leaving the pending message as is, it will be completed on next calls. + */ + + mMessage.type = (kMIDIType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xF7: + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + + // Store System Exclusive array in midimsg structure + for (byte i=0;i> 8; + + mMessage.channel = 0; + mMessage.valid = true; + + reset_input_attributes(); + + return true; + } + else { + // Well well well.. error. + reset_input_attributes(); + return false; + } + + break; + default: + break; + } + + + + } + + + // Add extracted data byte to pending message + mPendingMessage[mPendingMessageIndex] = extracted; + + + // Now we are going to check if we have reached the end of the message + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { + + // "FML" case: fall down here with an overflown SysEx.. + // This means we received the last possible data byte that can fit the buffer. + // If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + reset_input_attributes(); + return false; + } + + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; + else mMessage.data2 = 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) { + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; + break; + + default: + // No running status + mRunningStatus_RX = InvalidType; + break; + } + return true; + } + else { + // Then update the index of the pending message. + mPendingMessageIndex++; + #if USE_1BYTE_PARSING - // Message is not complete. - return false; + // Message is not complete. + return false; #else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); #endif - - } - - } - - } - - // What are our chances to fall here? - return false; + + } + + } + + } + + // What are our chances to fall here? + return false; } // Private method: check if the received message is on the listened channel bool MIDI_Class::input_filter(byte inChannel) { - - - // This method handles recognition of channel (to know if the message is destinated to the Arduino) - - - if (mMessage.type == InvalidType) return false; - - - // First, check if the received message is Channel - if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { - - // Then we need to know if we listen to it - if ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)) { - return true; - - } - else { - // We don't listen to this channel - return false; - } - - } - else { - - // System messages are always received - return true; - } - + + + // This method handles recognition of channel (to know if the message is destinated to the Arduino) + + + if (mMessage.type == InvalidType) return false; + + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { + + // Then we need to know if we listen to it + if ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)) { + return true; + + } + else { + // We don't listen to this channel + return false; + } + + } + else { + + // System messages are always received + return true; + } + } // Private method: reset input attributes void MIDI_Class::reset_input_attributes() { - - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - mRunningStatus_RX = InvalidType; - + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; + } @@ -848,9 +848,9 @@ void MIDI_Class::reset_input_attributes() */ kMIDIType MIDI_Class::getType() const { - - return mMessage.type; - + + return mMessage.type; + } @@ -860,27 +860,27 @@ kMIDIType MIDI_Class::getType() const */ byte MIDI_Class::getChannel() const { - - return mMessage.channel; - + + return mMessage.channel; + } /*! \brief Get the first data byte of the last received message. */ byte MIDI_Class::getData1() const { - - return mMessage.data1; - + + return mMessage.data1; + } /*! \brief Get the second data byte of the last received message. */ byte MIDI_Class::getData2() const { - - return mMessage.data2; - + + return mMessage.data2; + } @@ -890,9 +890,9 @@ byte MIDI_Class::getData2() const */ const byte * MIDI_Class::getSysExArray() const { - - return mMessage.sysex_array; - + + return mMessage.sysex_array; + } /*! \brief Get the lenght of the System Exclusive array. @@ -902,20 +902,20 @@ const byte * MIDI_Class::getSysExArray() const */ unsigned int MIDI_Class::getSysExArrayLength() const { - - unsigned int coded_size = ((unsigned int)(mMessage.data2) << 8) | mMessage.data1; - - return (coded_size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : coded_size; - + + unsigned int coded_size = ((unsigned int)(mMessage.data2) << 8) | mMessage.data1; + + return (coded_size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : coded_size; + } /*! \brief Check if a valid message is stored in the structure. */ bool MIDI_Class::check() const { - - return mMessage.valid; - + + return mMessage.valid; + } @@ -926,107 +926,107 @@ bool MIDI_Class::check() const */ void MIDI_Class::setInputChannel(const byte Channel) { - - mInputChannel = Channel; - + + mInputChannel = Channel; + } #if USE_CALLBACKS -void MIDI_Class::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } -void MIDI_Class::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } -void MIDI_Class::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } -void MIDI_Class::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } -void MIDI_Class::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } -void MIDI_Class::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } -void MIDI_Class::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } -void MIDI_Class::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } -void MIDI_Class::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } -void MIDI_Class::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } -void MIDI_Class::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } -void MIDI_Class::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } -void MIDI_Class::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } -void MIDI_Class::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } -void MIDI_Class::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } -void MIDI_Class::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } -void MIDI_Class::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } -void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } +void MIDI_Class::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } +void MIDI_Class::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } +void MIDI_Class::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } +void MIDI_Class::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } +void MIDI_Class::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } +void MIDI_Class::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } +void MIDI_Class::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } +void MIDI_Class::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } +void MIDI_Class::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } +void MIDI_Class::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } +void MIDI_Class::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } +void MIDI_Class::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } +void MIDI_Class::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } +void MIDI_Class::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +void MIDI_Class::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +void MIDI_Class::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +void MIDI_Class::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } /*! \brief Detach an external function from the given type. Use this method to cancel the effects of setHandle********. - \param Type The type of message to unbind. When a message of this type is received, no function will be called. + \param Type The type of message to unbind. When a message of this type is received, no function will be called. */ void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) { - - switch (Type) { - case NoteOff: mNoteOffCallback = NULL; break; - case NoteOn: mNoteOnCallback = NULL; break; - case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; - case ControlChange: mControlChangeCallback = NULL; break; - case ProgramChange: mProgramChangeCallback = NULL; break; - case AfterTouchChannel: mAfterTouchChannelCallback = NULL; break; - case PitchBend: mPitchBendCallback = NULL; break; - case SystemExclusive: mSystemExclusiveCallback = NULL; break; - case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = NULL; break; - case SongPosition: mSongPositionCallback = NULL; break; - case SongSelect: mSongSelectCallback = NULL; break; - case TuneRequest: mTuneRequestCallback = NULL; break; - case Clock: mClockCallback = NULL; break; - case Start: mStartCallback = NULL; break; - case Continue: mContinueCallback = NULL; break; - case Stop: mStopCallback = NULL; break; - case ActiveSensing: mActiveSensingCallback = NULL; break; - case SystemReset: mSystemResetCallback = NULL; break; - default: - break; - } - + + switch (Type) { + case NoteOff: mNoteOffCallback = NULL; break; + case NoteOn: mNoteOnCallback = NULL; break; + case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; + case ControlChange: mControlChangeCallback = NULL; break; + case ProgramChange: mProgramChangeCallback = NULL; break; + case AfterTouchChannel: mAfterTouchChannelCallback = NULL; break; + case PitchBend: mPitchBendCallback = NULL; break; + case SystemExclusive: mSystemExclusiveCallback = NULL; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = NULL; break; + case SongPosition: mSongPositionCallback = NULL; break; + case SongSelect: mSongSelectCallback = NULL; break; + case TuneRequest: mTuneRequestCallback = NULL; break; + case Clock: mClockCallback = NULL; break; + case Start: mStartCallback = NULL; break; + case Continue: mContinueCallback = NULL; break; + case Stop: mStopCallback = NULL; break; + case ActiveSensing: mActiveSensingCallback = NULL; break; + case SystemReset: mSystemResetCallback = NULL; break; + default: + break; + } + } // Private - launch callback function based on received type. void MIDI_Class::launchCallback() { - - // The order is mixed to allow frequent messages to trigger their callback faster. - - switch (mMessage.type) { - // Notes - case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - - // Real-time messages - case Clock: if (mClockCallback != NULL) mClockCallback(); break; - case Start: if (mStartCallback != NULL) mStartCallback(); break; - case Continue: if (mContinueCallback != NULL) mContinueCallback(); break; - case Stop: if (mStopCallback != NULL) mStopCallback(); break; - case ActiveSensing: if (mActiveSensingCallback != NULL) mActiveSensingCallback(); break; - - // Continuous controllers - case ControlChange: if (mControlChangeCallback != NULL) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) - 8192); break; // TODO: check this - case AfterTouchPoly: if (mAfterTouchPolyCallback != NULL) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case AfterTouchChannel: if (mAfterTouchChannelCallback != NULL) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; - - case ProgramChange: if (mProgramChangeCallback != NULL) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; - case SystemExclusive: if (mSystemExclusiveCallback != NULL) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; - - // Occasional messages - case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != NULL) mTimeCodeQuarterFrameCallback(mMessage.data1); break; - case SongPosition: if (mSongPositionCallback != NULL) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; - case SongSelect: if (mSongSelectCallback != NULL) mSongSelectCallback(mMessage.data1); break; - case TuneRequest: if (mTuneRequestCallback != NULL) mTuneRequestCallback(); break; - - case SystemReset: if (mSystemResetCallback != NULL) mSystemResetCallback(); break; - case InvalidType: - default: - break; - } - + + // The order is mixed to allow frequent messages to trigger their callback faster. + + switch (mMessage.type) { + // Notes + case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != NULL) mClockCallback(); break; + case Start: if (mStartCallback != NULL) mStartCallback(); break; + case Continue: if (mContinueCallback != NULL) mContinueCallback(); break; + case Stop: if (mStopCallback != NULL) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != NULL) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != NULL) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) - 8192); break; // TODO: check this + case AfterTouchPoly: if (mAfterTouchPolyCallback != NULL) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != NULL) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; + + case ProgramChange: if (mProgramChangeCallback != NULL) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != NULL) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; + + // Occasional messages + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != NULL) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != NULL) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; + case SongSelect: if (mSongSelectCallback != NULL) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != NULL) mTuneRequestCallback(); break; + + case SystemReset: if (mSystemResetCallback != NULL) mSystemResetCallback(); break; + case InvalidType: + default: + break; + } + } @@ -1047,126 +1047,126 @@ void MIDI_Class::launchCallback() */ void MIDI_Class::setThruFilterMode(kThruFilterMode inThruFilterMode) { - - mThruFilterMode = inThruFilterMode; - if (mThruFilterMode != Off) mThruActivated = true; - else mThruActivated = false; - + + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) mThruActivated = true; + else mThruActivated = false; + } /*! \brief Setter method: turn message mirroring on. */ void MIDI_Class::turnThruOn(kThruFilterMode inThruFilterMode) { - - mThruActivated = true; - mThruFilterMode = inThruFilterMode; - + + mThruActivated = true; + mThruFilterMode = inThruFilterMode; + } /*! \brief Setter method: turn message mirroring off. */ void MIDI_Class::turnThruOff() { - - mThruActivated = false; - mThruFilterMode = Off; - + + mThruActivated = false; + mThruFilterMode = Off; + } // This method is called upon reception of a message and takes care of Thru filtering and sending. void MIDI_Class::thru_filter(byte inChannel) { - - /* - This method handles Soft-Thru filtering. - - Soft-Thru filtering: - - All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off - - Channel messages are passed to the output whether their channel is matching the input channel and the filter setting - - */ - - // If the feature is disabled, don't do anything. - if (!mThruActivated || (mThruFilterMode == Off)) return; - - - // First, check if the received message is Channel - if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { - - const bool filter_condition = ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)); - - // Now let's pass it to the output - switch (mThruFilterMode) { - case Full: - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); - return; - break; - case SameChannel: - if (filter_condition) { - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); - return; - } - break; - case DifferentChannel: - if (!filter_condition) { - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); - return; - } - break; - case Off: - // Do nothing. - // Technically it's impossible to get there because the case was already tested earlier. - break; - default: - break; - } - - } - else { - - // Send the message to the output - switch (mMessage.type) { - // Real Time and 1 byte - case Clock: - case Start: - case Stop: - case Continue: - case ActiveSensing: - case SystemReset: - case TuneRequest: - sendRealTime(mMessage.type); - return; - break; - - case SystemExclusive: - // Send SysEx (0xF0 and 0xF7 are included in the buffer) - sendSysEx(mMessage.data1,mMessage.sysex_array,true); - return; - break; - - case SongSelect: - sendSongSelect(mMessage.data1); - return; - break; - - case SongPosition: - sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7)); - return; - break; - - case TimeCodeQuarterFrame: - sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); - return; - break; - default: - break; - - } - - } - + + /* + This method handles Soft-Thru filtering. + + Soft-Thru filtering: + - All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off + - Channel messages are passed to the output whether their channel is matching the input channel and the filter setting + + */ + + // If the feature is disabled, don't do anything. + if (!mThruActivated || (mThruFilterMode == Off)) return; + + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { + + const bool filter_condition = ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)); + + // Now let's pass it to the output + switch (mThruFilterMode) { + case Full: + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + break; + case SameChannel: + if (filter_condition) { + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + } + break; + case DifferentChannel: + if (!filter_condition) { + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + } + break; + case Off: + // Do nothing. + // Technically it's impossible to get there because the case was already tested earlier. + break; + default: + break; + } + + } + else { + + // Send the message to the output + switch (mMessage.type) { + // Real Time and 1 byte + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + case TuneRequest: + sendRealTime(mMessage.type); + return; + break; + + case SystemExclusive: + // Send SysEx (0xF0 and 0xF7 are included in the buffer) + sendSysEx(mMessage.data1,mMessage.sysex_array,true); + return; + break; + + case SongSelect: + sendSongSelect(mMessage.data1); + return; + break; + + case SongPosition: + sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7)); + return; + break; + + case TimeCodeQuarterFrame: + sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); + return; + break; + default: + break; + + } + + } + } diff --git a/src/MIDI.h b/src/MIDI.h index 0ca4ec6..993c1d6 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -1,11 +1,11 @@ /*! - * @file MIDI.h - * Project MIDI Library - * @brief MIDI Library for the Arduino - * Version 3.2 - * @author Francois Best - * @date 24/02/11 - * License GPL Forty Seven Effects - 2011 + * @file MIDI.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @version 3.2 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 */ #ifndef LIB_MIDI_H_ @@ -15,40 +15,40 @@ /* - ############################################################### - # # - # CONFIGURATION AREA # - # # - # Here are a few settings you can change to customize # - # the library for your own project. You can for example # - # choose to compile only parts of it so you gain flash # - # space and optimise the speed of your sketch. # - # # - ############################################################### + ############################################################### + # # + # CONFIGURATION AREA # + # # + # Here are a few settings you can change to customize # + # the library for your own project. You can for example # + # choose to compile only parts of it so you gain flash # + # space and optimise the speed of your sketch. # + # # + ############################################################### */ #define COMPILE_MIDI_IN 1 // Set this setting to 1 to use the MIDI input. #define COMPILE_MIDI_OUT 1 // Set this setting to 1 to use the MIDI output. #define COMPILE_MIDI_THRU 1 // Set this setting to 1 to use the MIDI Soft Thru feature - // Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. +// Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. #define USE_SERIAL_PORT Serial // Change the number (to Serial1 for example) if you want - // to use a different serial port for MIDI I/O. +// to use a different serial port for MIDI I/O. #define USE_SOFTWARE_SERIAL 1 // Set to 1 to use SoftwareSerial instead of native serial ports. #define SOFTSERIAL_RX_PIN 1 // This pin number will be used for MIDI Input #define SOFTSERIAL_TX_PIN 2 // This pin number will be used for MIDI Output. -#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values - // of the same type and channel. - // Set to 0 if you have troubles with controlling you hardware. +#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values +// of the same type and channel. +// Set to 0 if you have troubles with controlling you hardware. #define USE_CALLBACKS 1 // Set this to 1 if you want to use callback handlers (to bind your functions to the library). - // To use the callbacks, you need to have COMPILE_MIDI_IN set to 1 +// To use the callbacks, you need to have COMPILE_MIDI_IN set to 1 #define USE_1BYTE_PARSING 1 // Each call to MIDI.read will only parse one byte (might be faster). @@ -56,12 +56,12 @@ // END OF CONFIGURATION AREA // (do not modify anything under this line unless you know what you are doing) -#define MIDI_BAUDRATE 31250 +#define MIDI_BAUDRATE 31250 -#define MIDI_CHANNEL_OMNI 0 -#define MIDI_CHANNEL_OFF 17 // and over +#define MIDI_CHANNEL_OMNI 0 +#define MIDI_CHANNEL_OFF 17 // and over -#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. +#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. /*! Type definition for practical use (because "unsigned char" is a bit long to write.. )*/ typedef uint8_t byte; @@ -69,249 +69,249 @@ typedef uint16_t word; /*! Enumeration of MIDI types */ enum kMIDIType { - NoteOff = 0x80, ///< Note Off - NoteOn = 0x90, ///< Note On - AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch - ControlChange = 0xB0, ///< Control Change / Channel Mode - ProgramChange = 0xC0, ///< Program Change - AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch - PitchBend = 0xE0, ///< Pitch Bend - SystemExclusive = 0xF0, ///< System Exclusive - TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame - SongPosition = 0xF2, ///< System Common - Song Position Pointer - SongSelect = 0xF3, ///< System Common - Song Select - TuneRequest = 0xF6, ///< System Common - Tune Request - Clock = 0xF8, ///< System Real Time - Timing Clock - Start = 0xFA, ///< System Real Time - Start - Continue = 0xFB, ///< System Real Time - Continue - Stop = 0xFC, ///< System Real Time - Stop - ActiveSensing = 0xFE, ///< System Real Time - Active Sensing - SystemReset = 0xFF, ///< System Real Time - System Reset - InvalidType = 0x00 ///< For notifying errors + NoteOff = 0x80, ///< Note Off + NoteOn = 0x90, ///< Note On + AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch + ControlChange = 0xB0, ///< Control Change / Channel Mode + ProgramChange = 0xC0, ///< Program Change + AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch + PitchBend = 0xE0, ///< Pitch Bend + SystemExclusive = 0xF0, ///< System Exclusive + TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame + SongPosition = 0xF2, ///< System Common - Song Position Pointer + SongSelect = 0xF3, ///< System Common - Song Select + TuneRequest = 0xF6, ///< System Common - Tune Request + Clock = 0xF8, ///< System Real Time - Timing Clock + Start = 0xFA, ///< System Real Time - Start + Continue = 0xFB, ///< System Real Time - Continue + Stop = 0xFC, ///< System Real Time - Stop + ActiveSensing = 0xFE, ///< System Real Time - Active Sensing + SystemReset = 0xFF, ///< System Real Time - System Reset + InvalidType = 0x00 ///< For notifying errors }; /*! Enumeration of Thru filter modes */ enum kThruFilterMode { - Off = 0, ///< Thru disabled (nothing passes through). - Full = 1, ///< Fully enabled Thru (every incoming message is sent back). - SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. - DifferentChannel = 3 ///< All the messages but the ones on the Input Channel will be sent back. + Off = 0, ///< Thru disabled (nothing passes through). + Full = 1, ///< Fully enabled Thru (every incoming message is sent back). + SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. + DifferentChannel = 3 ///< All the messages but the ones on the Input Channel will be sent back. }; /*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). \n */ struct midimsg { - /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ - byte channel; - /*! The type of the message (see the define section for types reference) */ - kMIDIType type; - /*! The first data byte.\n Value goes from 0 to 127.\n */ - byte data1; - /*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */ - byte data2; - /*! System Exclusive dedicated byte array. \n Array length is stocked on 16 bits, in data1 (LSB) and data2 (MSB) */ - byte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; - /*! This boolean indicates if the message is valid or not. There is no channel consideration here, validity means the message respects the MIDI norm. */ - bool valid; + /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ + byte channel; + /*! The type of the message (see the define section for types reference) */ + kMIDIType type; + /*! The first data byte.\n Value goes from 0 to 127.\n */ + byte data1; + /*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */ + byte data2; + /*! System Exclusive dedicated byte array. \n Array length is stocked on 16 bits, in data1 (LSB) and data2 (MSB) */ + byte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; + /*! This boolean indicates if the message is valid or not. There is no channel consideration here, validity means the message respects the MIDI norm. */ + bool valid; }; /*! \brief The main class for MIDI handling.\n - See member descriptions to know how to use it, - or check out the examples supplied with the library. + See member descriptions to know how to use it, + or check out the examples supplied with the library. */ class MIDI_Class { - - + + public: - // Constructor and Destructor - MIDI_Class(); - ~MIDI_Class(); - - - void begin(const byte inChannel = 1); - - - - -/* ####### OUTPUT COMPILATION BLOCK ####### */ + // Constructor and Destructor + MIDI_Class(); + ~MIDI_Class(); + + + void begin(const byte inChannel = 1); + + + + + /* ####### OUTPUT COMPILATION BLOCK ####### */ #if COMPILE_MIDI_OUT - -public: - - void sendNoteOn(byte NoteNumber,byte Velocity,byte Channel); - void sendNoteOff(byte NoteNumber,byte Velocity,byte Channel); - void sendProgramChange(byte ProgramNumber,byte Channel); - void sendControlChange(byte ControlNumber, byte ControlValue,byte Channel); - void sendPitchBend(int PitchValue,byte Channel); - void sendPitchBend(unsigned int PitchValue,byte Channel); - void sendPitchBend(double PitchValue,byte Channel); - void sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel); - void sendAfterTouch(byte Pressure,byte Channel); - void sendSysEx(int length, const byte *const array,bool ArrayContainsBoundaries = false); - void sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble); - void sendTimeCodeQuarterFrame(byte data); - void sendSongPosition(unsigned int Beats); - void sendSongSelect(byte SongNumber); - void sendTuneRequest(); - void sendRealTime(kMIDIType Type); - - void send(kMIDIType type, byte param1, byte param2, byte channel); - + +public: + + void sendNoteOn(byte NoteNumber,byte Velocity,byte Channel); + void sendNoteOff(byte NoteNumber,byte Velocity,byte Channel); + void sendProgramChange(byte ProgramNumber,byte Channel); + void sendControlChange(byte ControlNumber, byte ControlValue,byte Channel); + void sendPitchBend(int PitchValue,byte Channel); + void sendPitchBend(unsigned int PitchValue,byte Channel); + void sendPitchBend(double PitchValue,byte Channel); + void sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel); + void sendAfterTouch(byte Pressure,byte Channel); + void sendSysEx(int length, const byte *const array,bool ArrayContainsBoundaries = false); + void sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble); + void sendTimeCodeQuarterFrame(byte data); + void sendSongPosition(unsigned int Beats); + void sendSongSelect(byte SongNumber); + void sendTuneRequest(); + void sendRealTime(kMIDIType Type); + + void send(kMIDIType type, byte param1, byte param2, byte channel); + private: - - const byte genstatus(const kMIDIType inType,const byte inChannel) const; - - - // Attributes + + const byte genstatus(const kMIDIType inType,const byte inChannel) const; + + + // Attributes #if USE_RUNNING_STATUS - byte mRunningStatus_TX; + byte mRunningStatus_TX; #endif // USE_RUNNING_STATUS - -#endif // COMPILE_MIDI_OUT - - - -/* ####### INPUT COMPILATION BLOCK ####### */ -#if COMPILE_MIDI_IN - + +#endif // COMPILE_MIDI_OUT + + + + /* ####### INPUT COMPILATION BLOCK ####### */ +#if COMPILE_MIDI_IN + public: - - bool read(); - bool read(const byte Channel); - - // Getters - kMIDIType getType() const; - byte getChannel() const; - byte getData1() const; - byte getData2() const; - const byte * getSysExArray() const; - unsigned int getSysExArrayLength() const; - bool check() const; - - byte getInputChannel() const + + bool read(); + bool read(const byte Channel); + + // Getters + kMIDIType getType() const; + byte getChannel() const; + byte getData1() const; + byte getData2() const; + const byte * getSysExArray() const; + unsigned int getSysExArrayLength() const; + bool check() const; + + byte getInputChannel() const { return mInputChannel; } - - // Setters - void setInputChannel(const byte Channel); - - /*! \brief Extract an enumerated MIDI type from a status byte. - - This is a utility static method, used internally, made public so you can handle kMIDITypes more easily. - */ - static inline const kMIDIType getTypeFromStatusByte(const byte inStatus) + + // Setters + void setInputChannel(const byte Channel); + + /*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, made public so you can handle kMIDITypes more easily. + */ + static inline const kMIDIType getTypeFromStatusByte(const byte inStatus) { - if ((inStatus < 0x80) - || (inStatus == 0xF4) - || (inStatus == 0xF5) - || (inStatus == 0xF9) - || (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. - if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble. - else return (kMIDIType)inStatus; - } - - + if ((inStatus < 0x80) + || (inStatus == 0xF4) + || (inStatus == 0xF5) + || (inStatus == 0xF9) + || (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. + if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble. + else return (kMIDIType)inStatus; + } + + #if USE_CALLBACKS - - void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); - void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); - void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); - void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); - void setHandleProgramChange(void (*fptr)(byte channel, byte number)); - void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); - void setHandlePitchBend(void (*fptr)(byte channel, int bend)); - void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); - void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); - void setHandleSongPosition(void (*fptr)(unsigned int beats)); - void setHandleSongSelect(void (*fptr)(byte songnumber)); - void setHandleTuneRequest(void (*fptr)(void)); - void setHandleClock(void (*fptr)(void)); - void setHandleStart(void (*fptr)(void)); - void setHandleContinue(void (*fptr)(void)); - void setHandleStop(void (*fptr)(void)); - void setHandleActiveSensing(void (*fptr)(void)); - void setHandleSystemReset(void (*fptr)(void)); - - void disconnectCallbackFromType(kMIDIType Type); - + + void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); + void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); + void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); + void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); + void setHandleProgramChange(void (*fptr)(byte channel, byte number)); + void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); + void setHandlePitchBend(void (*fptr)(byte channel, int bend)); + void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); + void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); + void setHandleSongPosition(void (*fptr)(unsigned int beats)); + void setHandleSongSelect(void (*fptr)(byte songnumber)); + void setHandleTuneRequest(void (*fptr)(void)); + void setHandleClock(void (*fptr)(void)); + void setHandleStart(void (*fptr)(void)); + void setHandleContinue(void (*fptr)(void)); + void setHandleStop(void (*fptr)(void)); + void setHandleActiveSensing(void (*fptr)(void)); + void setHandleSystemReset(void (*fptr)(void)); + + void disconnectCallbackFromType(kMIDIType Type); + #endif // USE_CALLBACKS - - + + private: - - bool input_filter(byte inChannel); - bool parse(byte inChannel); - void reset_input_attributes(); - - // Attributes - byte mRunningStatus_RX; - byte mInputChannel; - - byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; - unsigned int mPendingMessageExpectedLenght; - unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. - - midimsg mMessage; - + + bool input_filter(byte inChannel); + bool parse(byte inChannel); + void reset_input_attributes(); + + // Attributes + byte mRunningStatus_RX; + byte mInputChannel; + + byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; + unsigned int mPendingMessageExpectedLenght; + unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. + + midimsg mMessage; + #if USE_CALLBACKS - - void launchCallback(); - - void (*mNoteOffCallback)(byte channel, byte note, byte velocity); - void (*mNoteOnCallback)(byte channel, byte note, byte velocity); - void (*mAfterTouchPolyCallback)(byte channel, byte note, byte velocity); - void (*mControlChangeCallback)(byte channel, byte, byte); - void (*mProgramChangeCallback)(byte channel, byte); - void (*mAfterTouchChannelCallback)(byte channel, byte); - void (*mPitchBendCallback)(byte channel, int); - void (*mSystemExclusiveCallback)(byte * array, byte size); - void (*mTimeCodeQuarterFrameCallback)(byte data); - void (*mSongPositionCallback)(unsigned int beats); - void (*mSongSelectCallback)(byte songnumber); - void (*mTuneRequestCallback)(void); - void (*mClockCallback)(void); - void (*mStartCallback)(void); - void (*mContinueCallback)(void); - void (*mStopCallback)(void); - void (*mActiveSensingCallback)(void); - void (*mSystemResetCallback)(void); - + + void launchCallback(); + + void (*mNoteOffCallback)(byte channel, byte note, byte velocity); + void (*mNoteOnCallback)(byte channel, byte note, byte velocity); + void (*mAfterTouchPolyCallback)(byte channel, byte note, byte velocity); + void (*mControlChangeCallback)(byte channel, byte, byte); + void (*mProgramChangeCallback)(byte channel, byte); + void (*mAfterTouchChannelCallback)(byte channel, byte); + void (*mPitchBendCallback)(byte channel, int); + void (*mSystemExclusiveCallback)(byte * array, byte size); + void (*mTimeCodeQuarterFrameCallback)(byte data); + void (*mSongPositionCallback)(unsigned int beats); + void (*mSongSelectCallback)(byte songnumber); + void (*mTuneRequestCallback)(void); + void (*mClockCallback)(void); + void (*mStartCallback)(void); + void (*mContinueCallback)(void); + void (*mStopCallback)(void); + void (*mActiveSensingCallback)(void); + void (*mSystemResetCallback)(void); + #endif // USE_CALLBACKS - - + + #endif // COMPILE_MIDI_IN - - -/* ####### THRU COMPILATION BLOCK ####### */ + + + /* ####### THRU COMPILATION BLOCK ####### */ #if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru - + public: - - // Getters - kThruFilterMode getFilterMode() const { return mThruFilterMode; } - bool getThruState() const { return mThruActivated; } - - - // Setters - void turnThruOn(kThruFilterMode inThruFilterMode = Full); - void turnThruOff(); - - void setThruFilterMode(const kThruFilterMode inThruFilterMode); - - + + // Getters + kThruFilterMode getFilterMode() const { return mThruFilterMode; } + bool getThruState() const { return mThruActivated; } + + + // Setters + void turnThruOn(kThruFilterMode inThruFilterMode = Full); + void turnThruOff(); + + void setThruFilterMode(const kThruFilterMode inThruFilterMode); + + private: - - void thru_filter(byte inChannel); - - bool mThruActivated; - kThruFilterMode mThruFilterMode; - + + void thru_filter(byte inChannel); + + bool mThruActivated; + kThruFilterMode mThruFilterMode; + #endif // Thru - + }; extern MIDI_Class MIDI; From 698889dd6ea011fca59424b161f9eae1a09071a8 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 22:34:15 +0200 Subject: [PATCH 4/6] Cosmetics. --- src/MIDI.cpp | 178 +++++++++++++++++++++++++++++++-------------------- src/MIDI.h | 149 ++++++++++++++++++++++++++---------------- 2 files changed, 204 insertions(+), 123 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 335748f..9517f99 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -10,7 +10,8 @@ #include "MIDI.h" #include -#include "Arduino.h" // If using an old (pre-1.0) version of Arduino, use WConstants.h instead of Arduino.h +#include "Arduino.h" // If using an old (pre-1.0) version of Arduino, + // use WConstants.h instead of Arduino.h #include "HardwareSerial.h" @@ -20,13 +21,12 @@ #include "../SoftwareSerial/SoftwareSerial.h" SoftwareSerial softSerialClass(SOFTSERIAL_RX_PIN,SOFTSERIAL_TX_PIN); -#undef USE_SERIAL_PORT -#define USE_SERIAL_PORT softSerialClass +#undef MIDI_SERIAL_PORT +#define MIDI_SERIAL_PORT softSerialClass #endif // USE_SOFTWARE_SERIAL - /*! \brief Main instance (the class comes pre-instantiated). */ MIDI_Class MIDI; @@ -82,7 +82,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 @@ -122,6 +122,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 @@ -136,11 +140,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, @@ -172,17 +179,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; } @@ -195,9 +202,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, @@ -210,9 +221,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, @@ -225,8 +239,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) @@ -238,9 +252,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, @@ -253,9 +270,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, @@ -268,8 +285,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) @@ -281,8 +298,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) @@ -295,8 +314,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) @@ -308,8 +329,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) @@ -323,10 +346,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, @@ -335,22 +361,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; @@ -429,8 +457,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 +469,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 */ @@ -455,7 +484,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 @@ -473,14 +502,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() { @@ -490,7 +524,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) { @@ -522,7 +558,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. @@ -531,7 +567,7 @@ bool MIDI_Class::parse(byte inChannel) // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. if (bytes_available == 128) { - USE_SERIAL_PORT.flush(); + MIDI_SERIAL_PORT.flush(); } else { @@ -544,7 +580,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; @@ -856,7 +892,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 { @@ -922,7 +959,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) { @@ -957,7 +995,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) { @@ -1036,9 +1075,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 @@ -1075,7 +1116,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 993c1d6..58ea2f2 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -31,24 +31,24 @@ #define COMPILE_MIDI_IN 1 // Set this setting to 1 to use the MIDI input. #define COMPILE_MIDI_OUT 1 // Set this setting to 1 to use the MIDI output. #define COMPILE_MIDI_THRU 1 // Set this setting to 1 to use the MIDI Soft Thru feature -// Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. + // Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. -#define USE_SERIAL_PORT Serial // Change the number (to Serial1 for example) if you want -// to use a different serial port for MIDI I/O. +#define MIDI_SERIAL_PORT Serial // Change the number (to Serial1 for example) if you want + // to use a different serial port for MIDI I/O. -#define USE_SOFTWARE_SERIAL 1 // Set to 1 to use SoftwareSerial instead of native serial ports. +#define USE_SOFTWARE_SERIAL 0 // Set to 1 to use SoftwareSerial instead of native serial ports. #define SOFTSERIAL_RX_PIN 1 // This pin number will be used for MIDI Input #define SOFTSERIAL_TX_PIN 2 // This pin number will be used for MIDI Output. #define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values -// of the same type and channel. -// Set to 0 if you have troubles with controlling you hardware. + // of the same type and channel. + // Set to 0 if you have troubles controlling your hardware. #define USE_CALLBACKS 1 // Set this to 1 if you want to use callback handlers (to bind your functions to the library). -// To use the callbacks, you need to have COMPILE_MIDI_IN set to 1 + // To use the callbacks, you need to have COMPILE_MIDI_IN set to 1 #define USE_1BYTE_PARSING 1 // Each call to MIDI.read will only parse one byte (might be faster). @@ -63,10 +63,14 @@ #define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. -/*! Type definition for practical use (because "unsigned char" is a bit long to write.. )*/ -typedef uint8_t byte; + +/*! Type definition for practical use + (because "unsigned char" is a bit long to write.. ) + */ +typedef uint8_t byte; typedef uint16_t word; + /*! Enumeration of MIDI types */ enum kMIDIType { NoteOff = 0x80, ///< Note Off @@ -87,9 +91,10 @@ enum kMIDIType { 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 + InvalidType = 0x00 ///< For notifying errors }; + /*! Enumeration of Thru filter modes */ enum kThruFilterMode { Off = 0, ///< Thru disabled (nothing passes through). @@ -99,20 +104,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; + }; @@ -122,22 +153,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: @@ -170,12 +203,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: @@ -205,18 +240,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)); @@ -238,27 +295,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); @@ -280,14 +318,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 0e1f2e8d33546b66417caa5e5509fbc10984a617 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 23:17:52 +0200 Subject: [PATCH 5/6] Added two levels of doc, one for the git structure and one for the packaged (installed) structure. --- doc/Doxyfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 2bcae45..2aea847 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -619,7 +619,8 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = ../ +INPUT = ../ \ + ../src # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is From 36a8b73b2de17a71931953f80dff39af3c4fa032 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 23:18:10 +0200 Subject: [PATCH 6/6] Added scripts and resouces. --- res/install_local_mac.sh | 38 ++++++++++++++ res/install_mac.sh | 27 ++++++++++ res/keywords.txt | 105 +++++++++++++++++++++++++++++++++++++++ res/packaging.sh | 27 ++++++++++ 4 files changed, 197 insertions(+) create mode 100644 res/install_local_mac.sh create mode 100644 res/install_mac.sh create mode 100644 res/keywords.txt create mode 100644 res/packaging.sh diff --git a/res/install_local_mac.sh b/res/install_local_mac.sh new file mode 100644 index 0000000..bf517cc --- /dev/null +++ b/res/install_local_mac.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +if [[ -d /Applications/Arduino.app ]] +then + + # Define locations + + lib_path=/Applications/Arduino.app/Contents/Resources/Java/libraries/MIDI + + if [[ -d $lib_path ]] + then + # Remove old lib + rm -rf $lib_path + fi + + # Create folder + mkdir $lib_path + + # Copy sources + cp ../src/MIDI.cpp $lib_path + cp ../src/MIDI.h $lib_path + + # Copy resources + cp ../res/keywords.txt $lib_path + + # Copy examples + mkdir $lib_path/examples + + cp -r examples/* $lib_path/examples + + # Copy doc + mkdir $lib_path/doc + + cp ../doc/* $lib_path/doc + +else + echo "Arduino application not found." +fi diff --git a/res/install_mac.sh b/res/install_mac.sh new file mode 100644 index 0000000..9eaeac1 --- /dev/null +++ b/res/install_mac.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +if [[ -d /Applications/Arduino.app ]] +then + + # Define locations + + lib_path=/Applications/Arduino.app/Contents/Resources/Java/libraries/MIDI + + if [[ -d $lib_path ]] + then + # Remove old lib + rm -rf $lib_path + fi + + # Create folder + mkdir $lib_path + + # Install contents + cp -r * $lib_path + + # Cleanup + rm $lib_path/install_mac.sh + +else + echo "Arduino application not found." +fi diff --git a/res/keywords.txt b/res/keywords.txt new file mode 100644 index 0000000..5ce9af2 --- /dev/null +++ b/res/keywords.txt @@ -0,0 +1,105 @@ +####################################### +# Syntax Coloring Map For Test +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +MIDI KEYWORD1 +MIDI.h KEYWORD1 +MIDI_Class KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +send KEYWORD2 +sendNoteOn KEYWORD2 +sendNoteOff KEYWORD2 +sendProgramChange KEYWORD2 +sendControlChange KEYWORD2 +sendPitchBend KEYWORD2 +sendPolyPressure KEYWORD2 +sendAfterTouch KEYWORD2 +sendSysEx KEYWORD2 +sendTimeCodeQuarterFrame KEYWORD2 +sendSongPosition KEYWORD2 +sendSongSelect KEYWORD2 +sendTuneRequest KEYWORD2 +sendRealTime KEYWORD2 +begin KEYWORD2 +read KEYWORD2 +getType KEYWORD2 +getChannel KEYWORD2 +getData1 KEYWORD2 +getData2 KEYWORD2 +getSysExArray KEYWORD2 +getFilterMode KEYWORD2 +getThruState KEYWORD2 +getInputChannel KEYWORD2 +check KEYWORD2 +delMsg KEYWORD2 +delSysEx KEYWORD2 +setInputChannel KEYWORD2 +setStatus KEYWORD2 +turnThruOn KEYWORD2 +turnThruOff KEYWORD2 +setThruFilterMode KEYWORD2 +disconnectCallbackFromType KEYWORD2 +setHandleNoteOff KEYWORD2 +setHandleNoteOn KEYWORD2 +setHandleAfterTouchPoly KEYWORD2 +setHandleControlChange KEYWORD2 +setHandleProgramChange KEYWORD2 +setHandleAfterTouchChannel KEYWORD2 +setHandlePitchBend KEYWORD2 +setHandleSystemExclusive KEYWORD2 +setHandleTimeCodeQuarterFrame KEYWORD2 +setHandleSongPosition KEYWORD2 +setHandleSongSelect KEYWORD2 +setHandleTuneRequest KEYWORD2 +setHandleClock KEYWORD2 +setHandleStart KEYWORD2 +setHandleContinue KEYWORD2 +setHandleStop KEYWORD2 +setHandleActiveSensing KEYWORD2 +setHandleSystemReset KEYWORD2 +getTypeFromStatusByte KEYWORD2 + + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### + +NoteOff LITERAL1 +NoteOn LITERAL1 +AfterTouchPoly LITERAL1 +ControlChange LITERAL1 +ProgramChange LITERAL1 +AfterTouchChannel LITERAL1 +PitchBend LITERAL1 +SystemExclusive LITERAL1 +TimeCodeQuarterFrame LITERAL1 +SongPosition LITERAL1 +SongSelect LITERAL1 +TuneRequest LITERAL1 +Clock LITERAL1 +Start LITERAL1 +Stop LITERAL1 +Continue LITERAL1 +ActiveSensing LITERAL1 +SystemReset LITERAL1 +InvalidType LITERAL1 +Off LITERAL1 +Full LITERAL1 +SameChannel LITERAL1 +DifferentChannel LITERAL1 +MIDI_CHANNEL_OMNI LITERAL1 +MIDI_CHANNEL_OFF LITERAL1 +MIDI_BAUDRATE LITERAL1 +MIDI_SYSEX_ARRAY_SIZE LITERAL1 diff --git a/res/packaging.sh b/res/packaging.sh new file mode 100644 index 0000000..275fe83 --- /dev/null +++ b/res/packaging.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Create a temporary destination folder +mkdir -p temp/doc +mkdir -p temp/examples + +# Copy sources +cp ../src/* temp + +# Copy resources +cp keywords.txt temp + +# Copy examples +cp -r examples/* temp/examples + +# Generate & copy doc +cd ../doc +/Applications/Doxygen.app/Contents/Resources/doxygen Doxyfile +rm -rf latex +cd ../res + +cp -r ../doc/* temp/doc + +# Generate package +mv temp MIDI +zip -r MIDI.zip MIDI +mv MIDI.zip Arduino_MIDI_Library_v.zip