From 5c5069dcac5e88796578ee1d629c70ae6281656e Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 21:45:40 +0200 Subject: [PATCH 01/38] 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 02/38] 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 64a3ff548761db3c6ebf5461f3dfd75dee85d9cb Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 21:47:35 +0200 Subject: [PATCH 03/38] Added avr_core sources. --- src/MIDI.cpp | 1161 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/MIDI.h | 313 ++++++++++++++ 2 files changed, 1474 insertions(+) create mode 100644 src/MIDI.cpp create mode 100644 src/MIDI.h diff --git a/src/MIDI.cpp b/src/MIDI.cpp new file mode 100644 index 0000000..177b660 --- /dev/null +++ b/src/MIDI.cpp @@ -0,0 +1,1161 @@ +/*! + * @file MIDI.cpp + * Project MIDI Library + * @brief MIDI Library for the Arduino + * @version 3.2 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 + */ + +#include "MIDI.h" +#include "Serial.h" +#include + + +/*! \brief Main instance (the class comes pre-instantiated). */ +MIDI_Class MIDI; + + +/*! \brief Default constructor for MIDI_Class. */ +MIDI_Class::MIDI_Class() +{ + +#if USE_CALLBACKS + + // Initialise callbacks to NULL pointer + mNoteOffCallback = NULL; + mNoteOnCallback = NULL; + mAfterTouchPolyCallback = NULL; + mControlChangeCallback = NULL; + mProgramChangeCallback = NULL; + mAfterTouchChannelCallback = NULL; + mPitchBendCallback = NULL; + mSystemExclusiveCallback = NULL; + mTimeCodeQuarterFrameCallback = NULL; + mSongPositionCallback = NULL; + mSongSelectCallback = NULL; + mTuneRequestCallback = NULL; + mClockCallback = NULL; + mStartCallback = NULL; + mContinueCallback = NULL; + mStopCallback = NULL; + mActiveSensingCallback = NULL; + mSystemResetCallback = NULL; + +#endif + +} + + +/*! \brief Default destructor for MIDI_Class. + + This is not really useful for the Arduino, as it is never called... + */ +MIDI_Class::~MIDI_Class() +{ + +} + + +/*! \brief Call the begin method in the setup() function of the Arduino. + + All parameters are set to their default values: + - Input channel set to 1 if no value is specified + - Full thru mirroring + */ +void MIDI_Class::begin(const byte inChannel) +{ + + // Initialise the Serial port + USE_SERIAL_PORT.begin(MIDI_BAUDRATE); + + +#if COMPILE_MIDI_OUT + +#if USE_RUNNING_STATUS + + mRunningStatus_TX = InvalidType; + +#endif // USE_RUNNING_STATUS + +#endif // COMPILE_MIDI_OUT + + +#if COMPILE_MIDI_IN + + mInputChannel = inChannel; + mRunningStatus_RX = InvalidType; + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = false; + mMessage.type = InvalidType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + +#endif // COMPILE_MIDI_IN + + +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + + mThruFilterMode = Full; + mThruActivated = true; + +#endif // Thru + +} + + +#if COMPILE_MIDI_OUT + +// Private method for generating a status byte from channel and type +const byte MIDI_Class::genstatus(const kMIDIType inType, + const byte inChannel) const +{ + + return ((byte)inType | ((inChannel-1) & 0x0F)); + +} + + +/*! \brief Generate and send a MIDI message from the values given. + \param type The message type (see type defines for reference) + \param data1 The first data byte. + \param data2 The second data byte (if the message contains only 1 data byte, set this one to 0). + \param channel The output channel on which the message will be sent (values from 1 to 16). Note: you cannot send to OMNI. + + This is an internal method, use it only if you need to send raw data from your code, at your own risks. + */ +void MIDI_Class::send(kMIDIType type, + byte data1, + byte data2, + byte channel) +{ + + // Then test if channel is valid + if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { + +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif + + return; // Don't send anything + } + + if (type <= PitchBend) { + // Channel messages + + // Protection: remove MSBs on data + data1 &= 0x7F; + data2 &= 0x7F; + + byte statusbyte = genstatus(type,channel); + +#if USE_RUNNING_STATUS + // Check Running Status + if (mRunningStatus_TX != statusbyte) { + // New message, memorise and send header + mRunningStatus_TX = statusbyte; + USE_SERIAL_PORT.write(mRunningStatus_TX); + } +#else + // Don't care about running status, send the Control byte. + USE_SERIAL_PORT.write(statusbyte); +#endif + + // Then send data + USE_SERIAL_PORT.write(data1); + if (type != ProgramChange && type != AfterTouchChannel) { + USE_SERIAL_PORT.write(data2); + } + return; + } + if (type >= TuneRequest && type <= SystemReset) { + // System Real-time and 1 byte. + sendRealTime(type); + } + +} + + +/*! \brief Send a Note On message + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendNoteOn(byte NoteNumber, + byte Velocity, + byte Channel) +{ + + send(NoteOn,NoteNumber,Velocity,Channel); + +} + + +/*! \brief Send a Note Off message (a real Note Off, not a Note On with null velocity) + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Release velocity (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendNoteOff(byte NoteNumber, + byte Velocity, + byte Channel) +{ + + send(NoteOff,NoteNumber,Velocity,Channel); + +} + + +/*! \brief Send a Program Change message + \param ProgramNumber The Program to select (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendProgramChange(byte ProgramNumber, + byte Channel) +{ + + send(ProgramChange,ProgramNumber,0,Channel); + +} + + +/*! \brief Send a Control Change message + \param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums + \param ControlValue The value for the specified controller (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendControlChange(byte ControlNumber, + byte ControlValue, + byte Channel) +{ + + send(ControlChange,ControlNumber,ControlValue,Channel); + +} + + +/*! \brief Send a Polyphonic AfterTouch message (applies to only one specified note) + \param NoteNumber The note to apply AfterTouch to (0 to 127). + \param Pressure The amount of AfterTouch to apply (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendPolyPressure(byte NoteNumber, + byte Pressure, + byte Channel) +{ + + send(AfterTouchPoly,NoteNumber,Pressure,Channel); + +} + + +/*! \brief Send a MonoPhonic AfterTouch message (applies to all notes) + \param Pressure The amount of AfterTouch to apply to all notes. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendAfterTouch(byte Pressure, + byte Channel) +{ + + send(AfterTouchChannel,Pressure,0,Channel); + +} + + +/*! \brief Send a Pitch Bend message using a signed integer value. + \param PitchValue The amount of bend to send (in a signed integer format), between -8192 (maximum downwards bend) and 8191 (max upwards bend), center value is 0. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendPitchBend(int PitchValue, + byte Channel) +{ + + unsigned int bend = PitchValue + 8192; + sendPitchBend(bend,Channel); + +} + + +/*! \brief Send a Pitch Bend message using an unsigned integer value. + \param PitchValue The amount of bend to send (in a signed integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendPitchBend(unsigned int PitchValue, + byte Channel) +{ + + send(PitchBend,(PitchValue & 0x7F),(PitchValue >> 7) & 0x7F,Channel); + +} + + +/*! \brief Send a Pitch Bend message using a floating point value. + \param PitchValue The amount of bend to send (in a floating point format), between -1.0f (maximum downwards bend) and +1.0f (max upwards bend), center value is 0.0f. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendPitchBend(double PitchValue, + byte Channel) +{ + + unsigned int pitchval = (PitchValue+1.f)*8192; + if (pitchval > 16383) pitchval = 16383; // overflow protection + sendPitchBend(pitchval,Channel); + +} + + +/*! \brief Generate and send a System Exclusive frame. + \param length The size of the array to send + \param array The byte array containing the data to send + \param ArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes (start & stop SysEx) will NOT be sent (and therefore must be included in the array). + default value is set to 'false' for compatibility with previous versions of the library. + */ +void MIDI_Class::sendSysEx(int length, + const byte *const array, + bool ArrayContainsBoundaries) +{ + + if (ArrayContainsBoundaries == false) { + + USE_SERIAL_PORT.write(0xF0); + + for (int i=0;i> 7) & 0x7F); + +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif + +} + + +/*! \brief Send a Song Select message */ +void MIDI_Class::sendSongSelect(byte SongNumber) +{ + + USE_SERIAL_PORT.write((byte)SongSelect); + USE_SERIAL_PORT.write(SongNumber & 0x7F); + +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif + +} + + +/*! \brief Send a Real Time (one byte) message. + + \param Type The available Real Time types are: Start, Stop, Continue, Clock, ActiveSensing and SystemReset. + You can also send a Tune Request with this method. + @see kMIDIType + */ +void MIDI_Class::sendRealTime(kMIDIType Type) +{ + switch (Type) { + case TuneRequest: // Not really real-time, but one byte anyway. + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + USE_SERIAL_PORT.write((byte)Type); + break; + default: + // Invalid Real Time marker + break; + } + + // Do not cancel Running Status for real-time messages as they can be interleaved within any message. + // Though, TuneRequest can be sent here, and as it is a System Common message, it must reset Running Status. +#if USE_RUNNING_STATUS + if (Type == TuneRequest) mRunningStatus_TX = InvalidType; +#endif + +} + +#endif // COMPILE_MIDI_OUT + + + +#if COMPILE_MIDI_IN + +/*! \brief Read a MIDI message from the serial port using the main input channel (see setInputChannel() for reference). + + Returned value: true if any valid message has been stored in the structure, false if not. + A valid message is a message that matches the input channel. \n\n + If the Thru is enabled and the messages matches the filter, it is sent back on the MIDI output. + */ +bool MIDI_Class::read() +{ + + return read(mInputChannel); + +} + + +/*! \brief Reading/thru-ing method, the same as read() with a given input channel to read on. */ +bool MIDI_Class::read(const byte inChannel) +{ + + if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. + + if (parse(inChannel)) { + + if (input_filter(inChannel)) { + +#if (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) + thru_filter(inChannel); +#endif + +#if USE_CALLBACKS + launchCallback(); +#endif + + return true; + } + + } + + return false; + +} + + +// Private method: MIDI parser +bool MIDI_Class::parse(byte inChannel) +{ + + const int bytes_available = USE_SERIAL_PORT.available(); + + if (bytes_available <= 0) { + // No data available. + return false; + } + + // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. + if (bytes_available == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; + USE_SERIAL_PORT.flush(); + } + else { + + /* Parsing algorithm: + Get a byte from the serial buffer. + * If there is no pending message to be recomposed, start a new one. + - Find type and channel (if pertinent) + - Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty. + * Else, add the extracted byte to the pending message, and check validity. When the message is done, store it. + */ + + + const byte extracted = USE_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) { // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + switch (getTypeFromStatusByte(mRunningStatus_RX)) { + // Only these types allow Running Status: + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + + // If the status byte is not received, prepend it to the pending message + if (extracted < 0x80) { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, so the running status does not apply here. + // It will be updated upon completion of this message. + + break; + + default: + // No running status + break; + } + + + switch (getTypeFromStatusByte(mPendingMessage[0])) { + + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //reset_input_attributes(); + + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLenght = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLenght = 3; + break; + + case SystemExclusive: + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mRunningStatus_RX = InvalidType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + reset_input_attributes(); + return false; + break; + } + + // Then update the index of the pending message. + mPendingMessageIndex++; + +#if USE_1BYTE_PARSING + // Message is not complete. + return false; +#else + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); +#endif + + } + else { + + // First, test if this is a status byte + if (extracted >= 0x80) { + + // Reception of status bytes in the middle of an uncompleted message + // are allowed only for interleaved Real Time message or EOX + switch (extracted) { + case Clock: + case Start: + case Continue: + case Stop: + case ActiveSensing: + case SystemReset: + + /* + This is tricky. Here we will have to extract the one-byte message, + pass it to the structure for being read outside the MIDI class, + and recompose the message it was interleaved into. + + Oh, and without killing the running status.. + + This is done by leaving the pending message as is, it will be completed on next calls. + */ + + mMessage.type = (kMIDIType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xF7: + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + + // Store System Exclusive array in midimsg structure + for (byte i=0;i> 8; + + mMessage.channel = 0; + mMessage.valid = true; + + reset_input_attributes(); + + return true; + } + else { + // Well well well.. error. + reset_input_attributes(); + return false; + } + + break; + default: + break; + } + + + + } + + + // Add extracted data byte to pending message + mPendingMessage[mPendingMessageIndex] = extracted; + + + // Now we are going to check if we have reached the end of the message + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { + + // "FML" case: fall down here with an overflown SysEx.. + // This means we received the last possible data byte that can fit the buffer. + // If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + reset_input_attributes(); + return false; + } + + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; + else mMessage.data2 = 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) { + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; + break; + + default: + // No running status + mRunningStatus_RX = InvalidType; + break; + } + return true; + } + else { + // Then update the index of the pending message. + mPendingMessageIndex++; + +#if USE_1BYTE_PARSING + // Message is not complete. + return false; +#else + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); +#endif + + } + + } + + } + + // What are our chances to fall here? + return false; +} + + +// Private method: check if the received message is on the listened channel +bool MIDI_Class::input_filter(byte inChannel) +{ + + + // This method handles recognition of channel (to know if the message is destinated to the Arduino) + + + if (mMessage.type == InvalidType) return false; + + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { + + // Then we need to know if we listen to it + if ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)) { + return true; + + } + else { + // We don't listen to this channel + return false; + } + + } + else { + + // System messages are always received + return true; + } + +} + + +// Private method: reset input attributes +void MIDI_Class::reset_input_attributes() +{ + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; + +} + + +// Getters +/*! \brief Get the last received message's type + + Returns an enumerated type. @see kMIDIType + */ +kMIDIType MIDI_Class::getType() const +{ + + return mMessage.type; + +} + + +/*! \brief Get the channel of the message stored in the structure. + + Channel range is 1 to 16. For non-channel messages, this will return 0. + */ +byte MIDI_Class::getChannel() const +{ + + return mMessage.channel; + +} + + +/*! \brief Get the first data byte of the last received message. */ +byte MIDI_Class::getData1() const +{ + + return mMessage.data1; + +} + + +/*! \brief Get the second data byte of the last received message. */ +byte MIDI_Class::getData2() const +{ + + return mMessage.data2; + +} + + +/*! \brief Get the System Exclusive byte array. + + @see getSysExArrayLength to get the array's length in bytes. + */ +const byte * MIDI_Class::getSysExArray() const +{ + + return mMessage.sysex_array; + +} + +/*! \brief Get the lenght of the System Exclusive array. + + It is coded using data1 as LSB and data2 as MSB. + \return The array's length, in bytes. + */ +unsigned int MIDI_Class::getSysExArrayLength() const +{ + + unsigned int coded_size = ((unsigned int)(mMessage.data2) << 8) | mMessage.data1; + + return (coded_size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : coded_size; + +} + + +/*! \brief Check if a valid message is stored in the structure. */ +bool MIDI_Class::check() const +{ + + return mMessage.valid; + +} + + +// Setters +/*! \brief Set the value for the input MIDI channel + \param Channel the channel value. Valid values are 1 to 16, + MIDI_CHANNEL_OMNI if you want to listen to all channels, and MIDI_CHANNEL_OFF to disable MIDI input. + */ +void MIDI_Class::setInputChannel(const byte Channel) +{ + + mInputChannel = Channel; + +} + + +#if USE_CALLBACKS + +void MIDI_Class::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } +void MIDI_Class::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } +void MIDI_Class::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } +void MIDI_Class::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } +void MIDI_Class::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } +void MIDI_Class::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } +void MIDI_Class::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } +void MIDI_Class::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } +void MIDI_Class::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } +void MIDI_Class::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } +void MIDI_Class::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } +void MIDI_Class::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } +void MIDI_Class::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } +void MIDI_Class::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +void MIDI_Class::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +void MIDI_Class::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +void MIDI_Class::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } + + +/*! \brief Detach an external function from the given type. + + Use this method to cancel the effects of setHandle********. + \param Type The type of message to unbind. When a message of this type is received, no function will be called. + */ +void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) +{ + + switch (Type) { + case NoteOff: mNoteOffCallback = NULL; break; + case NoteOn: mNoteOnCallback = NULL; break; + case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; + case ControlChange: mControlChangeCallback = NULL; break; + case ProgramChange: mProgramChangeCallback = NULL; break; + case AfterTouchChannel: mAfterTouchChannelCallback = NULL; break; + case PitchBend: mPitchBendCallback = NULL; break; + case SystemExclusive: mSystemExclusiveCallback = NULL; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = NULL; break; + case SongPosition: mSongPositionCallback = NULL; break; + case SongSelect: mSongSelectCallback = NULL; break; + case TuneRequest: mTuneRequestCallback = NULL; break; + case Clock: mClockCallback = NULL; break; + case Start: mStartCallback = NULL; break; + case Continue: mContinueCallback = NULL; break; + case Stop: mStopCallback = NULL; break; + case ActiveSensing: mActiveSensingCallback = NULL; break; + case SystemReset: mSystemResetCallback = NULL; break; + default: + break; + } + +} + + +// Private - launch callback function based on received type. +void MIDI_Class::launchCallback() +{ + + // The order is mixed to allow frequent messages to trigger their callback faster. + + switch (mMessage.type) { + // Notes + case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != NULL) mClockCallback(); break; + case Start: if (mStartCallback != NULL) mStartCallback(); break; + case Continue: if (mContinueCallback != NULL) mContinueCallback(); break; + case Stop: if (mStopCallback != NULL) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != NULL) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != NULL) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) - 8192); break; // TODO: check this + case AfterTouchPoly: if (mAfterTouchPolyCallback != NULL) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != NULL) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; + + case ProgramChange: if (mProgramChangeCallback != NULL) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != NULL) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; + + // Occasional messages + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != NULL) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != NULL) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; + case SongSelect: if (mSongSelectCallback != NULL) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != NULL) mTuneRequestCallback(); break; + + case SystemReset: if (mSystemResetCallback != NULL) mSystemResetCallback(); break; + case InvalidType: + default: + break; + } + +} + + +#endif // USE_CALLBACKS + + +#endif // COMPILE_MIDI_IN + + + + +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + +/*! \brief Set the filter for thru mirroring + \param inThruFilterMode a filter mode + + @see kThruFilterMode + */ +void MIDI_Class::setThruFilterMode(kThruFilterMode inThruFilterMode) +{ + + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) mThruActivated = true; + else mThruActivated = false; + +} + + +/*! \brief Setter method: turn message mirroring on. */ +void MIDI_Class::turnThruOn(kThruFilterMode inThruFilterMode) +{ + + mThruActivated = true; + mThruFilterMode = inThruFilterMode; + +} + + +/*! \brief Setter method: turn message mirroring off. */ +void MIDI_Class::turnThruOff() +{ + + mThruActivated = false; + mThruFilterMode = Off; + +} + + +// This method is called upon reception of a message and takes care of Thru filtering and sending. +void MIDI_Class::thru_filter(byte inChannel) +{ + + /* + This method handles Soft-Thru filtering. + + Soft-Thru filtering: + - All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off + - Channel messages are passed to the output whether their channel is matching the input channel and the filter setting + + */ + + // If the feature is disabled, don't do anything. + if (!mThruActivated || (mThruFilterMode == Off)) return; + + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { + + const bool filter_condition = ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)); + + // Now let's pass it to the output + switch (mThruFilterMode) { + case Full: + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + break; + case SameChannel: + if (filter_condition) { + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + } + break; + case DifferentChannel: + if (!filter_condition) { + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + } + break; + case Off: + // Do nothing. + // Technically it's impossible to get there because the case was already tested earlier. + break; + default: + break; + } + + } + else { + + // Send the message to the output + switch (mMessage.type) { + // Real Time and 1 byte + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + case TuneRequest: + sendRealTime(mMessage.type); + return; + break; + + case SystemExclusive: + // Send SysEx (0xF0 and 0xF7 are included in the buffer) + sendSysEx(mMessage.data1,mMessage.sysex_array,true); + return; + break; + + case SongSelect: + sendSongSelect(mMessage.data1); + return; + break; + + case SongPosition: + sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7)); + return; + break; + + case TimeCodeQuarterFrame: + sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); + return; + break; + default: + break; + + } + + } + +} + + +#endif // Thru + + diff --git a/src/MIDI.h b/src/MIDI.h new file mode 100644 index 0000000..ff20f8a --- /dev/null +++ b/src/MIDI.h @@ -0,0 +1,313 @@ +/*! + * @file MIDI.h + * Project MIDI Library + * @brief MIDI Library for the Arduino + * Version 3.2 + * @author Francois Best + * @date 24/02/11 + * License GPL Forty Seven Effects - 2011 + */ + +#ifndef LIB_MIDI_H_ +#define LIB_MIDI_H_ + +#include "Types.h" // Include all the types we need. + + +/* + ############################################################### + # # + # CONFIGURATION AREA # + # # + # Here are a few settings you can change to customize # + # the library for your own project. You can for example # + # choose to compile only parts of it so you gain flash # + # space and optimise the speed of your sketch. # + # # + ############################################################### + */ + + +#define COMPILE_MIDI_IN 1 // Set this setting to 1 to use the MIDI input. +#define COMPILE_MIDI_OUT 1 // Set this setting to 1 to use the MIDI output. +#define COMPILE_MIDI_THRU 1 // Set this setting to 1 to use the MIDI Soft Thru feature + // Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. + + +#define USE_SERIAL_PORT Serial1 // Change the number (to Serial1 for example) if you want + // to use a different serial port for MIDI I/O. + + +#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values + // of the same type and channel. + // Set to 0 if you have troubles with controlling you hardware. + + +#define USE_CALLBACKS 1 // Set this to 1 if you want to use callback handlers (to bind your functions to the library). + // To use the callbacks, you need to have COMPILE_MIDI_IN set to 1 + +#define USE_1BYTE_PARSING 1 // Each call to MIDI.read will only parse one byte (might be faster). + + +// END OF CONFIGURATION AREA +// (do not modify anything under this line unless you know what you are doing) + + +#define MIDI_BAUDRATE 31250 + +#define MIDI_CHANNEL_OMNI 0 +#define MIDI_CHANNEL_OFF 17 // and over + +#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. + + +/*! Enumeration of MIDI types */ +enum kMIDIType { + NoteOff = 0x80, ///< Note Off + NoteOn = 0x90, ///< Note On + AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch + ControlChange = 0xB0, ///< Control Change / Channel Mode + ProgramChange = 0xC0, ///< Program Change + AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch + PitchBend = 0xE0, ///< Pitch Bend + SystemExclusive = 0xF0, ///< System Exclusive + TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame + SongPosition = 0xF2, ///< System Common - Song Position Pointer + SongSelect = 0xF3, ///< System Common - Song Select + TuneRequest = 0xF6, ///< System Common - Tune Request + Clock = 0xF8, ///< System Real Time - Timing Clock + Start = 0xFA, ///< System Real Time - Start + Continue = 0xFB, ///< System Real Time - Continue + Stop = 0xFC, ///< System Real Time - Stop + ActiveSensing = 0xFE, ///< System Real Time - Active Sensing + SystemReset = 0xFF, ///< System Real Time - System Reset + InvalidType = 0x00 ///< For notifying errors +}; + +/*! Enumeration of Thru filter modes */ +enum kThruFilterMode { + Off = 0, ///< Thru disabled (nothing passes through). + Full = 1, ///< Fully enabled Thru (every incoming message is sent back). + SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. + DifferentChannel = 3 ///< All the messages but the ones on the Input Channel will be sent back. +}; + + +/*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). \n */ +struct midimsg { + /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ + byte channel; + /*! The type of the message (see the define section for types reference) */ + kMIDIType type; + /*! The first data byte.\n Value goes from 0 to 127.\n */ + byte data1; + /*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */ + byte data2; + /*! System Exclusive dedicated byte array. \n Array length is stocked on 16 bits, in data1 (LSB) and data2 (MSB) */ + byte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; + /*! This boolean indicates if the message is valid or not. There is no channel consideration here, validity means the message respects the MIDI norm. */ + bool valid; +}; + + + + +/*! \brief The main class for MIDI handling.\n + See member descriptions to know how to use it, + or check out the examples supplied with the library. + */ +class MIDI_Class { + + +public: + // Constructor and Destructor + MIDI_Class(); + ~MIDI_Class(); + + + void begin(const byte inChannel = 1); + + + + +/* ####### OUTPUT COMPILATION BLOCK ####### */ +#if COMPILE_MIDI_OUT + +public: + + void sendNoteOn(byte NoteNumber,byte Velocity,byte Channel); + void sendNoteOff(byte NoteNumber,byte Velocity,byte Channel); + void sendProgramChange(byte ProgramNumber,byte Channel); + void sendControlChange(byte ControlNumber, byte ControlValue,byte Channel); + void sendPitchBend(int PitchValue,byte Channel); + void sendPitchBend(unsigned int PitchValue,byte Channel); + void sendPitchBend(double PitchValue,byte Channel); + void sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel); + void sendAfterTouch(byte Pressure,byte Channel); + void sendSysEx(int length, const byte *const array,bool ArrayContainsBoundaries = false); + void sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble); + void sendTimeCodeQuarterFrame(byte data); + void sendSongPosition(unsigned int Beats); + void sendSongSelect(byte SongNumber); + void sendTuneRequest(); + void sendRealTime(kMIDIType Type); + + void send(kMIDIType type, byte param1, byte param2, byte channel); + +private: + + const byte genstatus(const kMIDIType inType,const byte inChannel) const; + + + // Attributes +#if USE_RUNNING_STATUS + byte mRunningStatus_TX; +#endif // USE_RUNNING_STATUS + +#endif // COMPILE_MIDI_OUT + + + +/* ####### INPUT COMPILATION BLOCK ####### */ +#if COMPILE_MIDI_IN + +public: + + bool read(); + bool read(const byte Channel); + + // Getters + kMIDIType getType() const; + byte getChannel() const; + byte getData1() const; + byte getData2() const; + const byte * getSysExArray() const; + unsigned int getSysExArrayLength() const; + bool check() const; + + byte getInputChannel() const + { + return mInputChannel; + } + + // Setters + void setInputChannel(const byte Channel); + + /*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, made public so you can handle kMIDITypes more easily. + */ + static inline const kMIDIType getTypeFromStatusByte(const byte inStatus) + { + if ((inStatus < 0x80) + || (inStatus == 0xF4) + || (inStatus == 0xF5) + || (inStatus == 0xF9) + || (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. + if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble. + else return (kMIDIType)inStatus; + } + + +#if USE_CALLBACKS + + void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); + void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); + void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); + void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); + void setHandleProgramChange(void (*fptr)(byte channel, byte number)); + void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); + void setHandlePitchBend(void (*fptr)(byte channel, int bend)); + void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); + void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); + void setHandleSongPosition(void (*fptr)(unsigned int beats)); + void setHandleSongSelect(void (*fptr)(byte songnumber)); + void setHandleTuneRequest(void (*fptr)(void)); + void setHandleClock(void (*fptr)(void)); + void setHandleStart(void (*fptr)(void)); + void setHandleContinue(void (*fptr)(void)); + void setHandleStop(void (*fptr)(void)); + void setHandleActiveSensing(void (*fptr)(void)); + void setHandleSystemReset(void (*fptr)(void)); + + void disconnectCallbackFromType(kMIDIType Type); + +#endif // USE_CALLBACKS + + +private: + + bool input_filter(byte inChannel); + bool parse(byte inChannel); + void reset_input_attributes(); + + // Attributes + byte mRunningStatus_RX; + byte mInputChannel; + + byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; + unsigned int mPendingMessageExpectedLenght; + unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. + + midimsg mMessage; + +#if USE_CALLBACKS + + void launchCallback(); + + void (*mNoteOffCallback)(byte channel, byte note, byte velocity); + void (*mNoteOnCallback)(byte channel, byte note, byte velocity); + void (*mAfterTouchPolyCallback)(byte channel, byte note, byte velocity); + void (*mControlChangeCallback)(byte channel, byte, byte); + void (*mProgramChangeCallback)(byte channel, byte); + void (*mAfterTouchChannelCallback)(byte channel, byte); + void (*mPitchBendCallback)(byte channel, int); + void (*mSystemExclusiveCallback)(byte * array, byte size); + void (*mTimeCodeQuarterFrameCallback)(byte data); + void (*mSongPositionCallback)(unsigned int beats); + void (*mSongSelectCallback)(byte songnumber); + void (*mTuneRequestCallback)(void); + void (*mClockCallback)(void); + void (*mStartCallback)(void); + void (*mContinueCallback)(void); + void (*mStopCallback)(void); + void (*mActiveSensingCallback)(void); + void (*mSystemResetCallback)(void); + +#endif // USE_CALLBACKS + + +#endif // COMPILE_MIDI_IN + + +/* ####### THRU COMPILATION BLOCK ####### */ +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + +public: + + // Getters + kThruFilterMode getFilterMode() const { return mThruFilterMode; } + bool getThruState() const { return mThruActivated; } + + + // Setters + void turnThruOn(kThruFilterMode inThruFilterMode = Full); + void turnThruOff(); + + void setThruFilterMode(const kThruFilterMode inThruFilterMode); + + +private: + + void thru_filter(byte inChannel); + + bool mThruActivated; + kThruFilterMode mThruFilterMode; + +#endif // Thru + +}; + +extern MIDI_Class MIDI; + +#endif // LIB_MIDI_H_ From 21b1e76e958eb597adc77167bbfc1671ebdfe2a9 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 22:00:50 +0200 Subject: [PATCH 04/38] 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 2dd633816d36be390a1141998b6452537f539765 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 22:09:41 +0200 Subject: [PATCH 05/38] Cosmetics. --- src/MIDI.cpp | 1568 +++++++++++++++++++++++++------------------------- src/MIDI.h | 438 +++++++------- 2 files changed, 1003 insertions(+), 1003 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 177b660..71fec37 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -1,11 +1,11 @@ /*! - * @file MIDI.cpp - * Project MIDI Library - * @brief MIDI Library for the Arduino - * @version 3.2 - * @author Francois Best - * @date 24/02/11 - * license GPL Forty Seven Effects - 2011 + * @file MIDI.cpp + * Project AVR Core MIDI Library + * @brief MIDI Library for the AVR Core + * @version 3.2 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 */ #include "MIDI.h" @@ -20,31 +20,31 @@ MIDI_Class MIDI; /*! \brief Default constructor for MIDI_Class. */ MIDI_Class::MIDI_Class() { - + #if USE_CALLBACKS - - // Initialise callbacks to NULL pointer - mNoteOffCallback = NULL; - mNoteOnCallback = NULL; - mAfterTouchPolyCallback = NULL; - mControlChangeCallback = NULL; - mProgramChangeCallback = NULL; - mAfterTouchChannelCallback = NULL; - mPitchBendCallback = NULL; - mSystemExclusiveCallback = NULL; - mTimeCodeQuarterFrameCallback = NULL; - mSongPositionCallback = NULL; - mSongSelectCallback = NULL; - mTuneRequestCallback = NULL; - mClockCallback = NULL; - mStartCallback = NULL; - mContinueCallback = NULL; - mStopCallback = NULL; - mActiveSensingCallback = NULL; - mSystemResetCallback = NULL; - + + // Initialise callbacks to NULL pointer + mNoteOffCallback = NULL; + mNoteOnCallback = NULL; + mAfterTouchPolyCallback = NULL; + mControlChangeCallback = NULL; + mProgramChangeCallback = NULL; + mAfterTouchChannelCallback = NULL; + mPitchBendCallback = NULL; + mSystemExclusiveCallback = NULL; + mTimeCodeQuarterFrameCallback = NULL; + mSongPositionCallback = NULL; + mSongSelectCallback = NULL; + mTuneRequestCallback = NULL; + mClockCallback = NULL; + mStartCallback = NULL; + mContinueCallback = NULL; + mStopCallback = NULL; + mActiveSensingCallback = NULL; + mSystemResetCallback = NULL; + #endif - + } @@ -54,7 +54,7 @@ MIDI_Class::MIDI_Class() */ MIDI_Class::~MIDI_Class() { - + } @@ -66,45 +66,45 @@ MIDI_Class::~MIDI_Class() */ void MIDI_Class::begin(const byte inChannel) { - - // Initialise the Serial port - USE_SERIAL_PORT.begin(MIDI_BAUDRATE); - - + + // Initialise the Serial port + USE_SERIAL_PORT.begin(MIDI_BAUDRATE); + + #if COMPILE_MIDI_OUT - + #if USE_RUNNING_STATUS - - mRunningStatus_TX = InvalidType; - + + mRunningStatus_TX = InvalidType; + #endif // USE_RUNNING_STATUS - + #endif // COMPILE_MIDI_OUT - - + + #if COMPILE_MIDI_IN - - mInputChannel = inChannel; - mRunningStatus_RX = InvalidType; - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = false; - mMessage.type = InvalidType; - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - + + mInputChannel = inChannel; + mRunningStatus_RX = InvalidType; + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = false; + mMessage.type = InvalidType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + #endif // COMPILE_MIDI_IN - - + + #if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru - - mThruFilterMode = Full; - mThruActivated = true; - + + mThruFilterMode = Full; + mThruActivated = true; + #endif // Thru - + } @@ -112,240 +112,240 @@ void MIDI_Class::begin(const byte inChannel) // Private method for generating a status byte from channel and type const byte MIDI_Class::genstatus(const kMIDIType inType, - const byte inChannel) const + const byte inChannel) const { - - return ((byte)inType | ((inChannel-1) & 0x0F)); - + + return ((byte)inType | ((inChannel-1) & 0x0F)); + } /*! \brief Generate and send a MIDI message from the values given. - \param type The message type (see type defines for reference) - \param data1 The first data byte. - \param data2 The second data byte (if the message contains only 1 data byte, set this one to 0). - \param channel The output channel on which the message will be sent (values from 1 to 16). Note: you cannot send to OMNI. + \param type The message type (see type defines for reference) + \param data1 The first data byte. + \param data2 The second data byte (if the message contains only 1 data byte, set this one to 0). + \param channel The output channel on which the message will be sent (values from 1 to 16). Note: you cannot send to OMNI. This is an internal method, use it only if you need to send raw data from your code, at your own risks. */ void MIDI_Class::send(kMIDIType type, - byte data1, - byte data2, - byte channel) + byte data1, + byte data2, + byte channel) { - - // Then test if channel is valid - if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { - -#if USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; + + // Then test if channel is valid + if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { + +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; #endif - - return; // Don't send anything - } - - if (type <= PitchBend) { - // Channel messages - - // Protection: remove MSBs on data - data1 &= 0x7F; - data2 &= 0x7F; - - byte statusbyte = genstatus(type,channel); - + + return; // Don't send anything + } + + if (type <= PitchBend) { + // Channel messages + + // Protection: remove MSBs on data + data1 &= 0x7F; + data2 &= 0x7F; + + byte statusbyte = genstatus(type,channel); + #if USE_RUNNING_STATUS - // Check Running Status - if (mRunningStatus_TX != statusbyte) { - // New message, memorise and send header - mRunningStatus_TX = statusbyte; - USE_SERIAL_PORT.write(mRunningStatus_TX); - } + // Check Running Status + if (mRunningStatus_TX != statusbyte) { + // New message, memorise and send header + mRunningStatus_TX = statusbyte; + USE_SERIAL_PORT.write(mRunningStatus_TX); + } #else - // Don't care about running status, send the Control byte. - USE_SERIAL_PORT.write(statusbyte); + // Don't care about running status, send the Control byte. + USE_SERIAL_PORT.write(statusbyte); #endif - - // Then send data - USE_SERIAL_PORT.write(data1); - if (type != ProgramChange && type != AfterTouchChannel) { - USE_SERIAL_PORT.write(data2); - } - return; - } - if (type >= TuneRequest && type <= SystemReset) { - // System Real-time and 1 byte. - sendRealTime(type); - } - + + // Then send data + USE_SERIAL_PORT.write(data1); + if (type != ProgramChange && type != AfterTouchChannel) { + USE_SERIAL_PORT.write(data2); + } + return; + } + if (type >= TuneRequest && type <= SystemReset) { + // System Real-time and 1 byte. + sendRealTime(type); + } + } /*! \brief Send a Note On message - \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n - \param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff. - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendNoteOn(byte NoteNumber, - byte Velocity, - byte Channel) + byte Velocity, + byte Channel) { - - send(NoteOn,NoteNumber,Velocity,Channel); - + + send(NoteOn,NoteNumber,Velocity,Channel); + } /*! \brief Send a Note Off message (a real Note Off, not a Note On with null velocity) - \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n - \param Velocity Release velocity (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Release velocity (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendNoteOff(byte NoteNumber, - byte Velocity, - byte Channel) + byte Velocity, + byte Channel) { - - send(NoteOff,NoteNumber,Velocity,Channel); - + + send(NoteOff,NoteNumber,Velocity,Channel); + } /*! \brief Send a Program Change message - \param ProgramNumber The Program to select (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param ProgramNumber The Program to select (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendProgramChange(byte ProgramNumber, - byte Channel) + byte Channel) { - - send(ProgramChange,ProgramNumber,0,Channel); - + + send(ProgramChange,ProgramNumber,0,Channel); + } /*! \brief Send a Control Change message - \param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums - \param ControlValue The value for the specified controller (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums + \param ControlValue The value for the specified controller (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendControlChange(byte ControlNumber, - byte ControlValue, - byte Channel) + byte ControlValue, + byte Channel) { - - send(ControlChange,ControlNumber,ControlValue,Channel); - + + send(ControlChange,ControlNumber,ControlValue,Channel); + } /*! \brief Send a Polyphonic AfterTouch message (applies to only one specified note) - \param NoteNumber The note to apply AfterTouch to (0 to 127). - \param Pressure The amount of AfterTouch to apply (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber The note to apply AfterTouch to (0 to 127). + \param Pressure The amount of AfterTouch to apply (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPolyPressure(byte NoteNumber, - byte Pressure, - byte Channel) + byte Pressure, + byte Channel) { - - send(AfterTouchPoly,NoteNumber,Pressure,Channel); - + + send(AfterTouchPoly,NoteNumber,Pressure,Channel); + } /*! \brief Send a MonoPhonic AfterTouch message (applies to all notes) - \param Pressure The amount of AfterTouch to apply to all notes. - \param Channel The channel on which the message will be sent (1 to 16). + \param Pressure The amount of AfterTouch to apply to all notes. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendAfterTouch(byte Pressure, - byte Channel) + byte Channel) { - - send(AfterTouchChannel,Pressure,0,Channel); - + + send(AfterTouchChannel,Pressure,0,Channel); + } /*! \brief Send a Pitch Bend message using a signed integer value. - \param PitchValue The amount of bend to send (in a signed integer format), between -8192 (maximum downwards bend) and 8191 (max upwards bend), center value is 0. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a signed integer format), between -8192 (maximum downwards bend) and 8191 (max upwards bend), center value is 0. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(int PitchValue, - byte Channel) + byte Channel) { - - unsigned int bend = PitchValue + 8192; - sendPitchBend(bend,Channel); - + + unsigned int bend = PitchValue + 8192; + sendPitchBend(bend,Channel); + } /*! \brief Send a Pitch Bend message using an unsigned integer value. - \param PitchValue The amount of bend to send (in a signed integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a signed integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(unsigned int PitchValue, - byte Channel) + byte Channel) { - - send(PitchBend,(PitchValue & 0x7F),(PitchValue >> 7) & 0x7F,Channel); - + + send(PitchBend,(PitchValue & 0x7F),(PitchValue >> 7) & 0x7F,Channel); + } /*! \brief Send a Pitch Bend message using a floating point value. - \param PitchValue The amount of bend to send (in a floating point format), between -1.0f (maximum downwards bend) and +1.0f (max upwards bend), center value is 0.0f. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a floating point format), between -1.0f (maximum downwards bend) and +1.0f (max upwards bend), center value is 0.0f. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(double PitchValue, - byte Channel) + byte Channel) { - - unsigned int pitchval = (PitchValue+1.f)*8192; - if (pitchval > 16383) pitchval = 16383; // overflow protection - sendPitchBend(pitchval,Channel); - + + unsigned int pitchval = (PitchValue+1.f)*8192; + if (pitchval > 16383) pitchval = 16383; // overflow protection + sendPitchBend(pitchval,Channel); + } /*! \brief Generate and send a System Exclusive frame. - \param length The size of the array to send - \param array The byte array containing the data to send + \param length The size of the array to send + \param array The byte array containing the data to send \param ArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes (start & stop SysEx) will NOT be sent (and therefore must be included in the array). default value is set to 'false' for compatibility with previous versions of the library. */ void MIDI_Class::sendSysEx(int length, - const byte *const array, - bool ArrayContainsBoundaries) + const byte *const array, + bool ArrayContainsBoundaries) { - - if (ArrayContainsBoundaries == false) { - - USE_SERIAL_PORT.write(0xF0); - - for (int i=0;i> 7) & 0x7F); - + + USE_SERIAL_PORT.write((byte)SongPosition); + USE_SERIAL_PORT.write(Beats & 0x7F); + USE_SERIAL_PORT.write((Beats >> 7) & 0x7F); + #if USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; + mRunningStatus_TX = InvalidType; #endif - + } /*! \brief Send a Song Select message */ void MIDI_Class::sendSongSelect(byte SongNumber) { - - USE_SERIAL_PORT.write((byte)SongSelect); - USE_SERIAL_PORT.write(SongNumber & 0x7F); - + + USE_SERIAL_PORT.write((byte)SongSelect); + USE_SERIAL_PORT.write(SongNumber & 0x7F); + #if USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; + mRunningStatus_TX = InvalidType; #endif - + } @@ -433,27 +433,27 @@ void MIDI_Class::sendSongSelect(byte SongNumber) */ void MIDI_Class::sendRealTime(kMIDIType Type) { - switch (Type) { - case TuneRequest: // Not really real-time, but one byte anyway. - case Clock: - case Start: - case Stop: - case Continue: - case ActiveSensing: - case SystemReset: - USE_SERIAL_PORT.write((byte)Type); - break; - default: - // Invalid Real Time marker - break; - } - - // Do not cancel Running Status for real-time messages as they can be interleaved within any message. - // Though, TuneRequest can be sent here, and as it is a System Common message, it must reset Running Status. + switch (Type) { + case TuneRequest: // Not really real-time, but one byte anyway. + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + USE_SERIAL_PORT.write((byte)Type); + break; + default: + // Invalid Real Time marker + break; + } + + // Do not cancel Running Status for real-time messages as they can be interleaved within any message. + // Though, TuneRequest can be sent here, and as it is a System Common message, it must reset Running Status. #if USE_RUNNING_STATUS - if (Type == TuneRequest) mRunningStatus_TX = InvalidType; + if (Type == TuneRequest) mRunningStatus_TX = InvalidType; #endif - + } #endif // COMPILE_MIDI_OUT @@ -470,360 +470,360 @@ void MIDI_Class::sendRealTime(kMIDIType Type) */ bool MIDI_Class::read() { - - return read(mInputChannel); - + + return read(mInputChannel); + } /*! \brief Reading/thru-ing method, the same as read() with a given input channel to read on. */ bool MIDI_Class::read(const byte inChannel) { - - if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. - - if (parse(inChannel)) { - - if (input_filter(inChannel)) { - + + if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. + + if (parse(inChannel)) { + + if (input_filter(inChannel)) { + #if (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) - thru_filter(inChannel); + thru_filter(inChannel); #endif - + #if USE_CALLBACKS - launchCallback(); + launchCallback(); #endif - - return true; - } - - } - - return false; - + + return true; + } + + } + + return false; + } // Private method: MIDI parser bool MIDI_Class::parse(byte inChannel) { - - const int bytes_available = USE_SERIAL_PORT.available(); - - if (bytes_available <= 0) { - // No data available. - return false; - } - - // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. - if (bytes_available == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; - USE_SERIAL_PORT.flush(); - } - else { - - /* Parsing algorithm: - Get a byte from the serial buffer. - * If there is no pending message to be recomposed, start a new one. - - Find type and channel (if pertinent) - - Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty. - * Else, add the extracted byte to the pending message, and check validity. When the message is done, store it. - */ - - - const byte extracted = USE_SERIAL_PORT.read(); - - if (mPendingMessageIndex == 0) { // Start a new pending message - mPendingMessage[0] = extracted; - - // Check for running status first - switch (getTypeFromStatusByte(mRunningStatus_RX)) { - // Only these types allow Running Status: - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - - // If the status byte is not received, prepend it to the pending message - if (extracted < 0x80) { - mPendingMessage[0] = mRunningStatus_RX; - mPendingMessage[1] = extracted; - mPendingMessageIndex = 1; - } - // Else: well, we received another status byte, so the running status does not apply here. - // It will be updated upon completion of this message. - - break; - - default: - // No running status - break; - } - - - switch (getTypeFromStatusByte(mPendingMessage[0])) { - - // 1 byte messages - case Start: - case Continue: - case Stop: - case Clock: - case ActiveSensing: - case SystemReset: - case TuneRequest: - // Handle the message type directly here. - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.valid = true; - - // \fix Running Status broken when receiving Clock messages. - // Do not reset all input attributes, Running Status must remain unchanged. - //reset_input_attributes(); - - // We still need to reset these - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - return true; - break; - - // 2 bytes messages - case ProgramChange: - case AfterTouchChannel: - case TimeCodeQuarterFrame: - case SongSelect: - mPendingMessageExpectedLenght = 2; - break; - - // 3 bytes messages - case NoteOn: - case NoteOff: - case ControlChange: - case PitchBend: - case AfterTouchPoly: - case SongPosition: - mPendingMessageExpectedLenght = 3; - break; - - case SystemExclusive: - mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes - mRunningStatus_RX = InvalidType; - break; - - case InvalidType: - default: - // This is obviously wrong. Let's get the hell out'a here. - reset_input_attributes(); - return false; - break; - } - - // Then update the index of the pending message. - mPendingMessageIndex++; - + + const int bytes_available = USE_SERIAL_PORT.available(); + + if (bytes_available <= 0) { + // No data available. + return false; + } + + // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. + if (bytes_available == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; + USE_SERIAL_PORT.flush(); + } + else { + + /* Parsing algorithm: + Get a byte from the serial buffer. + * If there is no pending message to be recomposed, start a new one. + - Find type and channel (if pertinent) + - Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty. + * Else, add the extracted byte to the pending message, and check validity. When the message is done, store it. + */ + + + const byte extracted = USE_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) { // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + switch (getTypeFromStatusByte(mRunningStatus_RX)) { + // Only these types allow Running Status: + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + + // If the status byte is not received, prepend it to the pending message + if (extracted < 0x80) { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, so the running status does not apply here. + // It will be updated upon completion of this message. + + break; + + default: + // No running status + break; + } + + + switch (getTypeFromStatusByte(mPendingMessage[0])) { + + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //reset_input_attributes(); + + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLenght = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLenght = 3; + break; + + case SystemExclusive: + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mRunningStatus_RX = InvalidType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + reset_input_attributes(); + return false; + break; + } + + // Then update the index of the pending message. + mPendingMessageIndex++; + #if USE_1BYTE_PARSING - // Message is not complete. - return false; + // Message is not complete. + return false; #else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); #endif - - } - else { - - // First, test if this is a status byte - if (extracted >= 0x80) { - - // Reception of status bytes in the middle of an uncompleted message - // are allowed only for interleaved Real Time message or EOX - switch (extracted) { - case Clock: - case Start: - case Continue: - case Stop: - case ActiveSensing: - case SystemReset: - - /* - This is tricky. Here we will have to extract the one-byte message, - pass it to the structure for being read outside the MIDI class, - and recompose the message it was interleaved into. - - Oh, and without killing the running status.. - - This is done by leaving the pending message as is, it will be completed on next calls. - */ - - mMessage.type = (kMIDIType)extracted; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.channel = 0; - mMessage.valid = true; - return true; - - break; - - // End of Exclusive - case 0xF7: - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - - // Store System Exclusive array in midimsg structure - for (byte i=0;i> 8; - - mMessage.channel = 0; - mMessage.valid = true; - - reset_input_attributes(); - - return true; - } - else { - // Well well well.. error. - reset_input_attributes(); - return false; - } - - break; - default: - break; - } - - - - } - - - // Add extracted data byte to pending message - mPendingMessage[mPendingMessageIndex] = extracted; - - - // Now we are going to check if we have reached the end of the message - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { - - // "FML" case: fall down here with an overflown SysEx.. - // This means we received the last possible data byte that can fit the buffer. - // If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - reset_input_attributes(); - return false; - } - - - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message - - mMessage.data1 = mPendingMessage[1]; - - // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; - else mMessage.data2 = 0; - - // Reset local variables - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = true; - - // Activate running status (if enabled for the received type) - switch (mMessage.type) { - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - // Running status enabled: store it from received message - mRunningStatus_RX = mPendingMessage[0]; - break; - - default: - // No running status - mRunningStatus_RX = InvalidType; - break; - } - return true; - } - else { - // Then update the index of the pending message. - mPendingMessageIndex++; - + + } + else { + + // First, test if this is a status byte + if (extracted >= 0x80) { + + // Reception of status bytes in the middle of an uncompleted message + // are allowed only for interleaved Real Time message or EOX + switch (extracted) { + case Clock: + case Start: + case Continue: + case Stop: + case ActiveSensing: + case SystemReset: + + /* + This is tricky. Here we will have to extract the one-byte message, + pass it to the structure for being read outside the MIDI class, + and recompose the message it was interleaved into. + + Oh, and without killing the running status.. + + This is done by leaving the pending message as is, it will be completed on next calls. + */ + + mMessage.type = (kMIDIType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xF7: + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + + // Store System Exclusive array in midimsg structure + for (byte i=0;i> 8; + + mMessage.channel = 0; + mMessage.valid = true; + + reset_input_attributes(); + + return true; + } + else { + // Well well well.. error. + reset_input_attributes(); + return false; + } + + break; + default: + break; + } + + + + } + + + // Add extracted data byte to pending message + mPendingMessage[mPendingMessageIndex] = extracted; + + + // Now we are going to check if we have reached the end of the message + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { + + // "FML" case: fall down here with an overflown SysEx.. + // This means we received the last possible data byte that can fit the buffer. + // If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + reset_input_attributes(); + return false; + } + + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; + else mMessage.data2 = 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) { + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; + break; + + default: + // No running status + mRunningStatus_RX = InvalidType; + break; + } + return true; + } + else { + // Then update the index of the pending message. + mPendingMessageIndex++; + #if USE_1BYTE_PARSING - // Message is not complete. - return false; + // Message is not complete. + return false; #else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); #endif - - } - - } - - } - - // What are our chances to fall here? - return false; + + } + + } + + } + + // What are our chances to fall here? + return false; } // Private method: check if the received message is on the listened channel bool MIDI_Class::input_filter(byte inChannel) { - - - // This method handles recognition of channel (to know if the message is destinated to the Arduino) - - - if (mMessage.type == InvalidType) return false; - - - // First, check if the received message is Channel - if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { - - // Then we need to know if we listen to it - if ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)) { - return true; - - } - else { - // We don't listen to this channel - return false; - } - - } - else { - - // System messages are always received - return true; - } - + + + // This method handles recognition of channel (to know if the message is destinated to the Arduino) + + + if (mMessage.type == InvalidType) return false; + + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { + + // Then we need to know if we listen to it + if ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)) { + return true; + + } + else { + // We don't listen to this channel + return false; + } + + } + else { + + // System messages are always received + return true; + } + } // Private method: reset input attributes void MIDI_Class::reset_input_attributes() { - - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - mRunningStatus_RX = InvalidType; - + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; + } @@ -834,9 +834,9 @@ void MIDI_Class::reset_input_attributes() */ kMIDIType MIDI_Class::getType() const { - - return mMessage.type; - + + return mMessage.type; + } @@ -846,27 +846,27 @@ kMIDIType MIDI_Class::getType() const */ byte MIDI_Class::getChannel() const { - - return mMessage.channel; - + + return mMessage.channel; + } /*! \brief Get the first data byte of the last received message. */ byte MIDI_Class::getData1() const { - - return mMessage.data1; - + + return mMessage.data1; + } /*! \brief Get the second data byte of the last received message. */ byte MIDI_Class::getData2() const { - - return mMessage.data2; - + + return mMessage.data2; + } @@ -876,9 +876,9 @@ byte MIDI_Class::getData2() const */ const byte * MIDI_Class::getSysExArray() const { - - return mMessage.sysex_array; - + + return mMessage.sysex_array; + } /*! \brief Get the lenght of the System Exclusive array. @@ -888,20 +888,20 @@ const byte * MIDI_Class::getSysExArray() const */ unsigned int MIDI_Class::getSysExArrayLength() const { - - unsigned int coded_size = ((unsigned int)(mMessage.data2) << 8) | mMessage.data1; - - return (coded_size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : coded_size; - + + unsigned int coded_size = ((unsigned int)(mMessage.data2) << 8) | mMessage.data1; + + return (coded_size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : coded_size; + } /*! \brief Check if a valid message is stored in the structure. */ bool MIDI_Class::check() const { - - return mMessage.valid; - + + return mMessage.valid; + } @@ -912,107 +912,107 @@ bool MIDI_Class::check() const */ void MIDI_Class::setInputChannel(const byte Channel) { - - mInputChannel = Channel; - + + mInputChannel = Channel; + } #if USE_CALLBACKS -void MIDI_Class::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } -void MIDI_Class::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } -void MIDI_Class::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } -void MIDI_Class::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } -void MIDI_Class::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } -void MIDI_Class::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } -void MIDI_Class::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } -void MIDI_Class::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } -void MIDI_Class::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } -void MIDI_Class::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } -void MIDI_Class::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } -void MIDI_Class::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } -void MIDI_Class::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } -void MIDI_Class::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } -void MIDI_Class::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } -void MIDI_Class::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } -void MIDI_Class::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } -void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } +void MIDI_Class::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } +void MIDI_Class::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } +void MIDI_Class::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } +void MIDI_Class::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } +void MIDI_Class::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } +void MIDI_Class::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } +void MIDI_Class::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } +void MIDI_Class::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } +void MIDI_Class::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } +void MIDI_Class::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } +void MIDI_Class::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } +void MIDI_Class::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } +void MIDI_Class::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } +void MIDI_Class::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +void MIDI_Class::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +void MIDI_Class::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +void MIDI_Class::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } /*! \brief Detach an external function from the given type. Use this method to cancel the effects of setHandle********. - \param Type The type of message to unbind. When a message of this type is received, no function will be called. + \param Type The type of message to unbind. When a message of this type is received, no function will be called. */ void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) { - - switch (Type) { - case NoteOff: mNoteOffCallback = NULL; break; - case NoteOn: mNoteOnCallback = NULL; break; - case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; - case ControlChange: mControlChangeCallback = NULL; break; - case ProgramChange: mProgramChangeCallback = NULL; break; - case AfterTouchChannel: mAfterTouchChannelCallback = NULL; break; - case PitchBend: mPitchBendCallback = NULL; break; - case SystemExclusive: mSystemExclusiveCallback = NULL; break; - case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = NULL; break; - case SongPosition: mSongPositionCallback = NULL; break; - case SongSelect: mSongSelectCallback = NULL; break; - case TuneRequest: mTuneRequestCallback = NULL; break; - case Clock: mClockCallback = NULL; break; - case Start: mStartCallback = NULL; break; - case Continue: mContinueCallback = NULL; break; - case Stop: mStopCallback = NULL; break; - case ActiveSensing: mActiveSensingCallback = NULL; break; - case SystemReset: mSystemResetCallback = NULL; break; - default: - break; - } - + + switch (Type) { + case NoteOff: mNoteOffCallback = NULL; break; + case NoteOn: mNoteOnCallback = NULL; break; + case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; + case ControlChange: mControlChangeCallback = NULL; break; + case ProgramChange: mProgramChangeCallback = NULL; break; + case AfterTouchChannel: mAfterTouchChannelCallback = NULL; break; + case PitchBend: mPitchBendCallback = NULL; break; + case SystemExclusive: mSystemExclusiveCallback = NULL; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = NULL; break; + case SongPosition: mSongPositionCallback = NULL; break; + case SongSelect: mSongSelectCallback = NULL; break; + case TuneRequest: mTuneRequestCallback = NULL; break; + case Clock: mClockCallback = NULL; break; + case Start: mStartCallback = NULL; break; + case Continue: mContinueCallback = NULL; break; + case Stop: mStopCallback = NULL; break; + case ActiveSensing: mActiveSensingCallback = NULL; break; + case SystemReset: mSystemResetCallback = NULL; break; + default: + break; + } + } // Private - launch callback function based on received type. void MIDI_Class::launchCallback() { - - // The order is mixed to allow frequent messages to trigger their callback faster. - - switch (mMessage.type) { - // Notes - case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - - // Real-time messages - case Clock: if (mClockCallback != NULL) mClockCallback(); break; - case Start: if (mStartCallback != NULL) mStartCallback(); break; - case Continue: if (mContinueCallback != NULL) mContinueCallback(); break; - case Stop: if (mStopCallback != NULL) mStopCallback(); break; - case ActiveSensing: if (mActiveSensingCallback != NULL) mActiveSensingCallback(); break; - - // Continuous controllers - case ControlChange: if (mControlChangeCallback != NULL) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) - 8192); break; // TODO: check this - case AfterTouchPoly: if (mAfterTouchPolyCallback != NULL) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case AfterTouchChannel: if (mAfterTouchChannelCallback != NULL) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; - - case ProgramChange: if (mProgramChangeCallback != NULL) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; - case SystemExclusive: if (mSystemExclusiveCallback != NULL) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; - - // Occasional messages - case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != NULL) mTimeCodeQuarterFrameCallback(mMessage.data1); break; - case SongPosition: if (mSongPositionCallback != NULL) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; - case SongSelect: if (mSongSelectCallback != NULL) mSongSelectCallback(mMessage.data1); break; - case TuneRequest: if (mTuneRequestCallback != NULL) mTuneRequestCallback(); break; - - case SystemReset: if (mSystemResetCallback != NULL) mSystemResetCallback(); break; - case InvalidType: - default: - break; - } - + + // The order is mixed to allow frequent messages to trigger their callback faster. + + switch (mMessage.type) { + // Notes + case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != NULL) mClockCallback(); break; + case Start: if (mStartCallback != NULL) mStartCallback(); break; + case Continue: if (mContinueCallback != NULL) mContinueCallback(); break; + case Stop: if (mStopCallback != NULL) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != NULL) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != NULL) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) - 8192); break; // TODO: check this + case AfterTouchPoly: if (mAfterTouchPolyCallback != NULL) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != NULL) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; + + case ProgramChange: if (mProgramChangeCallback != NULL) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != NULL) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; + + // Occasional messages + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != NULL) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != NULL) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; + case SongSelect: if (mSongSelectCallback != NULL) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != NULL) mTuneRequestCallback(); break; + + case SystemReset: if (mSystemResetCallback != NULL) mSystemResetCallback(); break; + case InvalidType: + default: + break; + } + } @@ -1033,126 +1033,126 @@ void MIDI_Class::launchCallback() */ void MIDI_Class::setThruFilterMode(kThruFilterMode inThruFilterMode) { - - mThruFilterMode = inThruFilterMode; - if (mThruFilterMode != Off) mThruActivated = true; - else mThruActivated = false; - + + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) mThruActivated = true; + else mThruActivated = false; + } /*! \brief Setter method: turn message mirroring on. */ void MIDI_Class::turnThruOn(kThruFilterMode inThruFilterMode) { - - mThruActivated = true; - mThruFilterMode = inThruFilterMode; - + + mThruActivated = true; + mThruFilterMode = inThruFilterMode; + } /*! \brief Setter method: turn message mirroring off. */ void MIDI_Class::turnThruOff() { - - mThruActivated = false; - mThruFilterMode = Off; - + + mThruActivated = false; + mThruFilterMode = Off; + } // This method is called upon reception of a message and takes care of Thru filtering and sending. void MIDI_Class::thru_filter(byte inChannel) { - - /* - This method handles Soft-Thru filtering. - - Soft-Thru filtering: - - All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off - - Channel messages are passed to the output whether their channel is matching the input channel and the filter setting - - */ - - // If the feature is disabled, don't do anything. - if (!mThruActivated || (mThruFilterMode == Off)) return; - - - // First, check if the received message is Channel - if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { - - const bool filter_condition = ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)); - - // Now let's pass it to the output - switch (mThruFilterMode) { - case Full: - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); - return; - break; - case SameChannel: - if (filter_condition) { - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); - return; - } - break; - case DifferentChannel: - if (!filter_condition) { - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); - return; - } - break; - case Off: - // Do nothing. - // Technically it's impossible to get there because the case was already tested earlier. - break; - default: - break; - } - - } - else { - - // Send the message to the output - switch (mMessage.type) { - // Real Time and 1 byte - case Clock: - case Start: - case Stop: - case Continue: - case ActiveSensing: - case SystemReset: - case TuneRequest: - sendRealTime(mMessage.type); - return; - break; - - case SystemExclusive: - // Send SysEx (0xF0 and 0xF7 are included in the buffer) - sendSysEx(mMessage.data1,mMessage.sysex_array,true); - return; - break; - - case SongSelect: - sendSongSelect(mMessage.data1); - return; - break; - - case SongPosition: - sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7)); - return; - break; - - case TimeCodeQuarterFrame: - sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); - return; - break; - default: - break; - - } - - } - + + /* + This method handles Soft-Thru filtering. + + Soft-Thru filtering: + - All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off + - Channel messages are passed to the output whether their channel is matching the input channel and the filter setting + + */ + + // If the feature is disabled, don't do anything. + if (!mThruActivated || (mThruFilterMode == Off)) return; + + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { + + const bool filter_condition = ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)); + + // Now let's pass it to the output + switch (mThruFilterMode) { + case Full: + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + break; + case SameChannel: + if (filter_condition) { + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + } + break; + case DifferentChannel: + if (!filter_condition) { + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + } + break; + case Off: + // Do nothing. + // Technically it's impossible to get there because the case was already tested earlier. + break; + default: + break; + } + + } + else { + + // Send the message to the output + switch (mMessage.type) { + // Real Time and 1 byte + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + case TuneRequest: + sendRealTime(mMessage.type); + return; + break; + + case SystemExclusive: + // Send SysEx (0xF0 and 0xF7 are included in the buffer) + sendSysEx(mMessage.data1,mMessage.sysex_array,true); + return; + break; + + case SongSelect: + sendSongSelect(mMessage.data1); + return; + break; + + case SongPosition: + sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7)); + return; + break; + + case TimeCodeQuarterFrame: + sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); + return; + break; + default: + break; + + } + + } + } diff --git a/src/MIDI.h b/src/MIDI.h index ff20f8a..d1c4ef8 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -1,17 +1,17 @@ /*! - * @file MIDI.h - * Project MIDI Library - * @brief MIDI Library for the Arduino - * Version 3.2 - * @author Francois Best - * @date 24/02/11 - * License GPL Forty Seven Effects - 2011 + * @file MIDI.h + * Project AVR Core MIDI Library + * @brief MIDI Library for the AVR Core + * @version 3.2 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 */ #ifndef LIB_MIDI_H_ #define LIB_MIDI_H_ -#include "Types.h" // Include all the types we need. +#include "Types.h" // Include all the types we need. /* @@ -38,7 +38,7 @@ // to use a different serial port for MIDI I/O. -#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values +#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values // of the same type and channel. // Set to 0 if you have troubles with controlling you hardware. @@ -53,259 +53,259 @@ // (do not modify anything under this line unless you know what you are doing) -#define MIDI_BAUDRATE 31250 +#define MIDI_BAUDRATE 31250 -#define MIDI_CHANNEL_OMNI 0 -#define MIDI_CHANNEL_OFF 17 // and over +#define MIDI_CHANNEL_OMNI 0 +#define MIDI_CHANNEL_OFF 17 // and over -#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. +#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. /*! Enumeration of MIDI types */ enum kMIDIType { - NoteOff = 0x80, ///< Note Off - NoteOn = 0x90, ///< Note On - AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch - ControlChange = 0xB0, ///< Control Change / Channel Mode - ProgramChange = 0xC0, ///< Program Change - AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch - PitchBend = 0xE0, ///< Pitch Bend - SystemExclusive = 0xF0, ///< System Exclusive - TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame - SongPosition = 0xF2, ///< System Common - Song Position Pointer - SongSelect = 0xF3, ///< System Common - Song Select - TuneRequest = 0xF6, ///< System Common - Tune Request - Clock = 0xF8, ///< System Real Time - Timing Clock - Start = 0xFA, ///< System Real Time - Start - Continue = 0xFB, ///< System Real Time - Continue - Stop = 0xFC, ///< System Real Time - Stop - ActiveSensing = 0xFE, ///< System Real Time - Active Sensing - SystemReset = 0xFF, ///< System Real Time - System Reset - InvalidType = 0x00 ///< For notifying errors + NoteOff = 0x80, ///< Note Off + NoteOn = 0x90, ///< Note On + AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch + ControlChange = 0xB0, ///< Control Change / Channel Mode + ProgramChange = 0xC0, ///< Program Change + AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch + PitchBend = 0xE0, ///< Pitch Bend + SystemExclusive = 0xF0, ///< System Exclusive + TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame + SongPosition = 0xF2, ///< System Common - Song Position Pointer + SongSelect = 0xF3, ///< System Common - Song Select + TuneRequest = 0xF6, ///< System Common - Tune Request + Clock = 0xF8, ///< System Real Time - Timing Clock + Start = 0xFA, ///< System Real Time - Start + Continue = 0xFB, ///< System Real Time - Continue + Stop = 0xFC, ///< System Real Time - Stop + ActiveSensing = 0xFE, ///< System Real Time - Active Sensing + SystemReset = 0xFF, ///< System Real Time - System Reset + InvalidType = 0x00 ///< For notifying errors }; /*! Enumeration of Thru filter modes */ enum kThruFilterMode { - Off = 0, ///< Thru disabled (nothing passes through). - Full = 1, ///< Fully enabled Thru (every incoming message is sent back). - SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. - DifferentChannel = 3 ///< All the messages but the ones on the Input Channel will be sent back. + Off = 0, ///< Thru disabled (nothing passes through). + Full = 1, ///< Fully enabled Thru (every incoming message is sent back). + SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. + DifferentChannel = 3 ///< All the messages but the ones on the Input Channel will be sent back. }; /*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). \n */ struct midimsg { - /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ - byte channel; - /*! The type of the message (see the define section for types reference) */ - kMIDIType type; - /*! The first data byte.\n Value goes from 0 to 127.\n */ - byte data1; - /*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */ - byte data2; - /*! System Exclusive dedicated byte array. \n Array length is stocked on 16 bits, in data1 (LSB) and data2 (MSB) */ - byte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; - /*! This boolean indicates if the message is valid or not. There is no channel consideration here, validity means the message respects the MIDI norm. */ - bool valid; + /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ + byte channel; + /*! The type of the message (see the define section for types reference) */ + kMIDIType type; + /*! The first data byte.\n Value goes from 0 to 127.\n */ + byte data1; + /*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */ + byte data2; + /*! System Exclusive dedicated byte array. \n Array length is stocked on 16 bits, in data1 (LSB) and data2 (MSB) */ + byte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; + /*! This boolean indicates if the message is valid or not. There is no channel consideration here, validity means the message respects the MIDI norm. */ + bool valid; }; /*! \brief The main class for MIDI handling.\n - See member descriptions to know how to use it, - or check out the examples supplied with the library. + See member descriptions to know how to use it, + or check out the examples supplied with the library. */ class MIDI_Class { - - + + public: - // Constructor and Destructor - MIDI_Class(); - ~MIDI_Class(); - - - void begin(const byte inChannel = 1); - - - - -/* ####### OUTPUT COMPILATION BLOCK ####### */ + // Constructor and Destructor + MIDI_Class(); + ~MIDI_Class(); + + + void begin(const byte inChannel = 1); + + + + + /* ####### OUTPUT COMPILATION BLOCK ####### */ #if COMPILE_MIDI_OUT - -public: - - void sendNoteOn(byte NoteNumber,byte Velocity,byte Channel); - void sendNoteOff(byte NoteNumber,byte Velocity,byte Channel); - void sendProgramChange(byte ProgramNumber,byte Channel); - void sendControlChange(byte ControlNumber, byte ControlValue,byte Channel); - void sendPitchBend(int PitchValue,byte Channel); - void sendPitchBend(unsigned int PitchValue,byte Channel); - void sendPitchBend(double PitchValue,byte Channel); - void sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel); - void sendAfterTouch(byte Pressure,byte Channel); - void sendSysEx(int length, const byte *const array,bool ArrayContainsBoundaries = false); - void sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble); - void sendTimeCodeQuarterFrame(byte data); - void sendSongPosition(unsigned int Beats); - void sendSongSelect(byte SongNumber); - void sendTuneRequest(); - void sendRealTime(kMIDIType Type); - - void send(kMIDIType type, byte param1, byte param2, byte channel); - + +public: + + void sendNoteOn(byte NoteNumber,byte Velocity,byte Channel); + void sendNoteOff(byte NoteNumber,byte Velocity,byte Channel); + void sendProgramChange(byte ProgramNumber,byte Channel); + void sendControlChange(byte ControlNumber, byte ControlValue,byte Channel); + void sendPitchBend(int PitchValue,byte Channel); + void sendPitchBend(unsigned int PitchValue,byte Channel); + void sendPitchBend(double PitchValue,byte Channel); + void sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel); + void sendAfterTouch(byte Pressure,byte Channel); + void sendSysEx(int length, const byte *const array,bool ArrayContainsBoundaries = false); + void sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble); + void sendTimeCodeQuarterFrame(byte data); + void sendSongPosition(unsigned int Beats); + void sendSongSelect(byte SongNumber); + void sendTuneRequest(); + void sendRealTime(kMIDIType Type); + + void send(kMIDIType type, byte param1, byte param2, byte channel); + private: - - const byte genstatus(const kMIDIType inType,const byte inChannel) const; - - - // Attributes + + const byte genstatus(const kMIDIType inType,const byte inChannel) const; + + + // Attributes #if USE_RUNNING_STATUS - byte mRunningStatus_TX; + byte mRunningStatus_TX; #endif // USE_RUNNING_STATUS - -#endif // COMPILE_MIDI_OUT - - - -/* ####### INPUT COMPILATION BLOCK ####### */ -#if COMPILE_MIDI_IN - + +#endif // COMPILE_MIDI_OUT + + + + /* ####### INPUT COMPILATION BLOCK ####### */ +#if COMPILE_MIDI_IN + public: - - bool read(); - bool read(const byte Channel); - - // Getters - kMIDIType getType() const; - byte getChannel() const; - byte getData1() const; - byte getData2() const; - const byte * getSysExArray() const; - unsigned int getSysExArrayLength() const; - bool check() const; - - byte getInputChannel() const + + bool read(); + bool read(const byte Channel); + + // Getters + kMIDIType getType() const; + byte getChannel() const; + byte getData1() const; + byte getData2() const; + const byte * getSysExArray() const; + unsigned int getSysExArrayLength() const; + bool check() const; + + byte getInputChannel() const { return mInputChannel; } - - // Setters - void setInputChannel(const byte Channel); - - /*! \brief Extract an enumerated MIDI type from a status byte. - - This is a utility static method, used internally, made public so you can handle kMIDITypes more easily. - */ - static inline const kMIDIType getTypeFromStatusByte(const byte inStatus) + + // Setters + void setInputChannel(const byte Channel); + + /*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, made public so you can handle kMIDITypes more easily. + */ + static inline const kMIDIType getTypeFromStatusByte(const byte inStatus) { - if ((inStatus < 0x80) - || (inStatus == 0xF4) - || (inStatus == 0xF5) - || (inStatus == 0xF9) - || (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. - if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble. - else return (kMIDIType)inStatus; - } - - + if ((inStatus < 0x80) + || (inStatus == 0xF4) + || (inStatus == 0xF5) + || (inStatus == 0xF9) + || (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. + if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble. + else return (kMIDIType)inStatus; + } + + #if USE_CALLBACKS - - void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); - void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); - void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); - void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); - void setHandleProgramChange(void (*fptr)(byte channel, byte number)); - void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); - void setHandlePitchBend(void (*fptr)(byte channel, int bend)); - void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); - void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); - void setHandleSongPosition(void (*fptr)(unsigned int beats)); - void setHandleSongSelect(void (*fptr)(byte songnumber)); - void setHandleTuneRequest(void (*fptr)(void)); - void setHandleClock(void (*fptr)(void)); - void setHandleStart(void (*fptr)(void)); - void setHandleContinue(void (*fptr)(void)); - void setHandleStop(void (*fptr)(void)); - void setHandleActiveSensing(void (*fptr)(void)); - void setHandleSystemReset(void (*fptr)(void)); - - void disconnectCallbackFromType(kMIDIType Type); - + + void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); + void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); + void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); + void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); + void setHandleProgramChange(void (*fptr)(byte channel, byte number)); + void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); + void setHandlePitchBend(void (*fptr)(byte channel, int bend)); + void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); + void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); + void setHandleSongPosition(void (*fptr)(unsigned int beats)); + void setHandleSongSelect(void (*fptr)(byte songnumber)); + void setHandleTuneRequest(void (*fptr)(void)); + void setHandleClock(void (*fptr)(void)); + void setHandleStart(void (*fptr)(void)); + void setHandleContinue(void (*fptr)(void)); + void setHandleStop(void (*fptr)(void)); + void setHandleActiveSensing(void (*fptr)(void)); + void setHandleSystemReset(void (*fptr)(void)); + + void disconnectCallbackFromType(kMIDIType Type); + #endif // USE_CALLBACKS - - + + private: - - bool input_filter(byte inChannel); - bool parse(byte inChannel); - void reset_input_attributes(); - - // Attributes - byte mRunningStatus_RX; - byte mInputChannel; - - byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; - unsigned int mPendingMessageExpectedLenght; - unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. - - midimsg mMessage; - + + bool input_filter(byte inChannel); + bool parse(byte inChannel); + void reset_input_attributes(); + + // Attributes + byte mRunningStatus_RX; + byte mInputChannel; + + byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; + unsigned int mPendingMessageExpectedLenght; + unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. + + midimsg mMessage; + #if USE_CALLBACKS - - void launchCallback(); - - void (*mNoteOffCallback)(byte channel, byte note, byte velocity); - void (*mNoteOnCallback)(byte channel, byte note, byte velocity); - void (*mAfterTouchPolyCallback)(byte channel, byte note, byte velocity); - void (*mControlChangeCallback)(byte channel, byte, byte); - void (*mProgramChangeCallback)(byte channel, byte); - void (*mAfterTouchChannelCallback)(byte channel, byte); - void (*mPitchBendCallback)(byte channel, int); - void (*mSystemExclusiveCallback)(byte * array, byte size); - void (*mTimeCodeQuarterFrameCallback)(byte data); - void (*mSongPositionCallback)(unsigned int beats); - void (*mSongSelectCallback)(byte songnumber); - void (*mTuneRequestCallback)(void); - void (*mClockCallback)(void); - void (*mStartCallback)(void); - void (*mContinueCallback)(void); - void (*mStopCallback)(void); - void (*mActiveSensingCallback)(void); - void (*mSystemResetCallback)(void); - + + void launchCallback(); + + void (*mNoteOffCallback)(byte channel, byte note, byte velocity); + void (*mNoteOnCallback)(byte channel, byte note, byte velocity); + void (*mAfterTouchPolyCallback)(byte channel, byte note, byte velocity); + void (*mControlChangeCallback)(byte channel, byte, byte); + void (*mProgramChangeCallback)(byte channel, byte); + void (*mAfterTouchChannelCallback)(byte channel, byte); + void (*mPitchBendCallback)(byte channel, int); + void (*mSystemExclusiveCallback)(byte * array, byte size); + void (*mTimeCodeQuarterFrameCallback)(byte data); + void (*mSongPositionCallback)(unsigned int beats); + void (*mSongSelectCallback)(byte songnumber); + void (*mTuneRequestCallback)(void); + void (*mClockCallback)(void); + void (*mStartCallback)(void); + void (*mContinueCallback)(void); + void (*mStopCallback)(void); + void (*mActiveSensingCallback)(void); + void (*mSystemResetCallback)(void); + #endif // USE_CALLBACKS - - + + #endif // COMPILE_MIDI_IN - - -/* ####### THRU COMPILATION BLOCK ####### */ + + + /* ####### THRU COMPILATION BLOCK ####### */ #if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru - + public: - - // Getters - kThruFilterMode getFilterMode() const { return mThruFilterMode; } - bool getThruState() const { return mThruActivated; } - - - // Setters - void turnThruOn(kThruFilterMode inThruFilterMode = Full); - void turnThruOff(); - - void setThruFilterMode(const kThruFilterMode inThruFilterMode); - - + + // Getters + kThruFilterMode getFilterMode() const { return mThruFilterMode; } + bool getThruState() const { return mThruActivated; } + + + // Setters + void turnThruOn(kThruFilterMode inThruFilterMode = Full); + void turnThruOff(); + + void setThruFilterMode(const kThruFilterMode inThruFilterMode); + + private: - - void thru_filter(byte inChannel); - - bool mThruActivated; - kThruFilterMode mThruFilterMode; - + + void thru_filter(byte inChannel); + + bool mThruActivated; + kThruFilterMode mThruFilterMode; + #endif // Thru - + }; extern MIDI_Class MIDI; From 698889dd6ea011fca59424b161f9eae1a09071a8 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 22:34:15 +0200 Subject: [PATCH 06/38] 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 07/38] 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 08/38] 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 From a1bea899ce5f3c08adfe428f880fcaa777771de9 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 23:33:58 +0200 Subject: [PATCH 09/38] Changed macro name. --- src/MIDI.cpp | 40 ++++++++++++++++++++-------------------- src/MIDI.h | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 71fec37..28f8b19 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -68,7 +68,7 @@ void MIDI_Class::begin(const byte inChannel) { // Initialise the Serial port - USE_SERIAL_PORT.begin(MIDI_BAUDRATE); + MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); #if COMPILE_MIDI_OUT @@ -158,17 +158,17 @@ void MIDI_Class::send(kMIDIType type, if (mRunningStatus_TX != statusbyte) { // New message, memorise and send header mRunningStatus_TX = statusbyte; - USE_SERIAL_PORT.write(mRunningStatus_TX); + MIDI_SERIAL_PORT.write(mRunningStatus_TX); } #else // Don't care about running status, send the Control byte. - USE_SERIAL_PORT.write(statusbyte); + MIDI_SERIAL_PORT.write(statusbyte); #endif // Then send data - USE_SERIAL_PORT.write(data1); + MIDI_SERIAL_PORT.write(data1); if (type != ProgramChange && type != AfterTouchChannel) { - USE_SERIAL_PORT.write(data2); + MIDI_SERIAL_PORT.write(data2); } return; } @@ -321,22 +321,22 @@ void MIDI_Class::sendSysEx(int length, if (ArrayContainsBoundaries == false) { - USE_SERIAL_PORT.write(0xF0); + MIDI_SERIAL_PORT.write(0xF0); for (int i=0;i> 7) & 0x7F); + MIDI_SERIAL_PORT.write((byte)SongPosition); + MIDI_SERIAL_PORT.write(Beats & 0x7F); + MIDI_SERIAL_PORT.write((Beats >> 7) & 0x7F); #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; @@ -415,8 +415,8 @@ void MIDI_Class::sendSongPosition(unsigned int Beats) void MIDI_Class::sendSongSelect(byte SongNumber) { - USE_SERIAL_PORT.write((byte)SongSelect); - USE_SERIAL_PORT.write(SongNumber & 0x7F); + MIDI_SERIAL_PORT.write((byte)SongSelect); + MIDI_SERIAL_PORT.write(SongNumber & 0x7F); #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; @@ -441,7 +441,7 @@ void MIDI_Class::sendRealTime(kMIDIType Type) case Continue: case ActiveSensing: case SystemReset: - USE_SERIAL_PORT.write((byte)Type); + MIDI_SERIAL_PORT.write((byte)Type); break; default: // Invalid Real Time marker @@ -508,7 +508,7 @@ bool MIDI_Class::read(const byte inChannel) bool MIDI_Class::parse(byte inChannel) { - const int bytes_available = USE_SERIAL_PORT.available(); + const int bytes_available = MIDI_SERIAL_PORT.available(); if (bytes_available <= 0) { // No data available. @@ -517,7 +517,7 @@ bool MIDI_Class::parse(byte inChannel) // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. if (bytes_available == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; - USE_SERIAL_PORT.flush(); + MIDI_SERIAL_PORT.flush(); } else { @@ -530,7 +530,7 @@ bool MIDI_Class::parse(byte inChannel) */ - const byte extracted = USE_SERIAL_PORT.read(); + const byte extracted = MIDI_SERIAL_PORT.read(); if (mPendingMessageIndex == 0) { // Start a new pending message mPendingMessage[0] = extracted; diff --git a/src/MIDI.h b/src/MIDI.h index d1c4ef8..6fb3184 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -34,7 +34,7 @@ // Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. -#define USE_SERIAL_PORT Serial1 // Change the number (to Serial1 for example) if you want +#define MIDI_SERIAL_PORT Serial1 // Change the number (to Serial1 for example) if you want // to use a different serial port for MIDI I/O. From eadffc8222b3659476fb4689b624e8d267932cf2 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 22 May 2012 23:40:08 +0200 Subject: [PATCH 10/38] Applied cosmetic patch from Arduino branch. --- src/MIDI.cpp | 125 ++++++++++++++++++++++++++++++++++----------------- src/MIDI.h | 124 +++++++++++++++++++++++++++++++------------------- 2 files changed, 162 insertions(+), 87 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 28f8b19..7ffc5b0 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -108,6 +108,10 @@ void MIDI_Class::begin(const byte inChannel) } +// ============================================================================= +// MIDI Output +// ============================================================================= + #if COMPILE_MIDI_OUT // Private method for generating a status byte from channel and type @@ -122,11 +126,14 @@ const byte MIDI_Class::genstatus(const kMIDIType inType, /*! \brief Generate and send a MIDI message from the values given. \param type The message type (see type defines for reference) - \param data1 The first data byte. - \param data2 The second data byte (if the message contains only 1 data byte, set this one to 0). - \param channel The output channel on which the message will be sent (values from 1 to 16). Note: you cannot send to OMNI. + \param data1 The first data byte. + \param data2 The second data byte (if the message contains only 1 data byte, + set this one to 0). + \param channel The output channel on which the message will be sent + (values from 1 to 16). Note: you cannot send to OMNI. - This is an internal method, use it only if you need to send raw data from your code, at your own risks. + This is an internal method, use it only if you need to send raw data + from your code, at your own risks. */ void MIDI_Class::send(kMIDIType type, byte data1, @@ -181,9 +188,13 @@ void MIDI_Class::send(kMIDIType type, /*! \brief Send a Note On message - \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n - \param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff. - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber Pitch value in the MIDI format (0 to 127). + \param Velocity Note attack velocity (0 to 127). A + NoteOn with 0 velocity is considered as a NoteOff. + \param Channel The channel on which the message will be sent (1 to 16). + + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html */ void MIDI_Class::sendNoteOn(byte NoteNumber, byte Velocity, @@ -196,9 +207,12 @@ void MIDI_Class::sendNoteOn(byte NoteNumber, /*! \brief Send a Note Off message (a real Note Off, not a Note On with null velocity) - \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param NoteNumber Pitch value in the MIDI format (0 to 127). \param Velocity Release velocity (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param Channel The channel on which the message will be sent (1 to 16). + + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html */ void MIDI_Class::sendNoteOff(byte NoteNumber, byte Velocity, @@ -211,8 +225,8 @@ void MIDI_Class::sendNoteOff(byte NoteNumber, /*! \brief Send a Program Change message - \param ProgramNumber The Program to select (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param ProgramNumber The Program to select (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendProgramChange(byte ProgramNumber, byte Channel) @@ -224,9 +238,12 @@ void MIDI_Class::sendProgramChange(byte ProgramNumber, /*! \brief Send a Control Change message - \param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums + \param ControlNumber The controller number (0 to 127). \param ControlValue The value for the specified controller (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param Channel The channel on which the message will be sent (1 to 16). + + See the detailed controllers numbers & description here: + http://www.somascape.org/midi/tech/spec.html#ctrlnums */ void MIDI_Class::sendControlChange(byte ControlNumber, byte ControlValue, @@ -239,9 +256,9 @@ void MIDI_Class::sendControlChange(byte ControlNumber, /*! \brief Send a Polyphonic AfterTouch message (applies to only one specified note) - \param NoteNumber The note to apply AfterTouch to (0 to 127). - \param Pressure The amount of AfterTouch to apply (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). + \param NoteNumber The note to apply AfterTouch to (0 to 127). + \param Pressure The amount of AfterTouch to apply (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPolyPressure(byte NoteNumber, byte Pressure, @@ -254,8 +271,8 @@ void MIDI_Class::sendPolyPressure(byte NoteNumber, /*! \brief Send a MonoPhonic AfterTouch message (applies to all notes) - \param Pressure The amount of AfterTouch to apply to all notes. - \param Channel The channel on which the message will be sent (1 to 16). + \param Pressure The amount of AfterTouch to apply to all notes. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendAfterTouch(byte Pressure, byte Channel) @@ -267,8 +284,10 @@ void MIDI_Class::sendAfterTouch(byte Pressure, /*! \brief Send a Pitch Bend message using a signed integer value. - \param PitchValue The amount of bend to send (in a signed integer format), between -8192 (maximum downwards bend) and 8191 (max upwards bend), center value is 0. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a signed integer format), + between -8192 (maximum downwards bend) + and 8191 (max upwards bend), center value is 0. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(int PitchValue, byte Channel) @@ -281,8 +300,10 @@ void MIDI_Class::sendPitchBend(int PitchValue, /*! \brief Send a Pitch Bend message using an unsigned integer value. - \param PitchValue The amount of bend to send (in a signed integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a signed integer format), + between 0 (maximum downwards bend) + and 16383 (max upwards bend), center value is 8192. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(unsigned int PitchValue, byte Channel) @@ -294,8 +315,10 @@ void MIDI_Class::sendPitchBend(unsigned int PitchValue, /*! \brief Send a Pitch Bend message using a floating point value. - \param PitchValue The amount of bend to send (in a floating point format), between -1.0f (maximum downwards bend) and +1.0f (max upwards bend), center value is 0.0f. - \param Channel The channel on which the message will be sent (1 to 16). + \param PitchValue The amount of bend to send (in a floating point format), + between -1.0f (maximum downwards bend) + and +1.0f (max upwards bend), center value is 0.0f. + \param Channel The channel on which the message will be sent (1 to 16). */ void MIDI_Class::sendPitchBend(double PitchValue, byte Channel) @@ -309,10 +332,13 @@ void MIDI_Class::sendPitchBend(double PitchValue, /*! \brief Generate and send a System Exclusive frame. - \param length The size of the array to send - \param array The byte array containing the data to send - \param ArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes (start & stop SysEx) will NOT be sent (and therefore must be included in the array). - default value is set to 'false' for compatibility with previous versions of the library. + \param length The size of the array to send + \param array The byte array containing the data to send + \param ArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes + (start & stop SysEx) will NOT be sent + (and therefore must be included in the array). + default value for ArrayContainsBoundaries is set to 'false' for compatibility + with previous versions of the library. */ void MIDI_Class::sendSysEx(int length, const byte *const array, @@ -351,7 +377,8 @@ void MIDI_Class::sendSysEx(int length, /*! \brief Send a Tune Request message. - When a MIDI unit receives this message, it should tune its oscillators (if equipped with any) + When a MIDI unit receives this message, + it should tune its oscillators (if equipped with any). */ void MIDI_Class::sendTuneRequest() { @@ -363,9 +390,9 @@ void MIDI_Class::sendTuneRequest() /*! \brief Send a MIDI Time Code Quarter Frame. - See MIDI Specification for more information. - \param TypeNibble MTC type + \param TypeNibble MTC type \param ValuesNibble MTC data + See MIDI Specification for more information. */ void MIDI_Class::sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble) { @@ -427,7 +454,8 @@ void MIDI_Class::sendSongSelect(byte SongNumber) /*! \brief Send a Real Time (one byte) message. - \param Type The available Real Time types are: Start, Stop, Continue, Clock, ActiveSensing and SystemReset. + \param Type The available Real Time types are: + Start, Stop, Continue, Clock, ActiveSensing and SystemReset. You can also send a Tune Request with this method. @see kMIDIType */ @@ -459,14 +487,19 @@ void MIDI_Class::sendRealTime(kMIDIType Type) #endif // COMPILE_MIDI_OUT +// ============================================================================= +// MIDI Input +// ============================================================================= #if COMPILE_MIDI_IN -/*! \brief Read a MIDI message from the serial port using the main input channel (see setInputChannel() for reference). +/*! \brief Read a MIDI message from the serial port + using the main input channel (see setInputChannel() for reference). - Returned value: true if any valid message has been stored in the structure, false if not. + \return True if a valid message has been stored in the structure, false if not. A valid message is a message that matches the input channel. \n\n - If the Thru is enabled and the messages matches the filter, it is sent back on the MIDI output. + If the Thru is enabled and the messages matches the filter, + it is sent back on the MIDI output. */ bool MIDI_Class::read() { @@ -476,7 +509,9 @@ bool MIDI_Class::read() } -/*! \brief Reading/thru-ing method, the same as read() with a given input channel to read on. */ +/*! \brief Reading/thru-ing method, the same as read() + with a given input channel to read on. + */ bool MIDI_Class::read(const byte inChannel) { @@ -842,7 +877,8 @@ kMIDIType MIDI_Class::getType() const /*! \brief Get the channel of the message stored in the structure. - Channel range is 1 to 16. For non-channel messages, this will return 0. + \return Channel range is 1 to 16. + For non-channel messages, this will return 0. */ byte MIDI_Class::getChannel() const { @@ -908,7 +944,8 @@ bool MIDI_Class::check() const // Setters /*! \brief Set the value for the input MIDI channel \param Channel the channel value. Valid values are 1 to 16, - MIDI_CHANNEL_OMNI if you want to listen to all channels, and MIDI_CHANNEL_OFF to disable MIDI input. + MIDI_CHANNEL_OMNI if you want to listen to all channels, + and MIDI_CHANNEL_OFF to disable MIDI input. */ void MIDI_Class::setInputChannel(const byte Channel) { @@ -943,7 +980,8 @@ void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) /*! \brief Detach an external function from the given type. Use this method to cancel the effects of setHandle********. - \param Type The type of message to unbind. When a message of this type is received, no function will be called. + \param Type The type of message to unbind. + When a message of this type is received, no function will be called. */ void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) { @@ -1022,9 +1060,11 @@ void MIDI_Class::launchCallback() #endif // COMPILE_MIDI_IN +// ============================================================================= +// MIDI Soft Thru +// ============================================================================= - -#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) /*! \brief Set the filter for thru mirroring \param inThruFilterMode a filter mode @@ -1061,7 +1101,8 @@ void MIDI_Class::turnThruOff() } -// This method is called upon reception of a message and takes care of Thru filtering and sending. +// This method is called upon reception of a message +// and takes care of Thru filtering and sending. void MIDI_Class::thru_filter(byte inChannel) { diff --git a/src/MIDI.h b/src/MIDI.h index 6fb3184..4a375bd 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -93,20 +93,46 @@ enum kThruFilterMode { }; -/*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). \n */ -struct midimsg { - /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ +/*! The midimsg structure contains decoded data + of a MIDI message read from the serial port + with read() or thru(). + */ +struct midimsg +{ + + /*! The MIDI channel on which the message was recieved. + \n Value goes from 1 to 16. + */ byte channel; - /*! The type of the message (see the define section for types reference) */ + + /*! The type of the message + (see the define section for types reference) + */ kMIDIType type; - /*! The first data byte.\n Value goes from 0 to 127.\n */ + + /*! The first data byte. + \n Value goes from 0 to 127. + */ byte data1; - /*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */ + + /*! The second data byte. + If the message is only 2 bytes long, this one is null. + \n Value goes from 0 to 127. + */ byte data2; - /*! System Exclusive dedicated byte array. \n Array length is stocked on 16 bits, in data1 (LSB) and data2 (MSB) */ + + /*! System Exclusive dedicated byte array. + \n Array length is stocked on 16 bits, + in data1 (LSB) and data2 (MSB) + */ byte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; - /*! This boolean indicates if the message is valid or not. There is no channel consideration here, validity means the message respects the MIDI norm. */ + + /*! This boolean indicates if the message is valid or not. + There is no channel consideration here, + validity means the message respects the MIDI norm. + */ bool valid; + }; @@ -116,22 +142,24 @@ struct midimsg { See member descriptions to know how to use it, or check out the examples supplied with the library. */ -class MIDI_Class { - +class MIDI_Class +{ public: + + // ========================================================================= // Constructor and Destructor + MIDI_Class(); ~MIDI_Class(); - void begin(const byte inChannel = 1); + // ========================================================================= + // MIDI Output - - /* ####### OUTPUT COMPILATION BLOCK ####### */ -#if COMPILE_MIDI_OUT +#if COMPILE_MIDI_OUT // Start compilation block public: @@ -164,12 +192,14 @@ private: byte mRunningStatus_TX; #endif // USE_RUNNING_STATUS -#endif // COMPILE_MIDI_OUT +#endif // COMPILE_MIDI_OUT - /* ####### INPUT COMPILATION BLOCK ####### */ -#if COMPILE_MIDI_IN + // ========================================================================= + // MIDI Input + +#if COMPILE_MIDI_IN // Start compilation block public: @@ -199,18 +229,40 @@ public: */ static inline const kMIDIType getTypeFromStatusByte(const byte inStatus) { - if ((inStatus < 0x80) - || (inStatus == 0xF4) - || (inStatus == 0xF5) - || (inStatus == 0xF9) - || (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. + if ((inStatus < 0x80) || + (inStatus == 0xF4) || + (inStatus == 0xF5) || + (inStatus == 0xF9) || + (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble. else return (kMIDIType)inStatus; } +private: + + bool input_filter(byte inChannel); + bool parse(byte inChannel); + void reset_input_attributes(); + + // Attributes + byte mRunningStatus_RX; + byte mInputChannel; + + byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; + unsigned int mPendingMessageExpectedLenght; + unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. + + midimsg mMessage; + + + // ========================================================================= + // Input Callbacks + #if USE_CALLBACKS +public: + void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); @@ -232,27 +284,8 @@ public: void disconnectCallbackFromType(kMIDIType Type); -#endif // USE_CALLBACKS - - private: - bool input_filter(byte inChannel); - bool parse(byte inChannel); - void reset_input_attributes(); - - // Attributes - byte mRunningStatus_RX; - byte mInputChannel; - - byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; - unsigned int mPendingMessageExpectedLenght; - unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. - - midimsg mMessage; - -#if USE_CALLBACKS - void launchCallback(); void (*mNoteOffCallback)(byte channel, byte note, byte velocity); @@ -274,14 +307,15 @@ private: void (*mActiveSensingCallback)(void); void (*mSystemResetCallback)(void); -#endif // USE_CALLBACKS - +#endif // USE_CALLBACKS #endif // COMPILE_MIDI_IN - /* ####### THRU COMPILATION BLOCK ####### */ -#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + // ========================================================================= + // MIDI Soft Thru + +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) public: From 5b9a29bd75e5f9bb414c7694f2801bd787dc80f7 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 23 May 2012 00:01:41 +0200 Subject: [PATCH 11/38] Copying installation scripts (stripping local install scripts though, they would make no sense once packaged). --- res/packaging.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/packaging.sh b/res/packaging.sh index 275fe83..6429592 100644 --- a/res/packaging.sh +++ b/res/packaging.sh @@ -9,6 +9,8 @@ cp ../src/* temp # Copy resources cp keywords.txt temp +cp install_* temp +rm temp/install_local_* # Copy examples cp -r examples/* temp/examples From 88e4a19fcf10c8a25fd71a686b6543f7bd39d6b7 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 23 May 2012 00:17:34 +0200 Subject: [PATCH 12/38] Archiving built packages once created and cleaning up. --- res/packaging.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/res/packaging.sh b/res/packaging.sh index 6429592..dbad4b8 100644 --- a/res/packaging.sh +++ b/res/packaging.sh @@ -26,4 +26,16 @@ cp -r ../doc/* temp/doc # Generate package mv temp MIDI zip -r MIDI.zip MIDI -mv MIDI.zip Arduino_MIDI_Library_v.zip + + +# Remove temp folder +rm -rf MIDI + +# Archive generated packaged + +if [[ !( -d ../bin ) ]] +then + mkdir ../bin # Create archives location +fi + +mv MIDI.zip ../bin/Arduino_MIDI_Library.zip From eadb9750785eebffb143d538f5987a9ab445b99a Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 23 May 2012 00:18:56 +0200 Subject: [PATCH 13/38] Ignoring generated doc. --- doc/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 doc/.gitignore diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..2ec816f --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,2 @@ +html +latex From da05198ed1c3e036b5c470b0232caf524cf3ec00 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 23 May 2012 00:23:32 +0200 Subject: [PATCH 14/38] Removed extension for scripts and made them executable for easier use. --- res/{install_local_mac.sh => install_local_mac} | 0 res/{install_mac.sh => install_mac} | 0 res/{packaging.sh => packaging} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename res/{install_local_mac.sh => install_local_mac} (100%) mode change 100644 => 100755 rename res/{install_mac.sh => install_mac} (100%) mode change 100644 => 100755 rename res/{packaging.sh => packaging} (100%) mode change 100644 => 100755 diff --git a/res/install_local_mac.sh b/res/install_local_mac old mode 100644 new mode 100755 similarity index 100% rename from res/install_local_mac.sh rename to res/install_local_mac diff --git a/res/install_mac.sh b/res/install_mac old mode 100644 new mode 100755 similarity index 100% rename from res/install_mac.sh rename to res/install_mac diff --git a/res/packaging.sh b/res/packaging old mode 100644 new mode 100755 similarity index 100% rename from res/packaging.sh rename to res/packaging From c4a4db9de7e19a3a03cb9d3b0aba40988d82b32c Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 23 May 2012 00:32:56 +0200 Subject: [PATCH 15/38] Added doc. --- res/install_local_mac | 2 ++ res/install_mac | 12 +++++++++++- res/packaging | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/res/install_local_mac b/res/install_local_mac index bf517cc..5f9376c 100755 --- a/res/install_local_mac +++ b/res/install_local_mac @@ -1,4 +1,6 @@ #!/bin/bash +# Use this script to install the library directy from the git clone. + if [[ -d /Applications/Arduino.app ]] then diff --git a/res/install_mac b/res/install_mac index 9eaeac1..4e6659a 100755 --- a/res/install_mac +++ b/res/install_mac @@ -1,10 +1,20 @@ #!/bin/bash +# This script installs the Arduino MIDI Library into the Arduino application, +# so that every sketch can include it directly, without having to copy anything. +# +# To install the library, run this script by double-clicking it, +# it should be directly executable and seen as such by Mac OS X. +# If not, open a terminal, cd to the script location, and run ./install_mac +# +# Open the Arduino IDE, and you're ready to go! + +# The script assumes the Arduino application +# is installed in the default location. if [[ -d /Applications/Arduino.app ]] then # Define locations - lib_path=/Applications/Arduino.app/Contents/Resources/Java/libraries/MIDI if [[ -d $lib_path ]] diff --git a/res/packaging b/res/packaging index dbad4b8..33ee9c3 100755 --- a/res/packaging +++ b/res/packaging @@ -1,4 +1,13 @@ #!/bin/bash +# +# Generate an archive with packaged content for easier delivery. +# The generated archive contains: +# - Source files (MIDI.cpp / MIDI.h) +# - Resources (keywords.txt) +# - Documentation (Doxygen) +# - Examples for Arduino IDE +# - Installation scripts + # Create a temporary destination folder mkdir -p temp/doc From 7a59d08f5528fd927a0ca899f7a74823bc663975 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 23 May 2012 01:03:59 +0200 Subject: [PATCH 16/38] Fixed directory copy for doc. --- res/install_local_mac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/install_local_mac b/res/install_local_mac index 5f9376c..9b870bf 100755 --- a/res/install_local_mac +++ b/res/install_local_mac @@ -33,7 +33,7 @@ then # Copy doc mkdir $lib_path/doc - cp ../doc/* $lib_path/doc + cp -r ../doc/* $lib_path/doc else echo "Arduino application not found." From 65b5db2362cbde4679c67c8bec99da9627df370a Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 23 May 2012 01:51:12 +0200 Subject: [PATCH 17/38] Fixed script name. --- res/install_mac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/install_mac b/res/install_mac index 4e6659a..b94a610 100755 --- a/res/install_mac +++ b/res/install_mac @@ -30,7 +30,7 @@ then cp -r * $lib_path # Cleanup - rm $lib_path/install_mac.sh + rm $lib_path/install_mac else echo "Arduino application not found." From cfe39e04aad660f4019bc3c2f84a0da827922e88 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 25 May 2012 14:32:55 +0200 Subject: [PATCH 18/38] Removed pitchBend method using unsigned interface, as it conflicted with the signed. Added macros for min and max, so that it's easier to use. --- src/MIDI.cpp | 24 +++++------------------- src/MIDI.h | 5 ++++- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 9517f99..5908157 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -299,31 +299,17 @@ 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. + between MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX, + 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); + unsigned int bend = PitchValue - MIDI_PITCHBEND_MIN; -} - - -/*! \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); + send(PitchBend,(bend & 0x7F),(bend >> 7) & 0x7F,Channel); } @@ -1047,7 +1033,7 @@ void MIDI_Class::launchCallback() // 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 PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) + MIDI_PITCHBEND_MIN); 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; diff --git a/src/MIDI.h b/src/MIDI.h index 58ea2f2..6263d90 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -63,6 +63,10 @@ #define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. +#define MIDI_PITCHBEND_MIN -8192 +#define MIDI_PITCHBEND_MAX 8191 + + /*! Type definition for practical use (because "unsigned char" is a bit long to write.. ) @@ -179,7 +183,6 @@ public: 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); From 3edb9c15ea59fbaf442720f04c40ddc74e47a22d Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sat, 16 Jun 2012 17:36:34 +0200 Subject: [PATCH 19/38] Refactoring to build on the new AVR core. --- src/MIDI.cpp | 20 +++++++++----------- src/MIDI.h | 23 ++++++++++++----------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index c66f4fe..bb4265e 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -9,7 +9,8 @@ */ #include "MIDI.h" -#include "Serial.h" +#include "core.h" +#include "hardware_Serial.h" #include @@ -21,7 +22,7 @@ MIDI_Class MIDI; MIDI_Class::MIDI_Class() { -#if USE_CALLBACKS +#if COMPILE_MIDI_IN && USE_CALLBACKS // Initialise callbacks to NULL pointer mNoteOffCallback = NULL; @@ -310,8 +311,7 @@ void MIDI_Class::sendPitchBend(double PitchValue, byte Channel) { - unsigned int pitchval = (PitchValue+1.f)*8192; - if (pitchval > 16383) pitchval = 16383; // overflow protection + int pitchval = PitchValue * MIDI_PITCHBEND_MAX; sendPitchBend(pitchval,Channel); } @@ -528,16 +528,16 @@ bool MIDI_Class::read(const byte inChannel) // Private method: MIDI parser bool MIDI_Class::parse(byte inChannel) { + const byte bytes_available = MIDI_SERIAL_PORT.available(); - const int bytes_available = MIDI_SERIAL_PORT.available(); - - if (bytes_available <= 0) { + if (bytes_available == 0) // No data available. return false; - } // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. - if (bytes_available == UART_BUFFER_SIZE) { Serial << "Overflow, call the Vogons!!" << endl; + if (bytes_available == UART::bufferSize) + { + PRINT_DEBUG("MIDI Overflow"); MIDI_SERIAL_PORT.flush(); } else { @@ -1184,5 +1184,3 @@ void MIDI_Class::thru_filter(byte inChannel) #endif // Thru - - diff --git a/src/MIDI.h b/src/MIDI.h index 0c1327c..81cd69e 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -8,11 +8,10 @@ * license GPL Forty Seven Effects - 2011 */ -#ifndef LIB_MIDI_H_ -#define LIB_MIDI_H_ - -#include "Types.h" // Include all the types we need. +#ifndef _FSE_LIB_MIDI_H_ +#define _FSE_LIB_MIDI_H_ +#include "core_Types.h" // Include all the types we need. /* ############################################################### @@ -58,7 +57,7 @@ #define MIDI_CHANNEL_OMNI 0 #define MIDI_CHANNEL_OFF 17 // and over -#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. +#define MIDI_SYSEX_ARRAY_SIZE 128 // Maximum size is 65535 bytes. #define MIDI_PITCHBEND_MIN -8192 #define MIDI_PITCHBEND_MAX 8191 @@ -66,7 +65,8 @@ /*! Enumeration of MIDI types */ -enum kMIDIType { +enum kMIDIType +{ NoteOff = 0x80, ///< Note Off NoteOn = 0x90, ///< Note On AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch @@ -146,7 +146,7 @@ struct midimsg See member descriptions to know how to use it, or check out the examples supplied with the library. */ -class MIDI_Class +class MIDI_Class { public: @@ -252,9 +252,9 @@ private: byte mRunningStatus_RX; byte mInputChannel; - byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; + byte mPendingMessage[3]; // SysEx are dumped into mMessage directly. unsigned int mPendingMessageExpectedLenght; - unsigned int mPendingMessageIndex; // Extended to unsigned int for larger sysex payloads. + unsigned int mPendingMessageIndex; // Extended to unsigned int for larger SysEx payloads. midimsg mMessage; @@ -339,7 +339,7 @@ private: void thru_filter(byte inChannel); bool mThruActivated; - kThruFilterMode mThruFilterMode; + kThruFilterMode mThruFilterMode; #endif // Thru @@ -347,4 +347,5 @@ private: extern MIDI_Class MIDI; -#endif // LIB_MIDI_H_ + +#endif // _FSE_LIB_MIDI_H_ From 8e087e40b1a682cffd10b3739102ee7ef7396c0b Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sun, 17 Jun 2012 12:10:58 +0200 Subject: [PATCH 21/38] Fixed ambiguous call. --- src/MIDI.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 5908157..9a37d35 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -324,8 +324,7 @@ void MIDI_Class::sendPitchBend(double PitchValue, byte Channel) { - unsigned int pitchval = (PitchValue+1.f)*8192; - if (pitchval > 16383) pitchval = 16383; // overflow protection + int pitchval = PitchValue * MIDI_PITCHBEND_MAX; sendPitchBend(pitchval,Channel); } From 323d8e1707e5b3ddfd4f77fca95b539dd65f0dae Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sun, 17 Jun 2012 12:31:09 +0200 Subject: [PATCH 22/38] Define some macros before including MIDI.h in your sketch to enable software serial. --- src/MIDI.cpp | 13 ++++++------- src/MIDI.h | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 9a37d35..d5981f9 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -12,19 +12,18 @@ #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 +#if MIDI_USE_SOFTWARE_SERIAL // Note: Make sure the following relative path is correct. #include "../SoftwareSerial/SoftwareSerial.h" -SoftwareSerial softSerialClass(SOFTSERIAL_RX_PIN,SOFTSERIAL_TX_PIN); - +SoftwareSerial softSerialClass(MIDI_SOFTSERIAL_RX_PIN, + MIDI_SOFTSERIAL_TX_PIN); #undef MIDI_SERIAL_PORT #define MIDI_SERIAL_PORT softSerialClass -#endif // USE_SOFTWARE_SERIAL +#else +#include "HardwareSerial.h" +#endif // MIDI_USE_SOFTWARE_SERIAL /*! \brief Main instance (the class comes pre-instantiated). */ diff --git a/src/MIDI.h b/src/MIDI.h index 6263d90..da580f3 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -36,12 +36,6 @@ #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 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 controlling your hardware. @@ -67,6 +61,18 @@ #define MIDI_PITCHBEND_MAX 8191 +#ifndef MIDI_USE_SOFTWARE_SERIAL +#define MIDI_USE_SOFTWARE_SERIAL 0 +#else + #if MIDI_USE_SOFTWARE_SERIAL + #if !defined(MIDI_SOFTSERIAL_RX_PIN) || !defined(MIDI_SOFTSERIAL_TX_PIN) + #error You need to define some pins to enable MIDI with software serial. + #endif + #endif +#endif + + + /*! Type definition for practical use (because "unsigned char" is a bit long to write.. ) From 44f6f78454b8479ca1369b549f0a0af4807a37ca Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sun, 17 Jun 2012 12:45:06 +0200 Subject: [PATCH 23/38] Reverted last commit (won't work). --- src/MIDI.h | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index da580f3..039d15d 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -36,6 +36,12 @@ #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 MIDI_USE_SOFTWARE_SERIAL 0 // Set to 1 to use SoftwareSerial instead of native serial ports. +#define MIDI_SOFTSERIAL_RX_PIN 1 // This pin number will be used for MIDI Input +#define MIDI_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 controlling your hardware. @@ -61,18 +67,6 @@ #define MIDI_PITCHBEND_MAX 8191 -#ifndef MIDI_USE_SOFTWARE_SERIAL -#define MIDI_USE_SOFTWARE_SERIAL 0 -#else - #if MIDI_USE_SOFTWARE_SERIAL - #if !defined(MIDI_SOFTSERIAL_RX_PIN) || !defined(MIDI_SOFTSERIAL_TX_PIN) - #error You need to define some pins to enable MIDI with software serial. - #endif - #endif -#endif - - - /*! Type definition for practical use (because "unsigned char" is a bit long to write.. ) From e4c8205dc397cdc60ac7f868b74dc7e61ae3dffb Mon Sep 17 00:00:00 2001 From: Francois Best Date: Sun, 17 Jun 2012 22:36:51 +0200 Subject: [PATCH 24/38] Don't check for overflow (the serial class should do it). --- src/MIDI.cpp | 449 +++++++++++++++++++++++++-------------------------- 1 file changed, 220 insertions(+), 229 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index bb4265e..c233e7d 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -534,31 +534,226 @@ bool MIDI_Class::parse(byte inChannel) // No data available. return false; - // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. - if (bytes_available == UART::bufferSize) - { - PRINT_DEBUG("MIDI Overflow"); - MIDI_SERIAL_PORT.flush(); - } - else { + + /* Parsing algorithm: + Get a byte from the serial buffer. + * If there is no pending message to be recomposed, start a new one. + - Find type and channel (if pertinent) + - Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty. + * Else, add the extracted byte to the pending message, and check validity. When the message is done, store it. + */ + + + const byte extracted = MIDI_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) { // Start a new pending message + mPendingMessage[0] = extracted; - /* Parsing algorithm: - Get a byte from the serial buffer. - * If there is no pending message to be recomposed, start a new one. - - Find type and channel (if pertinent) - - Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty. - * Else, add the extracted byte to the pending message, and check validity. When the message is done, store it. - */ + // Check for running status first + switch (getTypeFromStatusByte(mRunningStatus_RX)) { + // Only these types allow Running Status: + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + + // If the status byte is not received, prepend it to the pending message + if (extracted < 0x80) { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, so the running status does not apply here. + // It will be updated upon completion of this message. + + break; + + default: + // No running status + break; + } - const byte extracted = MIDI_SERIAL_PORT.read(); + switch (getTypeFromStatusByte(mPendingMessage[0])) { + + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //reset_input_attributes(); + + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLenght = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLenght = 3; + break; + + case SystemExclusive: + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mRunningStatus_RX = InvalidType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + reset_input_attributes(); + return false; + break; + } - if (mPendingMessageIndex == 0) { // Start a new pending message - mPendingMessage[0] = extracted; + // Then update the index of the pending message. + mPendingMessageIndex++; + +#if USE_1BYTE_PARSING + // Message is not complete. + return false; +#else + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); +#endif + + } + else { + + // First, test if this is a status byte + if (extracted >= 0x80) { - // Check for running status first - switch (getTypeFromStatusByte(mRunningStatus_RX)) { - // Only these types allow Running Status: + // Reception of status bytes in the middle of an uncompleted message + // are allowed only for interleaved Real Time message or EOX + switch (extracted) { + case Clock: + case Start: + case Continue: + case Stop: + case ActiveSensing: + case SystemReset: + + /* + This is tricky. Here we will have to extract the one-byte message, + pass it to the structure for being read outside the MIDI class, + and recompose the message it was interleaved into. + + Oh, and without killing the running status.. + + This is done by leaving the pending message as is, it will be completed on next calls. + */ + + mMessage.type = (kMIDIType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xF7: + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + + // Store System Exclusive array in midimsg structure + for (byte i=0;i> 8; + + mMessage.channel = 0; + mMessage.valid = true; + + reset_input_attributes(); + + return true; + } + else { + // Well well well.. error. + reset_input_attributes(); + return false; + } + + break; + default: + break; + } + + + + } + + + // Add extracted data byte to pending message + mPendingMessage[mPendingMessageIndex] = extracted; + + + // Now we are going to check if we have reached the end of the message + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { + + // "FML" case: fall down here with an overflown SysEx.. + // This means we received the last possible data byte that can fit the buffer. + // If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + reset_input_attributes(); + return false; + } + + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; + else mMessage.data2 = 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) { case NoteOff: case NoteOn: case AfterTouchPoly: @@ -566,83 +761,18 @@ bool MIDI_Class::parse(byte inChannel) case ProgramChange: case AfterTouchChannel: case PitchBend: - - // If the status byte is not received, prepend it to the pending message - if (extracted < 0x80) { - mPendingMessage[0] = mRunningStatus_RX; - mPendingMessage[1] = extracted; - mPendingMessageIndex = 1; - } - // Else: well, we received another status byte, so the running status does not apply here. - // It will be updated upon completion of this message. - + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; break; default: // No running status - break; - } - - - switch (getTypeFromStatusByte(mPendingMessage[0])) { - - // 1 byte messages - case Start: - case Continue: - case Stop: - case Clock: - case ActiveSensing: - case SystemReset: - case TuneRequest: - // Handle the message type directly here. - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.valid = true; - - // \fix Running Status broken when receiving Clock messages. - // Do not reset all input attributes, Running Status must remain unchanged. - //reset_input_attributes(); - - // We still need to reset these - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - return true; - break; - - // 2 bytes messages - case ProgramChange: - case AfterTouchChannel: - case TimeCodeQuarterFrame: - case SongSelect: - mPendingMessageExpectedLenght = 2; - break; - - // 3 bytes messages - case NoteOn: - case NoteOff: - case ControlChange: - case PitchBend: - case AfterTouchPoly: - case SongPosition: - mPendingMessageExpectedLenght = 3; - break; - - case SystemExclusive: - mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes mRunningStatus_RX = InvalidType; break; - - case InvalidType: - default: - // This is obviously wrong. Let's get the hell out'a here. - reset_input_attributes(); - return false; - break; } - + return true; + } + else { // Then update the index of the pending message. mPendingMessageIndex++; @@ -656,145 +786,6 @@ bool MIDI_Class::parse(byte inChannel) #endif } - else { - - // First, test if this is a status byte - if (extracted >= 0x80) { - - // Reception of status bytes in the middle of an uncompleted message - // are allowed only for interleaved Real Time message or EOX - switch (extracted) { - case Clock: - case Start: - case Continue: - case Stop: - case ActiveSensing: - case SystemReset: - - /* - This is tricky. Here we will have to extract the one-byte message, - pass it to the structure for being read outside the MIDI class, - and recompose the message it was interleaved into. - - Oh, and without killing the running status.. - - This is done by leaving the pending message as is, it will be completed on next calls. - */ - - mMessage.type = (kMIDIType)extracted; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.channel = 0; - mMessage.valid = true; - return true; - - break; - - // End of Exclusive - case 0xF7: - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - - // Store System Exclusive array in midimsg structure - for (byte i=0;i> 8; - - mMessage.channel = 0; - mMessage.valid = true; - - reset_input_attributes(); - - return true; - } - else { - // Well well well.. error. - reset_input_attributes(); - return false; - } - - break; - default: - break; - } - - - - } - - - // Add extracted data byte to pending message - mPendingMessage[mPendingMessageIndex] = extracted; - - - // Now we are going to check if we have reached the end of the message - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { - - // "FML" case: fall down here with an overflown SysEx.. - // This means we received the last possible data byte that can fit the buffer. - // If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - reset_input_attributes(); - return false; - } - - - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message - - mMessage.data1 = mPendingMessage[1]; - - // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; - else mMessage.data2 = 0; - - // Reset local variables - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = true; - - // Activate running status (if enabled for the received type) - switch (mMessage.type) { - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - // Running status enabled: store it from received message - mRunningStatus_RX = mPendingMessage[0]; - break; - - default: - // No running status - mRunningStatus_RX = InvalidType; - break; - } - return true; - } - else { - // Then update the index of the pending message. - mPendingMessageIndex++; - -#if USE_1BYTE_PARSING - // Message is not complete. - return false; -#else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); -#endif - - } - - } } From bdd7d15b8abd03c5ebf99f5ed55f96d88524cb63 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 20 Jun 2012 18:40:57 +0200 Subject: [PATCH 25/38] Added control change enumeration. --- src/MIDI.h | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/MIDI.h b/src/MIDI.h index 81cd69e..1a51c57 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -97,6 +97,74 @@ enum kThruFilterMode { }; +enum eMIDICCNumber +{ + // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- + BankSelect = 0, + ModulationWheel = 1, + BreathController = 2, + // CC3 undefined + FootController = 4, + PortamentoTime = 5, + DataEntry = 6, + ChannelVolume = 7, + Balance = 8, + // CC9 undefined + Pan = 10, + ExpressionController = 11, + EffectControl1 = 12, + EffectControl2 = 13, + // CC14 undefined + // CC15 undefined + GeneralPurposeController1 = 16, + GeneralPurposeController2 = 17, + GeneralPurposeController3 = 18, + GeneralPurposeController4 = 19, + + // Switches ---------------------------------------------------------------- + Sustain = 64, + Portamento = 65, + Sostenuto = 66, + SoftPedal = 67, + Legato = 68, + Hold2 = 69, + + // Low resolution continuous controllers ----------------------------------- + SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off + SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off + SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off + SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off + SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off + SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off + SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off + SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off + SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off + SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off + GeneralPurposeController5 = 80, + GeneralPurposeController6 = 81, + GeneralPurposeController7 = 82, + GeneralPurposeController8 = 83, + PortamentoControl = 84, + // CC85 to CC90 undefined + Effects1 = 91, ///< Reverb send level + Effects2 = 92, ///< Tremolo depth + Effects3 = 93, ///< Chorus send level + Effects4 = 94, ///< Celeste depth + Effects5 = 95, ///< Phaser depth + + // Channel Mode messages --------------------------------------------------- + AllSoundOff = 120, + ResetAllControllers = 121, + LocalControl = 122, + AllNotesOff = 123, + OmniModeOff = 124, + OmniModeOn = 125, + MonoModeOn = 126, + PolyModeOn = 127 +}; + + + /*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). From 0422aa23dfc14b5ebafce58b90aa492e54f7f97f Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 3 Jul 2012 08:01:50 +0200 Subject: [PATCH 27/38] Coding style. --- src/MIDI.cpp | 361 +++++++++++++++++++++------------------------------ 1 file changed, 150 insertions(+), 211 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index c233e7d..f919b42 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -21,7 +21,6 @@ MIDI_Class MIDI; /*! \brief Default constructor for MIDI_Class. */ MIDI_Class::MIDI_Class() { - #if COMPILE_MIDI_IN && USE_CALLBACKS // Initialise callbacks to NULL pointer @@ -45,7 +44,6 @@ MIDI_Class::MIDI_Class() mSystemResetCallback = NULL; #endif - } @@ -67,20 +65,14 @@ MIDI_Class::~MIDI_Class() */ void MIDI_Class::begin(const byte inChannel) { - // Initialise the Serial port MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); - -#if COMPILE_MIDI_OUT - -#if USE_RUNNING_STATUS +#if COMPILE_MIDI_OUT && USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; - -#endif // USE_RUNNING_STATUS - -#endif // COMPILE_MIDI_OUT + +#endif // COMPILE_MIDI_OUT && USE_RUNNING_STATUS #if COMPILE_MIDI_IN @@ -119,9 +111,7 @@ void MIDI_Class::begin(const byte inChannel) const byte MIDI_Class::genstatus(const kMIDIType inType, const byte inChannel) const { - return ((byte)inType | ((inChannel-1) & 0x0F)); - } @@ -141,9 +131,11 @@ void MIDI_Class::send(kMIDIType type, byte data2, byte channel) { - // Then test if channel is valid - if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { + if (channel >= MIDI_CHANNEL_OFF || + channel == MIDI_CHANNEL_OMNI || + type < NoteOff) + { #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; @@ -152,9 +144,8 @@ void MIDI_Class::send(kMIDIType type, return; // Don't send anything } - if (type <= PitchBend) { - // Channel messages - + if (type <= PitchBend) // Channel messages + { // Protection: remove MSBs on data data1 &= 0x7F; data2 &= 0x7F; @@ -163,7 +154,8 @@ void MIDI_Class::send(kMIDIType type, #if USE_RUNNING_STATUS // Check Running Status - if (mRunningStatus_TX != statusbyte) { + if (mRunningStatus_TX != statusbyte) + { // New message, memorise and send header mRunningStatus_TX = statusbyte; MIDI_SERIAL_PORT.write(mRunningStatus_TX); @@ -175,16 +167,13 @@ void MIDI_Class::send(kMIDIType type, // Then send data MIDI_SERIAL_PORT.write(data1); - if (type != ProgramChange && type != AfterTouchChannel) { + if (type != ProgramChange && type != AfterTouchChannel) MIDI_SERIAL_PORT.write(data2); - } + return; } - if (type >= TuneRequest && type <= SystemReset) { - // System Real-time and 1 byte. - sendRealTime(type); - } - + if (type >= TuneRequest && type <= SystemReset) + sendRealTime(type); // System Real-time and 1 byte. } @@ -201,17 +190,18 @@ void MIDI_Class::sendNoteOn(byte NoteNumber, byte Velocity, byte Channel) { - send(NoteOn,NoteNumber,Velocity,Channel); - } -/*! \brief Send a Note Off message (a real Note Off, not a Note On with null velocity) +/*! \brief Send a Note Off message \param NoteNumber Pitch value in the MIDI format (0 to 127). \param Velocity Release velocity (0 to 127). \param Channel The channel on which the message will be sent (1 to 16). + Note: you can send NoteOn with zero velocity to make a NoteOff, this is based + on the Running Status principle, to avoid sending status messages and thus + sending only NoteOn data. This method will always send a real NoteOff message. Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html */ @@ -219,9 +209,7 @@ void MIDI_Class::sendNoteOff(byte NoteNumber, byte Velocity, byte Channel) { - - send(NoteOff,NoteNumber,Velocity,Channel); - + send(NoteOff,NoteNumber,Velocity,Channel); } @@ -232,9 +220,7 @@ void MIDI_Class::sendNoteOff(byte NoteNumber, void MIDI_Class::sendProgramChange(byte ProgramNumber, byte Channel) { - send(ProgramChange,ProgramNumber,0,Channel); - } @@ -250,13 +236,11 @@ void MIDI_Class::sendControlChange(byte ControlNumber, byte ControlValue, byte Channel) { - send(ControlChange,ControlNumber,ControlValue,Channel); - } -/*! \brief Send a Polyphonic AfterTouch message (applies to only one specified note) +/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) \param NoteNumber The note to apply AfterTouch to (0 to 127). \param Pressure The amount of AfterTouch to apply (0 to 127). \param Channel The channel on which the message will be sent (1 to 16). @@ -265,9 +249,7 @@ void MIDI_Class::sendPolyPressure(byte NoteNumber, byte Pressure, byte Channel) { - send(AfterTouchPoly,NoteNumber,Pressure,Channel); - } @@ -278,9 +260,7 @@ void MIDI_Class::sendPolyPressure(byte NoteNumber, void MIDI_Class::sendAfterTouch(byte Pressure, byte Channel) { - send(AfterTouchChannel,Pressure,0,Channel); - } @@ -293,11 +273,8 @@ void MIDI_Class::sendAfterTouch(byte Pressure, void MIDI_Class::sendPitchBend(int PitchValue, byte Channel) { - - unsigned int bend = PitchValue - MIDI_PITCHBEND_MIN; - + const unsigned int bend = PitchValue - MIDI_PITCHBEND_MIN; send(PitchBend,(bend & 0x7F),(bend >> 7) & 0x7F,Channel); - } @@ -310,10 +287,8 @@ void MIDI_Class::sendPitchBend(int PitchValue, void MIDI_Class::sendPitchBend(double PitchValue, byte Channel) { - - int pitchval = PitchValue * MIDI_PITCHBEND_MAX; + const int pitchval = PitchValue * MIDI_PITCHBEND_MAX; sendPitchBend(pitchval,Channel); - } @@ -330,34 +305,24 @@ void MIDI_Class::sendSysEx(int length, const byte *const array, bool ArrayContainsBoundaries) { - - if (ArrayContainsBoundaries == false) { - + if (ArrayContainsBoundaries == false) + { MIDI_SERIAL_PORT.write(0xF0); - for (int i=0;i> 7) & 0x7F); @@ -420,21 +379,18 @@ void MIDI_Class::sendSongPosition(unsigned int Beats) #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif - } /*! \brief Send a Song Select message */ void MIDI_Class::sendSongSelect(byte SongNumber) { - MIDI_SERIAL_PORT.write((byte)SongSelect); MIDI_SERIAL_PORT.write(SongNumber & 0x7F); #if USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif - } @@ -447,7 +403,8 @@ void MIDI_Class::sendSongSelect(byte SongNumber) */ void MIDI_Class::sendRealTime(kMIDIType Type) { - switch (Type) { + switch (Type) + { case TuneRequest: // Not really real-time, but one byte anyway. case Clock: case Start: @@ -462,12 +419,12 @@ void MIDI_Class::sendRealTime(kMIDIType Type) break; } - // Do not cancel Running Status for real-time messages as they can be interleaved within any message. - // Though, TuneRequest can be sent here, and as it is a System Common message, it must reset Running Status. + // Do not cancel Running Status for real-time messages as they can be + // interleaved within any message. Though, TuneRequest can be sent here, + // and as it is a System Common message, it must reset Running Status. #if USE_RUNNING_STATUS if (Type == TuneRequest) mRunningStatus_TX = InvalidType; #endif - } #endif // COMPILE_MIDI_OUT @@ -489,9 +446,7 @@ void MIDI_Class::sendRealTime(kMIDIType Type) */ bool MIDI_Class::read() { - return read(mInputChannel); - } @@ -500,12 +455,13 @@ bool MIDI_Class::read() */ bool MIDI_Class::read(const byte inChannel) { + if (inChannel >= MIDI_CHANNEL_OFF) + return false; // MIDI Input disabled. - if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. - - if (parse(inChannel)) { - - if (input_filter(inChannel)) { + if (parse(inChannel)) + { + if (input_filter(inChannel)) + { #if (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) thru_filter(inChannel); @@ -514,14 +470,11 @@ bool MIDI_Class::read(const byte inChannel) #if USE_CALLBACKS launchCallback(); #endif - return true; } - } return false; - } @@ -538,19 +491,23 @@ bool MIDI_Class::parse(byte inChannel) /* Parsing algorithm: Get a byte from the serial buffer. * If there is no pending message to be recomposed, start a new one. - - Find type and channel (if pertinent) - - Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty. - * Else, add the extracted byte to the pending message, and check validity. When the message is done, store it. + - Find type and channel (if pertinent) + - Look for other bytes in buffer, call parser recursively, + until the message is assembled or the buffer is empty. + * Else, add the extracted byte to the pending message, and check validity. + When the message is done, store it. */ - const byte extracted = MIDI_SERIAL_PORT.read(); - if (mPendingMessageIndex == 0) { // Start a new pending message + if (mPendingMessageIndex == 0) + { + // Start a new pending message mPendingMessage[0] = extracted; // Check for running status first - switch (getTypeFromStatusByte(mRunningStatus_RX)) { + switch (getTypeFromStatusByte(mRunningStatus_RX)) + { // Only these types allow Running Status: case NoteOff: case NoteOn: @@ -560,13 +517,16 @@ bool MIDI_Class::parse(byte inChannel) case AfterTouchChannel: case PitchBend: - // If the status byte is not received, prepend it to the pending message - if (extracted < 0x80) { + // If the status byte is not received, prepend it + // to the pending message + if (extracted < 0x80) + { mPendingMessage[0] = mRunningStatus_RX; mPendingMessage[1] = extracted; mPendingMessageIndex = 1; } - // Else: well, we received another status byte, so the running status does not apply here. + // Else: well, we received another status byte, + // so the running status does not apply here. // It will be updated upon completion of this message. break; @@ -577,8 +537,8 @@ bool MIDI_Class::parse(byte inChannel) } - switch (getTypeFromStatusByte(mPendingMessage[0])) { - + switch (getTypeFromStatusByte(mPendingMessage[0])) + { // 1 byte messages case Start: case Continue: @@ -624,7 +584,9 @@ bool MIDI_Class::parse(byte inChannel) break; case SystemExclusive: - mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + // The message can be any lenght + // between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; mRunningStatus_RX = InvalidType; break; @@ -649,14 +611,15 @@ bool MIDI_Class::parse(byte inChannel) #endif } - else { - + else + { // First, test if this is a status byte - if (extracted >= 0x80) { - + if (extracted >= 0x80) + { // Reception of status bytes in the middle of an uncompleted message // are allowed only for interleaved Real Time message or EOX - switch (extracted) { + switch (extracted) + { case Clock: case Start: case Continue: @@ -664,15 +627,12 @@ bool MIDI_Class::parse(byte inChannel) case ActiveSensing: case SystemReset: - /* - This is tricky. Here we will have to extract the one-byte message, - pass it to the structure for being read outside the MIDI class, - and recompose the message it was interleaved into. - - Oh, and without killing the running status.. - - This is done by leaving the pending message as is, it will be completed on next calls. - */ + // Here we will have to extract the one-byte message, + // pass it to the structure for being read outside + // the MIDI class, and recompose the message it was + // interleaved into. Oh, and without killing the running status.. + // This is done by leaving the pending message as is, + // it will be completed on next calls. mMessage.type = (kMIDIType)extracted; mMessage.data1 = 0; @@ -685,27 +645,25 @@ bool MIDI_Class::parse(byte inChannel) // End of Exclusive case 0xF7: - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { - + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) + { // Store System Exclusive array in midimsg structure - for (byte i=0;i> 8; - mMessage.channel = 0; mMessage.valid = true; reset_input_attributes(); - return true; } - else { + else + { // Well well well.. error. reset_input_attributes(); return false; @@ -715,36 +673,34 @@ bool MIDI_Class::parse(byte inChannel) default: break; } - - - } - // Add extracted data byte to pending message mPendingMessage[mPendingMessageIndex] = extracted; - // Now we are going to check if we have reached the end of the message - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) { - + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) + { // "FML" case: fall down here with an overflown SysEx.. - // This means we received the last possible data byte that can fit the buffer. - // If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + // This means we received the last possible data byte that can fit + // the buffer. If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) + { reset_input_attributes(); return false; } - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message + // Don't check if it is a Channel Message + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; mMessage.data1 = mPendingMessage[1]; // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2]; - else mMessage.data2 = 0; + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; // Reset local variables mPendingMessageIndex = 0; @@ -753,7 +709,8 @@ bool MIDI_Class::parse(byte inChannel) mMessage.valid = true; // Activate running status (if enabled for the received type) - switch (mMessage.type) { + switch (mMessage.type) + { case NoteOff: case NoteOn: case AfterTouchPoly: @@ -772,7 +729,8 @@ bool MIDI_Class::parse(byte inChannel) } return true; } - else { + else + { // Then update the index of the pending message. mPendingMessageIndex++; @@ -784,9 +742,7 @@ bool MIDI_Class::parse(byte inChannel) // to parse the rest of the message. return parse(inChannel); #endif - } - } // What are our chances to fall here? @@ -797,45 +753,41 @@ bool MIDI_Class::parse(byte inChannel) // Private method: check if the received message is on the listened channel bool MIDI_Class::input_filter(byte inChannel) { + // This method handles recognition of channel + // (to know if the message is destinated to the Arduino) - - // This method handles recognition of channel (to know if the message is destinated to the Arduino) - - - if (mMessage.type == InvalidType) return false; - + if (mMessage.type == InvalidType) + return false; // First, check if the received message is Channel - if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { - + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { // Then we need to know if we listen to it - if ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)) { + if ((mMessage.channel == mInputChannel) || + (mInputChannel == MIDI_CHANNEL_OMNI)) + { return true; - } - else { + else + { // We don't listen to this channel return false; } - } - else { - + else + { // System messages are always received return true; } - } // Private method: reset input attributes void MIDI_Class::reset_input_attributes() { - mPendingMessageIndex = 0; mPendingMessageExpectedLenght = 0; mRunningStatus_RX = InvalidType; - } @@ -846,9 +798,7 @@ void MIDI_Class::reset_input_attributes() */ kMIDIType MIDI_Class::getType() const { - return mMessage.type; - } @@ -859,27 +809,21 @@ kMIDIType MIDI_Class::getType() const */ byte MIDI_Class::getChannel() const { - return mMessage.channel; - } /*! \brief Get the first data byte of the last received message. */ byte MIDI_Class::getData1() const { - return mMessage.data1; - } /*! \brief Get the second data byte of the last received message. */ byte MIDI_Class::getData2() const { - return mMessage.data2; - } @@ -889,9 +833,7 @@ byte MIDI_Class::getData2() const */ const byte * MIDI_Class::getSysExArray() const { - return mMessage.sysex_array; - } /*! \brief Get the lenght of the System Exclusive array. @@ -901,20 +843,15 @@ const byte * MIDI_Class::getSysExArray() const */ unsigned int MIDI_Class::getSysExArrayLength() const { - - unsigned int coded_size = ((unsigned int)(mMessage.data2) << 8) | mMessage.data1; - - return (coded_size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : coded_size; - + const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1; + return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size; } /*! \brief Check if a valid message is stored in the structure. */ bool MIDI_Class::check() const { - return mMessage.valid; - } @@ -926,9 +863,7 @@ bool MIDI_Class::check() const */ void MIDI_Class::setInputChannel(const byte Channel) { - mInputChannel = Channel; - } @@ -962,8 +897,8 @@ void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) */ void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) { - - switch (Type) { + switch (Type) + { case NoteOff: mNoteOffCallback = NULL; break; case NoteOn: mNoteOnCallback = NULL; break; case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; @@ -985,17 +920,15 @@ void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) default: break; } - } // Private - launch callback function based on received type. void MIDI_Class::launchCallback() { - // The order is mixed to allow frequent messages to trigger their callback faster. - - switch (mMessage.type) { + switch (mMessage.type) + { // Notes case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; @@ -1027,7 +960,6 @@ void MIDI_Class::launchCallback() default: break; } - } @@ -1050,31 +982,27 @@ void MIDI_Class::launchCallback() */ void MIDI_Class::setThruFilterMode(kThruFilterMode inThruFilterMode) { - mThruFilterMode = inThruFilterMode; - if (mThruFilterMode != Off) mThruActivated = true; - else mThruActivated = false; - + if (mThruFilterMode != Off) + mThruActivated = true; + else + mThruActivated = false; } /*! \brief Setter method: turn message mirroring on. */ void MIDI_Class::turnThruOn(kThruFilterMode inThruFilterMode) { - mThruActivated = true; mThruFilterMode = inThruFilterMode; - } /*! \brief Setter method: turn message mirroring off. */ void MIDI_Class::turnThruOff() { - mThruActivated = false; mThruFilterMode = Off; - } @@ -1093,45 +1021,60 @@ void MIDI_Class::thru_filter(byte inChannel) */ // If the feature is disabled, don't do anything. - if (!mThruActivated || (mThruFilterMode == Off)) return; + if (!mThruActivated || (mThruFilterMode == Off)) + return; // First, check if the received message is Channel - if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) { - - const bool filter_condition = ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)); + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { + const bool filter_condition = ((mMessage.channel == mInputChannel) || + (mInputChannel == MIDI_CHANNEL_OMNI)); // Now let's pass it to the output - switch (mThruFilterMode) { + switch (mThruFilterMode) + { case Full: - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); return; break; case SameChannel: - if (filter_condition) { - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + if (filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); return; } break; case DifferentChannel: - if (!filter_condition) { - send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + if (!filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); return; } break; case Off: // Do nothing. - // Technically it's impossible to get there because the case was already tested earlier. + // Technically it's impossible to get there because + // the case was already tested earlier. break; default: break; } - } - else { - + else + { // Send the message to the output - switch (mMessage.type) { + switch (mMessage.type) + { // Real Time and 1 byte case Clock: case Start: @@ -1166,12 +1109,8 @@ void MIDI_Class::thru_filter(byte inChannel) break; default: break; - } - } - } - #endif // Thru From 5bda959405687a42259621827a7c614522bcc0f3 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 3 Jul 2012 08:25:40 +0200 Subject: [PATCH 28/38] Fixed bug when receiving 2 byte messages with running status. --- src/MIDI.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 9dbf305..d85c5d1 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -527,7 +527,9 @@ bool MIDI_Class::parse(byte inChannel) case ControlChange: case ProgramChange: case AfterTouchChannel: - case PitchBend: + case PitchBend: + case ProgramChange: + case AfterTouchChannel: // If the status byte is not received, prepend it // to the pending message @@ -540,9 +542,25 @@ bool MIDI_Class::parse(byte inChannel) // Else: well, we received another status byte, // so the running status does not apply here. // It will be updated upon completion of this message. - + + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) + { + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mMessage.valid = true; + return true; + } break; - default: // No running status break; From c87a6c7bb582a90cab5bba333c6e4abc002c4d2a Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 3 Jul 2012 08:31:19 +0200 Subject: [PATCH 29/38] Removed duplicate cae value. --- src/MIDI.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index d85c5d1..dab4704 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -528,8 +528,6 @@ bool MIDI_Class::parse(byte inChannel) case ProgramChange: case AfterTouchChannel: case PitchBend: - case ProgramChange: - case AfterTouchChannel: // If the status byte is not received, prepend it // to the pending message From 5a42cb006dcb0b5e65e099e18a13dc91e7ec08dd Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 4 Sep 2012 09:09:07 +0200 Subject: [PATCH 30/38] Starting quake. --- src/MIDI.h | 3 +- src/midi_Defs.h | 176 +++++++++++++++++++++++++++++++++++++++++++ src/midi_Namespace.h | 21 ++++++ src/midi_Settings.h | 62 +++++++++++++++ 4 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 src/midi_Defs.h create mode 100644 src/midi_Namespace.h create mode 100644 src/midi_Settings.h diff --git a/src/MIDI.h b/src/MIDI.h index e17824a..684c2a7 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -12,7 +12,8 @@ #define LIB_MIDI_H_ #include - +#include "midi_Settings.h" +#include "midi_Defs.h" /* ############################################################### diff --git a/src/midi_Defs.h b/src/midi_Defs.h new file mode 100644 index 0000000..f2836b7 --- /dev/null +++ b/src/midi_Defs.h @@ -0,0 +1,176 @@ +/*! + * @file midi_Defs.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Definitions + * @version 3.5 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 + */ + +#pragma once + +#include +#include "midi_Namespace.h" + +BEGIN_MIDI_NAMESPACE + +#define MIDI_CHANNEL_OMNI 0 +#define MIDI_CHANNEL_OFF 17 // and over + +#define MIDI_PITCHBEND_MIN -8192 +#define MIDI_PITCHBEND_MAX 8191 + + +/*! 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. +}; + + +enum eMIDICCNumber +{ + // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- + BankSelect = 0, + ModulationWheel = 1, + BreathController = 2, + // CC3 undefined + FootController = 4, + PortamentoTime = 5, + DataEntry = 6, + ChannelVolume = 7, + Balance = 8, + // CC9 undefined + Pan = 10, + ExpressionController = 11, + EffectControl1 = 12, + EffectControl2 = 13, + // CC14 undefined + // CC15 undefined + GeneralPurposeController1 = 16, + GeneralPurposeController2 = 17, + GeneralPurposeController3 = 18, + GeneralPurposeController4 = 19, + + // Switches ---------------------------------------------------------------- + Sustain = 64, + Portamento = 65, + Sostenuto = 66, + SoftPedal = 67, + Legato = 68, + Hold2 = 69, + + // Low resolution continuous controllers ----------------------------------- + SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off + SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off + SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off + SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off + SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off + SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off + SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off + SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off + SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off + SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off + GeneralPurposeController5 = 80, + GeneralPurposeController6 = 81, + GeneralPurposeController7 = 82, + GeneralPurposeController8 = 83, + PortamentoControl = 84, + // CC85 to CC90 undefined + Effects1 = 91, ///< Reverb send level + Effects2 = 92, ///< Tremolo depth + Effects3 = 93, ///< Chorus send level + Effects4 = 94, ///< Celeste depth + Effects5 = 95, ///< Phaser depth + + // Channel Mode messages --------------------------------------------------- + AllSoundOff = 120, + ResetAllControllers = 121, + LocalControl = 122, + AllNotesOff = 123, + OmniModeOff = 124, + OmniModeOn = 125, + MonoModeOn = 126, + PolyModeOn = 127 +}; + + + +/*! The midimsg structure contains decoded data + of a MIDI message read from the serial port + with read() or thru(). + */ +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. + */ + 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; + +}; + + +END_MIDI_NAMESPACE diff --git a/src/midi_Namespace.h b/src/midi_Namespace.h new file mode 100644 index 0000000..4ea3e3f --- /dev/null +++ b/src/midi_Namespace.h @@ -0,0 +1,21 @@ +/*! + * @file midi_Namespace.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Namespace declaration + * @version 3.5 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 + */ + +#pragma once + +#define MIDI_NAMESPACE midi +#define BEGIN_MIDI_NAMESPACE namespace MIDI_NAMESPACE { +#define END_MIDI_NAMESPACE } + +#define USING_NAMESPACE_MIDI using namespace MIDI_NAMESPACE; + +BEGIN_MIDI_NAMESPACE + +END_MIDI_NAMESPACE diff --git a/src/midi_Settings.h b/src/midi_Settings.h new file mode 100644 index 0000000..fd51315 --- /dev/null +++ b/src/midi_Settings.h @@ -0,0 +1,62 @@ +/*! + * @file midi_Settings.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Settings + * @version 3.5 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 + */ + +#pragma once + +#include "midi_Namespace.h" + +BEGIN_MIDI_NAMESPACE + +// ----------------------------------------------------------------------------- + +// 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. + +// ----------------------------------------------------------------------------- + +// Compilation flags. Set them to 1 to build the associated feature +// (MIDI in, out, thru), or to 0 to disable the feature and save space. +// Note that the Thru can only work if in and out are enabled. + +#define MIDI_BUILD_INPUT 1 +#define MIDI_BUILD_OUTPUT 1 +#define MIDI_BUILD_THRU 1 + +// ----------------------------------------------------------------------------- +// Serial port configuration + +// 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 + +// Software serial options +#define MIDI_USE_SOFTWARE_SERIAL 0 + +#if MIDI_USE_SOFTWARE_SERIAL + #define MIDI_SOFTSERIAL_RX_PIN 1 // Pin number to use for MIDI Input + #define MIDI_SOFTSERIAL_TX_PIN 2 // Pin number to use for MIDI Output. +#endif + +// ----------------------------------------------------------------------------- +// Misc. options + +// Running status enables short messages when sending multiple values +// of the same type and channel. +// Set to 0 if you have troubles controlling your hardware. +#define MIDI_USE_RUNNING_STATUS 1 +#define MIDI_USE_1BYTE_PARSING 1 + + +#define MIDI_BAUDRATE 31250 +#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. + +END_MIDI_NAMESPACE From 4b8b38aeafd0008bb57f3071a84836ab606dd724 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 5 Sep 2012 18:32:42 +0200 Subject: [PATCH 31/38] Quake wip. --- src/MIDI.cpp | 258 ++++++++------------------- src/MIDI.h | 415 +++++++++++-------------------------------- src/midi_Defs.h | 49 +++-- src/midi_Inline.hpp | 167 +++++++++++++++++ src/midi_Namespace.h | 2 +- src/midi_Settings.h | 29 +-- 6 files changed, 395 insertions(+), 525 deletions(-) create mode 100644 src/midi_Inline.hpp diff --git a/src/MIDI.cpp b/src/MIDI.cpp index dab4704..cafc55a 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -2,7 +2,7 @@ * @file MIDI.cpp * Project Arduino MIDI Library * @brief MIDI Library for the Arduino - * @version 3.2 + * @version 4.0 * @author Francois Best * @date 24/02/11 * license GPL Forty Seven Effects - 2011 @@ -12,6 +12,7 @@ #include #include "Arduino.h" // If using an old (pre-1.0) version of Arduino, // use WConstants.h instead of Arduino.h + #if MIDI_USE_SOFTWARE_SERIAL // Note: Make sure the following relative path is correct. @@ -26,14 +27,17 @@ SoftwareSerial softSerialClass(MIDI_SOFTSERIAL_RX_PIN, #endif // MIDI_USE_SOFTWARE_SERIAL -/*! \brief Main instance (the class comes pre-instantiated). */ -MIDI_Class MIDI; +#if MIDI_AUTO_INSTANCIATE +midi::MidiInterface MIDI; +#endif -/*! \brief Default constructor for MIDI_Class. */ -MIDI_Class::MIDI_Class() +BEGIN_MIDI_NAMESPACE + +/*! \brief Default constructor for MidiInterface. */ +MidiInterface::MidiInterface() { -#if COMPILE_MIDI_IN && USE_CALLBACKS +#if MIDI_BUILD_INPUT && USE_CALLBACKS // Initialise callbacks to NULL pointer mNoteOffCallback = NULL; @@ -59,11 +63,11 @@ MIDI_Class::MIDI_Class() } -/*! \brief Default destructor for MIDI_Class. +/*! \brief Default destructor for MidiInterface. This is not really useful for the Arduino, as it is never called... */ -MIDI_Class::~MIDI_Class() +MidiInterface::~MidiInterface() { } @@ -75,19 +79,19 @@ MIDI_Class::~MIDI_Class() - Input channel set to 1 if no value is specified - Full thru mirroring */ -void MIDI_Class::begin(const byte inChannel) +void MidiInterface::begin(const byte inChannel) { // Initialise the Serial port MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); -#if COMPILE_MIDI_OUT && USE_RUNNING_STATUS +#if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; -#endif // COMPILE_MIDI_OUT && USE_RUNNING_STATUS +#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS -#if COMPILE_MIDI_IN +#if MIDI_BUILD_INPUT mInputChannel = inChannel; mRunningStatus_RX = InvalidType; @@ -100,10 +104,10 @@ void MIDI_Class::begin(const byte inChannel) mMessage.data1 = 0; mMessage.data2 = 0; -#endif // COMPILE_MIDI_IN +#endif // MIDI_BUILD_INPUT -#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru mThruFilterMode = Full; mThruActivated = true; @@ -117,14 +121,7 @@ void MIDI_Class::begin(const byte inChannel) // MIDI Output // ============================================================================= -#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)); -} +#if MIDI_BUILD_OUTPUT /*! \brief Generate and send a MIDI message from the values given. @@ -138,7 +135,7 @@ const byte MIDI_Class::genstatus(const kMIDIType inType, 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, +void MidiInterface::send(MidiType type, byte data1, byte data2, byte channel) @@ -149,7 +146,7 @@ void MIDI_Class::send(kMIDIType type, type < NoteOff) { -#if USE_RUNNING_STATUS +#if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif @@ -164,7 +161,7 @@ void MIDI_Class::send(kMIDIType type, byte statusbyte = genstatus(type,channel); -#if USE_RUNNING_STATUS +#if MIDI_USE_RUNNING_STATUS // Check Running Status if (mRunningStatus_TX != statusbyte) { @@ -198,7 +195,7 @@ void MIDI_Class::send(kMIDIType type, 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, +void MidiInterface::sendNoteOn(byte NoteNumber, byte Velocity, byte Channel) { @@ -217,7 +214,7 @@ void MIDI_Class::sendNoteOn(byte NoteNumber, 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, +void MidiInterface::sendNoteOff(byte NoteNumber, byte Velocity, byte Channel) { @@ -229,7 +226,7 @@ void MIDI_Class::sendNoteOff(byte NoteNumber, \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, +void MidiInterface::sendProgramChange(byte ProgramNumber, byte Channel) { send(ProgramChange,ProgramNumber,0,Channel); @@ -244,7 +241,7 @@ void MIDI_Class::sendProgramChange(byte ProgramNumber, See the detailed controllers numbers & description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums */ -void MIDI_Class::sendControlChange(byte ControlNumber, +void MidiInterface::sendControlChange(byte ControlNumber, byte ControlValue, byte Channel) { @@ -257,7 +254,7 @@ void MIDI_Class::sendControlChange(byte ControlNumber, \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, +void MidiInterface::sendPolyPressure(byte NoteNumber, byte Pressure, byte Channel) { @@ -269,7 +266,7 @@ void MIDI_Class::sendPolyPressure(byte NoteNumber, \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, +void MidiInterface::sendAfterTouch(byte Pressure, byte Channel) { send(AfterTouchChannel,Pressure,0,Channel); @@ -282,7 +279,7 @@ void MIDI_Class::sendAfterTouch(byte Pressure, center value is 0. \param Channel The channel on which the message will be sent (1 to 16). */ -void MIDI_Class::sendPitchBend(int PitchValue, +void MidiInterface::sendPitchBend(int PitchValue, byte Channel) { const unsigned int bend = PitchValue - MIDI_PITCHBEND_MIN; @@ -296,7 +293,7 @@ void MIDI_Class::sendPitchBend(int PitchValue, 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, +void MidiInterface::sendPitchBend(double PitchValue, byte Channel) { const int pitchval = PitchValue * MIDI_PITCHBEND_MAX; @@ -313,7 +310,7 @@ void MIDI_Class::sendPitchBend(double PitchValue, default value for ArrayContainsBoundaries is set to 'false' for compatibility with previous versions of the library. */ -void MIDI_Class::sendSysEx(int length, +void MidiInterface::sendSysEx(int length, const byte *const array, bool ArrayContainsBoundaries) { @@ -332,7 +329,7 @@ void MIDI_Class::sendSysEx(int length, MIDI_SERIAL_PORT.write(array[i]); } -#if USE_RUNNING_STATUS +#if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif } @@ -343,7 +340,7 @@ void MIDI_Class::sendSysEx(int length, When a MIDI unit receives this message, it should tune its oscillators (if equipped with any). */ -void MIDI_Class::sendTuneRequest() +void MidiInterface::sendTuneRequest() { sendRealTime(TuneRequest); } @@ -355,7 +352,7 @@ void MIDI_Class::sendTuneRequest() \param ValuesNibble MTC data See MIDI Specification for more information. */ -void MIDI_Class::sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble) +void MidiInterface::sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble) { const byte data = ( ((TypeNibble & 0x07) << 4) | (ValuesNibble & 0x0F) ); sendTimeCodeQuarterFrame(data); @@ -368,12 +365,12 @@ void MIDI_Class::sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble) \param data if you want to encode directly the nibbles in your program, you can send the byte here. */ -void MIDI_Class::sendTimeCodeQuarterFrame(byte data) +void MidiInterface::sendTimeCodeQuarterFrame(byte data) { MIDI_SERIAL_PORT.write((byte)TimeCodeQuarterFrame); MIDI_SERIAL_PORT.write(data); -#if USE_RUNNING_STATUS +#if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif } @@ -382,25 +379,25 @@ void MIDI_Class::sendTimeCodeQuarterFrame(byte data) /*! \brief Send a Song Position Pointer message. \param Beats The number of beats since the start of the song. */ -void MIDI_Class::sendSongPosition(unsigned int Beats) +void MidiInterface::sendSongPosition(unsigned int Beats) { MIDI_SERIAL_PORT.write((byte)SongPosition); MIDI_SERIAL_PORT.write(Beats & 0x7F); MIDI_SERIAL_PORT.write((Beats >> 7) & 0x7F); -#if USE_RUNNING_STATUS +#if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif } /*! \brief Send a Song Select message */ -void MIDI_Class::sendSongSelect(byte SongNumber) +void MidiInterface::sendSongSelect(byte SongNumber) { MIDI_SERIAL_PORT.write((byte)SongSelect); MIDI_SERIAL_PORT.write(SongNumber & 0x7F); -#if USE_RUNNING_STATUS +#if MIDI_USE_RUNNING_STATUS mRunningStatus_TX = InvalidType; #endif } @@ -411,9 +408,9 @@ void MIDI_Class::sendSongSelect(byte SongNumber) \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 + @see MidiType */ -void MIDI_Class::sendRealTime(kMIDIType Type) +void MidiInterface::sendRealTime(MidiType Type) { switch (Type) { @@ -434,19 +431,19 @@ void MIDI_Class::sendRealTime(kMIDIType Type) // 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 MIDI_USE_RUNNING_STATUS if (Type == TuneRequest) mRunningStatus_TX = InvalidType; #endif } -#endif // COMPILE_MIDI_OUT +#endif // MIDI_BUILD_OUTPUT // ============================================================================= // MIDI Input // ============================================================================= -#if COMPILE_MIDI_IN +#if MIDI_BUILD_INPUT /*! \brief Read a MIDI message from the serial port using the main input channel (see setInputChannel() for reference). @@ -456,7 +453,7 @@ void MIDI_Class::sendRealTime(kMIDIType Type) If the Thru is enabled and the messages matches the filter, it is sent back on the MIDI output. */ -bool MIDI_Class::read() +bool MidiInterface::read() { return read(mInputChannel); } @@ -465,7 +462,7 @@ bool MIDI_Class::read() /*! \brief Reading/thru-ing method, the same as read() with a given input channel to read on. */ -bool MIDI_Class::read(const byte inChannel) +bool MidiInterface::read(const byte inChannel) { if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. @@ -475,7 +472,7 @@ bool MIDI_Class::read(const byte inChannel) if (input_filter(inChannel)) { -#if (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) +#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) thru_filter(inChannel); #endif @@ -491,7 +488,7 @@ bool MIDI_Class::read(const byte inChannel) // Private method: MIDI parser -bool MIDI_Class::parse(byte inChannel) +bool MidiInterface::parse(byte inChannel) { const byte bytes_available = MIDI_SERIAL_PORT.available(); @@ -662,7 +659,7 @@ bool MIDI_Class::parse(byte inChannel) // This is done by leaving the pending message as is, // it will be completed on next calls. - mMessage.type = (kMIDIType)extracted; + mMessage.type = (MidiType)extracted; mMessage.data1 = 0; mMessage.data2 = 0; mMessage.channel = 0; @@ -779,7 +776,7 @@ bool MIDI_Class::parse(byte inChannel) // Private method: check if the received message is on the listened channel -bool MIDI_Class::input_filter(byte inChannel) +bool MidiInterface::input_filter(byte inChannel) { // This method handles recognition of channel // (to know if the message is destinated to the Arduino) @@ -811,7 +808,7 @@ bool MIDI_Class::input_filter(byte inChannel) // Private method: reset input attributes -void MIDI_Class::reset_input_attributes() +void MidiInterface::reset_input_attributes() { mPendingMessageIndex = 0; mPendingMessageExpectedLenght = 0; @@ -819,102 +816,30 @@ void MIDI_Class::reset_input_attributes() } -// 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. - - \return 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 -{ - const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1; - return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size; -} - - -/*! \brief Check if a valid message is stored in the structure. */ -bool MIDI_Class::check() const -{ - return mMessage.valid; -} - - -// 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; } +void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } +void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } +void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } +void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } +void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } +void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } +void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } +void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } +void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } +void MidiInterface::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } +void MidiInterface::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } +void MidiInterface::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } +void MidiInterface::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } +void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } /*! \brief Detach an external function from the given type. @@ -923,7 +848,7 @@ void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) \param Type The type of message to unbind. When a message of this type is received, no function will be called. */ -void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) +void MidiInterface::disconnectCallbackFromType(MidiType Type) { switch (Type) { @@ -952,7 +877,7 @@ void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) // Private - launch callback function based on received type. -void MIDI_Class::launchCallback() +void MidiInterface::launchCallback() { // The order is mixed to allow frequent messages to trigger their callback faster. switch (mMessage.type) @@ -994,49 +919,18 @@ void MIDI_Class::launchCallback() #endif // USE_CALLBACKS -#endif // COMPILE_MIDI_IN +#endif // MIDI_BUILD_INPUT // ============================================================================= // MIDI Soft Thru // ============================================================================= -#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_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; -} - +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // This method is called upon reception of a message // and takes care of Thru filtering and sending. -void MIDI_Class::thru_filter(byte inChannel) +void MidiInterface::thru_filter(byte inChannel) { /* @@ -1142,3 +1036,5 @@ void MIDI_Class::thru_filter(byte inChannel) } #endif // Thru + +END_MIDI_NAMESPACE diff --git a/src/MIDI.h b/src/MIDI.h index 684c2a7..9b344c0 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -2,7 +2,7 @@ * @file MIDI.h * Project Arduino MIDI Library * @brief MIDI Library for the Arduino - * @version 3.2 + * @version 4.0 * @author Francois Best * @date 24/02/11 * license GPL Forty Seven Effects - 2011 @@ -15,313 +15,101 @@ #include "midi_Settings.h" #include "midi_Defs.h" -/* - ############################################################### - # # - # 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. # - # # - ############################################################### +BEGIN_MIDI_NAMESPACE + +/*! \brief The main class for MIDI handling. */ - - -#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 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 MIDI_USE_SOFTWARE_SERIAL 0 // Set to 1 to use SoftwareSerial instead of native serial ports. -#define MIDI_SOFTSERIAL_RX_PIN 1 // This pin number will be used for MIDI Input -#define MIDI_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 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 - -#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. - -#define MIDI_PITCHBEND_MIN -8192 -#define MIDI_PITCHBEND_MAX 8191 - - - -/*! 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. -}; - - -enum eMIDICCNumber -{ - // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- - BankSelect = 0, - ModulationWheel = 1, - BreathController = 2, - // CC3 undefined - FootController = 4, - PortamentoTime = 5, - DataEntry = 6, - ChannelVolume = 7, - Balance = 8, - // CC9 undefined - Pan = 10, - ExpressionController = 11, - EffectControl1 = 12, - EffectControl2 = 13, - // CC14 undefined - // CC15 undefined - GeneralPurposeController1 = 16, - GeneralPurposeController2 = 17, - GeneralPurposeController3 = 18, - GeneralPurposeController4 = 19, - - // Switches ---------------------------------------------------------------- - Sustain = 64, - Portamento = 65, - Sostenuto = 66, - SoftPedal = 67, - Legato = 68, - Hold2 = 69, - - // Low resolution continuous controllers ----------------------------------- - SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off - SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off - SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off - SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off - SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off - SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off - SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off - SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off - SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off - SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off - GeneralPurposeController5 = 80, - GeneralPurposeController6 = 81, - GeneralPurposeController7 = 82, - GeneralPurposeController8 = 83, - PortamentoControl = 84, - // CC85 to CC90 undefined - Effects1 = 91, ///< Reverb send level - Effects2 = 92, ///< Tremolo depth - Effects3 = 93, ///< Chorus send level - Effects4 = 94, ///< Celeste depth - Effects5 = 95, ///< Phaser depth - - // Channel Mode messages --------------------------------------------------- - AllSoundOff = 120, - ResetAllControllers = 121, - LocalControl = 122, - AllNotesOff = 123, - OmniModeOff = 124, - OmniModeOn = 125, - MonoModeOn = 126, - PolyModeOn = 127 -}; - - - -/*! The midimsg structure contains decoded data - of a MIDI message read from the serial port - with read() or thru(). - */ -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. - */ - 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 +//template +class MidiInterface { +public: + MidiInterface(); + ~MidiInterface(); public: - - // ========================================================================= - // Constructor and Destructor - - MIDI_Class(); - ~MIDI_Class(); - - void begin(const byte inChannel = 1); + void begin(byte inChannel = 1); - // ========================================================================= + // ------------------------------------------------------------------------- // MIDI Output -#if COMPILE_MIDI_OUT // Start compilation block - -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(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 - - - - // ========================================================================= - // MIDI Input - -#if COMPILE_MIDI_IN // Start compilation block +#if MIDI_BUILD_OUTPUT public: + void sendNoteOn(byte inNoteNumber, + byte inVelocity, + Channel inChannel); + void sendNoteOff(byte inNoteNumber, + byte inVelocity, + Channel inChannel); + + void sendProgramChange(byte inProgramNumber, + Channel inChannel); + + void sendControlChange(byte inControlNumber, + byte inControlValue, + Channel inChannel); + + void sendPitchBend(int inPitchValue, Channel inChannel); + void sendPitchBend(double inPitchValue, Channel inChannel); + + void sendPolyPressure(byte inNoteNumber, + byte inPressure, + Channel inChannel); + + void sendAfterTouch(byte inPressure, + Channel inChannel); + + void sendSysEx(unsigned int inLength, + const byte* inArray, + bool inArrayContainsBoundaries = false); + + void sendTimeCodeQuarterFrame(byte inTypeNibble, + byte inValuesNibble); + void sendTimeCodeQuarterFrame(byte inData); + + void sendSongPosition(unsigned int inBeats); + void sendSongSelect(byte inSongNumber); + void sendTuneRequest(); + void sendRealTime(MidiType inType); + + void send(MidiType inType, + DataByte inData1, + DataByte inData2, + Channel inChannel); + +private: + inline StatusByte getStatus(MidiType inType, + Channel inChannel) const; + +#endif // MIDI_BUILD_OUTPUT + + + // ------------------------------------------------------------------------- + // MIDI Input + +#if MIDI_BUILD_INPUT + +public: bool read(); - bool read(const byte Channel); + bool read(Channel inChannel); - // Getters - kMIDIType getType() const; - byte getChannel() const; - byte getData1() const; - byte getData2() const; - const byte * getSysExArray() const; - unsigned int getSysExArrayLength() const; - bool check() const; +public: + inline MidiType getType() const; + inline Channel getChannel() const; + inline DataByte getData1() const; + inline DataByte getData2() const; + inline const byte* getSysExArray() const; + inline unsigned int getSysExArrayLength() const; + inline 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; - } +public: + inline Channel getInputChannel() const; + inline void setInputChannel(Channel inChannel); +public: + static inline MidiType getTypeFromStatusByte(const byte inStatus); private: @@ -366,7 +154,7 @@ public: void setHandleActiveSensing(void (*fptr)(void)); void setHandleSystemReset(void (*fptr)(void)); - void disconnectCallbackFromType(kMIDIType Type); + void disconnectCallbackFromType(MidiType Type); private: @@ -393,39 +181,46 @@ private: #endif // USE_CALLBACKS -#endif // COMPILE_MIDI_IN +#endif // MIDI_BUILD_INPUT // ========================================================================= // MIDI Soft Thru -#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) public: + inline MidiFilterMode getFilterMode() const; + inline bool getThruState() const; - // Getters - kThruFilterMode getFilterMode() const { return mThruFilterMode; } - bool getThruState() const { return mThruActivated; } - - - // Setters - void turnThruOn(kThruFilterMode inThruFilterMode = Full); - void turnThruOff(); - - void setThruFilterMode(const kThruFilterMode inThruFilterMode); + inline void turnThruOn(MidiFilterMode inThruFilterMode = Full); + inline void turnThruOff(); + inline void setThruFilterMode(MidiFilterMode inThruFilterMode); private: void thru_filter(byte inChannel); - bool mThruActivated; - kThruFilterMode mThruFilterMode; + bool mThruActivated : 1; + MidiFilterMode mThruFilterMode : 7; #endif // Thru + + + // Attributes +#if MIDI_USE_RUNNING_STATUS + StatusByte mRunningStatus_TX; +#endif // MIDI_USE_RUNNING_STATUS + + }; -extern MIDI_Class MIDI; +END_MIDI_NAMESPACE -#endif // LIB_MIDI_H_ +#if MIDI_AUTO_INSTANCIATE +extern midi::MidiInterface MIDI; +#endif + +#include "midi_Inline.hpp" diff --git a/src/midi_Defs.h b/src/midi_Defs.h index f2836b7..ca3c027 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -2,7 +2,7 @@ * @file midi_Defs.h * Project Arduino MIDI Library * @brief MIDI Library for the Arduino - Definitions - * @version 3.5 + * @version 4.0 * @author Francois Best * @date 24/02/11 * license GPL Forty Seven Effects - 2011 @@ -15,23 +15,31 @@ BEGIN_MIDI_NAMESPACE +// ----------------------------------------------------------------------------- + #define MIDI_CHANNEL_OMNI 0 -#define MIDI_CHANNEL_OFF 17 // and over +#define MIDI_CHANNEL_OFF 17 // and over #define MIDI_PITCHBEND_MIN -8192 #define MIDI_PITCHBEND_MAX 8191 +// ----------------------------------------------------------------------------- +// Type definitions -/*! Type definition for practical use - (because "unsigned char" is a bit long to write.. ) - */ typedef uint8_t byte; typedef uint16_t word; +typedef byte StatusByte; +typedef byte DataByte; +typedef byte Channel; +typedef byte FilterMode; + +// ----------------------------------------------------------------------------- /*! Enumeration of MIDI types */ -enum kMIDIType +enum MidiType { + InvalidType = 0x00, ///< For notifying errors NoteOff = 0x80, ///< Note Off NoteOn = 0x90, ///< Note On AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch @@ -50,19 +58,22 @@ 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 }; +// ----------------------------------------------------------------------------- + /*! Enumeration of Thru filter modes */ -enum kThruFilterMode { +enum MidiFilterMode +{ 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. + DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back. }; +// ----------------------------------------------------------------------------- -enum eMIDICCNumber +enum MidiControlChangeNumber { // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- BankSelect = 0, @@ -92,7 +103,7 @@ enum eMIDICCNumber Sostenuto = 66, SoftPedal = 67, Legato = 68, - Hold2 = 69, + Hold = 69, // Low resolution continuous controllers ----------------------------------- SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off @@ -129,40 +140,41 @@ enum eMIDICCNumber }; +// ----------------------------------------------------------------------------- /*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). */ -struct midimsg +struct Message { /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ - byte channel; + Channel channel; /*! The type of the message - (see the define section for types reference) + (see the MidiType enum for types reference) */ - kMIDIType type; + MidiType type; /*! The first data byte. \n Value goes from 0 to 127. */ - byte data1; + DataByte 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; + DataByte 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]; + DataByte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; /*! This boolean indicates if the message is valid or not. There is no channel consideration here, @@ -172,5 +184,4 @@ struct midimsg }; - END_MIDI_NAMESPACE diff --git a/src/midi_Inline.hpp b/src/midi_Inline.hpp new file mode 100644 index 0000000..ddd2716 --- /dev/null +++ b/src/midi_Inline.hpp @@ -0,0 +1,167 @@ +/*! + * @file midi_Inline.hpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Inline implementations + * @version 4.0 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 + */ + +#pragma once + +BEGIN_MIDI_NAMESPACE + +#if MIDI_BUILD_OUTPUT + +StatusByte MidiInterface::getStatus(kMIDIType inType, + Channel inChannel) const +{ + return ((byte)inType | ((inChannel - 1) & 0x0F)); +} + +#endif // MIDI_BUILD_OUTPUT + +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_INPUT + +/*! \brief Get the last received message's type + + Returns an enumerated type. @see MidiType + */ +MidiType MidiInterface::getType() const +{ + return mMessage.type; +} + +/*! \brief Get the channel of the message stored in the structure. + + \return Channel range is 1 to 16. + For non-channel messages, this will return 0. + */ +Channel MidiInterface::getChannel() const +{ + return mMessage.channel; +} + +/*! \brief Get the first data byte of the last received message. */ +DataByte MidiInterface::getData1() const +{ + return mMessage.data1; +} + +/*! \brief Get the second data byte of the last received message. */ +DataByte MidiInterface::getData2() const +{ + return mMessage.data2; +} + +/*! \brief Get the System Exclusive byte array. + + @see getSysExArrayLength to get the array's length in bytes. + */ +const byte* MidiInterface::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 MidiInterface::getSysExArrayLength() const +{ + const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1; + return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size; +} + +/*! \brief Check if a valid message is stored in the structure. */ +bool MidiInterface::check() const +{ + return mMessage.valid; +} + +// ----------------------------------------------------------------------------- + +Channel MidiInterface::getInputChannel() const +{ + return mInputChannel; +} + +/*! \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 input. +*/ +void MidiInterface::setInputChannel(Channel inChannel) +{ + mInputChannel = inChannel; +} + +// ----------------------------------------------------------------------------- + +/*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, + made public so you can handle MidiTypes more easily. + */ +MidiType MidiInterface::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 (MidiType)(inStatus & 0xF0); // Channel message, remove channel nibble. + else return (MidiType)inStatus; +} + +#endif // MIDI_BUILD_INPUT + +// ----------------------------------------------------------------------------- + +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) + +MidiFilterMode MidiInterface::getFilterMode() const +{ + return mThruFilterMode; +} + +bool MidiInterface::getThruState() const +{ + return mThruActivated; +} + +/*! \brief Setter method: turn message mirroring on. */ +void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) +{ + mThruActivated = true; + mThruFilterMode = inThruFilterMode; +} + + +/*! \brief Setter method: turn message mirroring off. */ +void MidiInterface::turnThruOff() +{ + mThruActivated = false; + mThruFilterMode = Off; +} + +/*! \brief Set the filter for thru mirroring + \param inThruFilterMode a filter mode + + @see MidiFilterMode + */ +void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode) +{ + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) + mThruActivated = true; + else + mThruActivated = false; +} + +#endif // MIDI_BUILD_THRU + +END_MIDI_NAMESPACE diff --git a/src/midi_Namespace.h b/src/midi_Namespace.h index 4ea3e3f..0867ab7 100644 --- a/src/midi_Namespace.h +++ b/src/midi_Namespace.h @@ -2,7 +2,7 @@ * @file midi_Namespace.h * Project Arduino MIDI Library * @brief MIDI Library for the Arduino - Namespace declaration - * @version 3.5 + * @version 4.0 * @author Francois Best * @date 24/02/11 * license GPL Forty Seven Effects - 2011 diff --git a/src/midi_Settings.h b/src/midi_Settings.h index fd51315..f835c2a 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -27,23 +27,25 @@ BEGIN_MIDI_NAMESPACE // (MIDI in, out, thru), or to 0 to disable the feature and save space. // Note that the Thru can only work if in and out are enabled. -#define MIDI_BUILD_INPUT 1 -#define MIDI_BUILD_OUTPUT 1 -#define MIDI_BUILD_THRU 1 +#define MIDI_BUILD_INPUT 1 +#define MIDI_BUILD_OUTPUT 1 +#define MIDI_BUILD_THRU 1 + +// Create a MIDI object automatically on the port defined with MIDI_SERIAL_PORT. +#define MIDI_AUTO_INSTANCIATE 1 // ----------------------------------------------------------------------------- // Serial port configuration -// 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 +// Set the default port to use for MIDI. +#define MIDI_SERIAL_PORT Serial // Software serial options -#define MIDI_USE_SOFTWARE_SERIAL 0 +#define MIDI_USE_SOFTWARE_SERIAL 0 #if MIDI_USE_SOFTWARE_SERIAL - #define MIDI_SOFTSERIAL_RX_PIN 1 // Pin number to use for MIDI Input - #define MIDI_SOFTSERIAL_TX_PIN 2 // Pin number to use for MIDI Output. + #define MIDI_SOFTSERIAL_RX_PIN 1 // Pin number to use for MIDI Input + #define MIDI_SOFTSERIAL_TX_PIN 2 // Pin number to use for MIDI Output #endif // ----------------------------------------------------------------------------- @@ -52,11 +54,10 @@ BEGIN_MIDI_NAMESPACE // Running status enables short messages when sending multiple values // of the same type and channel. // Set to 0 if you have troubles controlling your hardware. -#define MIDI_USE_RUNNING_STATUS 1 -#define MIDI_USE_1BYTE_PARSING 1 +#define MIDI_USE_RUNNING_STATUS 1 +#define MIDI_USE_1BYTE_PARSING 1 - -#define MIDI_BAUDRATE 31250 -#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. +#define MIDI_BAUDRATE 31250 +#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes. END_MIDI_NAMESPACE From 5078e0527e5e9b9092b87707fcbc5f8c6de70394 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Wed, 5 Sep 2012 18:58:37 +0200 Subject: [PATCH 32/38] Experiment with templates. --- src/MIDI.cpp | 1036 +---------------------------------- src/MIDI.h | 20 +- src/MIDI.hpp | 1268 +++++++++++++++++++++++++++++++++++++++++++ src/midi_Inline.hpp | 150 ----- src/midi_Settings.h | 2 + 5 files changed, 1298 insertions(+), 1178 deletions(-) create mode 100644 src/MIDI.hpp diff --git a/src/MIDI.cpp b/src/MIDI.cpp index cafc55a..15334d6 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -9,1032 +9,26 @@ */ #include "MIDI.h" -#include -#include "Arduino.h" // If using an old (pre-1.0) version of Arduino, - // use WConstants.h instead of Arduino.h - -#if MIDI_USE_SOFTWARE_SERIAL - -// Note: Make sure the following relative path is correct. -#include "../SoftwareSerial/SoftwareSerial.h" -SoftwareSerial softSerialClass(MIDI_SOFTSERIAL_RX_PIN, - MIDI_SOFTSERIAL_TX_PIN); -#undef MIDI_SERIAL_PORT -#define MIDI_SERIAL_PORT softSerialClass - -#else -#include "HardwareSerial.h" -#endif // MIDI_USE_SOFTWARE_SERIAL +// ----------------------------------------------------------------------------- #if MIDI_AUTO_INSTANCIATE -midi::MidiInterface MIDI; -#endif + #if MIDI_USE_SOFTWARE_SERIAL + #include "../SoftwareSerial/SoftwareSerial.h" + SoftwareSerial softSerialClass(MIDI_SOFTSERIAL_RX_PIN, + MIDI_SOFTSERIAL_TX_PIN); + #undef MIDI_SERIAL_PORT + #define MIDI_SERIAL_PORT softSerialClass + #else + #include "HardwareSerial.h" + #endif // MIDI_USE_SOFTWARE_SERIAL + MIDI_NAMESPACE::MidiInterface MIDI; + +#endif // MIDI_AUTO_INSTANCIATE + +// ----------------------------------------------------------------------------- BEGIN_MIDI_NAMESPACE -/*! \brief Default constructor for MidiInterface. */ -MidiInterface::MidiInterface() -{ -#if MIDI_BUILD_INPUT && 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 MidiInterface. - - This is not really useful for the Arduino, as it is never called... - */ -MidiInterface::~MidiInterface() -{ - -} - - -/*! \brief Call the begin method in the setup() function of the Arduino. - - All parameters are set to their default values: - - Input channel set to 1 if no value is specified - - Full thru mirroring - */ -void MidiInterface::begin(const byte inChannel) -{ - // Initialise the Serial port - MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); - -#if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS - - mRunningStatus_TX = InvalidType; - -#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS - - -#if MIDI_BUILD_INPUT - - mInputChannel = inChannel; - mRunningStatus_RX = InvalidType; - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = false; - mMessage.type = InvalidType; - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - -#endif // MIDI_BUILD_INPUT - - -#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru - - mThruFilterMode = Full; - mThruActivated = true; - -#endif // Thru - -} - - -// ============================================================================= -// MIDI Output -// ============================================================================= - -#if MIDI_BUILD_OUTPUT - - -/*! \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 MidiInterface::send(MidiType 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 MIDI_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 MIDI_USE_RUNNING_STATUS - // Check Running Status - if (mRunningStatus_TX != statusbyte) - { - // New message, memorise and send header - mRunningStatus_TX = statusbyte; - MIDI_SERIAL_PORT.write(mRunningStatus_TX); - } -#else - // Don't care about running status, send the Control byte. - MIDI_SERIAL_PORT.write(statusbyte); -#endif - - // Then send data - MIDI_SERIAL_PORT.write(data1); - if (type != ProgramChange && type != AfterTouchChannel) - MIDI_SERIAL_PORT.write(data2); - - return; - } - if (type >= TuneRequest && type <= SystemReset) - sendRealTime(type); // System Real-time and 1 byte. -} - - -/*! \brief Send a Note On message - \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 MidiInterface::sendNoteOn(byte NoteNumber, - byte Velocity, - byte Channel) -{ - send(NoteOn,NoteNumber,Velocity,Channel); -} - - -/*! \brief Send a Note Off message - \param NoteNumber Pitch value in the MIDI format (0 to 127). - \param Velocity Release velocity (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). - - Note: you can send NoteOn with zero velocity to make a NoteOff, this is based - on the Running Status principle, to avoid sending status messages and thus - sending only NoteOn data. This method will always send a real NoteOff message. - Take a look at the values, names and frequencies of notes here: - http://www.phys.unsw.edu.au/jw/notes.html - */ -void MidiInterface::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 MidiInterface::sendProgramChange(byte ProgramNumber, - byte Channel) -{ - send(ProgramChange,ProgramNumber,0,Channel); -} - - -/*! \brief Send a Control Change message - \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). - - See the detailed controllers numbers & description here: - http://www.somascape.org/midi/tech/spec.html#ctrlnums - */ -void MidiInterface::sendControlChange(byte ControlNumber, - byte ControlValue, - byte Channel) -{ - send(ControlChange,ControlNumber,ControlValue,Channel); -} - - -/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) - \param NoteNumber The note to apply AfterTouch to (0 to 127). - \param Pressure The amount of AfterTouch to apply (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). - */ -void MidiInterface::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 MidiInterface::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 MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX, - center value is 0. - \param Channel The channel on which the message will be sent (1 to 16). - */ -void MidiInterface::sendPitchBend(int PitchValue, - byte Channel) -{ - const unsigned int bend = PitchValue - MIDI_PITCHBEND_MIN; - send(PitchBend,(bend & 0x7F),(bend >> 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 MidiInterface::sendPitchBend(double PitchValue, - byte Channel) -{ - const int pitchval = PitchValue * MIDI_PITCHBEND_MAX; - 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 for ArrayContainsBoundaries is set to 'false' for compatibility - with previous versions of the library. - */ -void MidiInterface::sendSysEx(int length, - const byte *const array, - bool ArrayContainsBoundaries) -{ - if (ArrayContainsBoundaries == false) - { - MIDI_SERIAL_PORT.write(0xF0); - - for (int i=0;i> 7) & 0x7F); - -#if MIDI_USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; -#endif -} - - -/*! \brief Send a Song Select message */ -void MidiInterface::sendSongSelect(byte SongNumber) -{ - MIDI_SERIAL_PORT.write((byte)SongSelect); - MIDI_SERIAL_PORT.write(SongNumber & 0x7F); - -#if MIDI_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 MidiType - */ -void MidiInterface::sendRealTime(MidiType 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: - MIDI_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 MIDI_USE_RUNNING_STATUS - if (Type == TuneRequest) mRunningStatus_TX = InvalidType; -#endif -} - -#endif // MIDI_BUILD_OUTPUT - - -// ============================================================================= -// MIDI Input -// ============================================================================= - -#if MIDI_BUILD_INPUT - -/*! \brief Read a MIDI message from the serial port - using the main input channel (see setInputChannel() for reference). - - \return True if a valid message has been stored in the structure, false if not. - A valid message is a message that matches the input channel. \n\n - If the Thru is enabled and the messages matches the filter, - it is sent back on the MIDI output. - */ -bool MidiInterface::read() -{ - return read(mInputChannel); -} - - -/*! \brief Reading/thru-ing method, the same as read() - with a given input channel to read on. - */ -bool MidiInterface::read(const byte inChannel) -{ - if (inChannel >= MIDI_CHANNEL_OFF) - return false; // MIDI Input disabled. - - if (parse(inChannel)) - { - if (input_filter(inChannel)) - { - -#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) - thru_filter(inChannel); -#endif - -#if USE_CALLBACKS - launchCallback(); -#endif - return true; - } - } - - return false; -} - - -// Private method: MIDI parser -bool MidiInterface::parse(byte inChannel) -{ - const byte bytes_available = MIDI_SERIAL_PORT.available(); - - if (bytes_available == 0) - // No data available. - return false; - - - /* Parsing algorithm: - Get a byte from the serial buffer. - * If there is no pending message to be recomposed, start a new one. - - Find type and channel (if pertinent) - - Look for other bytes in buffer, call parser recursively, - until the message is assembled or the buffer is empty. - * Else, add the extracted byte to the pending message, and check validity. - When the message is done, store it. - */ - - const byte extracted = MIDI_SERIAL_PORT.read(); - - if (mPendingMessageIndex == 0) - { - // Start a new pending message - mPendingMessage[0] = extracted; - - // Check for running status first - switch (getTypeFromStatusByte(mRunningStatus_RX)) - { - // Only these types allow Running Status: - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - - // If the status byte is not received, prepend it - // to the pending message - if (extracted < 0x80) - { - mPendingMessage[0] = mRunningStatus_RX; - mPendingMessage[1] = extracted; - mPendingMessageIndex = 1; - } - // Else: well, we received another status byte, - // so the running status does not apply here. - // It will be updated upon completion of this message. - - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) - { - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; - mMessage.data1 = mPendingMessage[1]; - - // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) - mMessage.data2 = mPendingMessage[2]; - else - mMessage.data2 = 0; - - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - mMessage.valid = true; - return true; - } - break; - default: - // No running status - break; - } - - - switch (getTypeFromStatusByte(mPendingMessage[0])) - { - // 1 byte messages - case Start: - case Continue: - case Stop: - case Clock: - case ActiveSensing: - case SystemReset: - case TuneRequest: - // Handle the message type directly here. - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.valid = true; - - // \fix Running Status broken when receiving Clock messages. - // Do not reset all input attributes, Running Status must remain unchanged. - //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: - // The message can be any lenght - // between 3 and MIDI_SYSEX_ARRAY_SIZE bytes - mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; - mRunningStatus_RX = InvalidType; - break; - - case InvalidType: - default: - // This is obviously wrong. Let's get the hell out'a here. - 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: - - // Here we will have to extract the one-byte message, - // pass it to the structure for being read outside - // the MIDI class, and recompose the message it was - // interleaved into. Oh, and without killing the running status.. - // This is done by leaving the pending message as is, - // it will be completed on next calls. - - mMessage.type = (MidiType)extracted; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.channel = 0; - mMessage.valid = true; - return true; - - break; - - // End of Exclusive - case 0xF7: - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) - { - // Store System Exclusive array in midimsg structure - for (byte i=0;i> 8; - mMessage.channel = 0; - mMessage.valid = true; - - 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]); - // Don't check if it is a Channel Message - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; - - mMessage.data1 = mPendingMessage[1]; - - // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) - mMessage.data2 = mPendingMessage[2]; - else - mMessage.data2 = 0; - - // Reset local variables - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = true; - - // Activate running status (if enabled for the received type) - switch (mMessage.type) - { - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - // Running status enabled: store it from received message - mRunningStatus_RX = mPendingMessage[0]; - break; - - default: - // No running status - mRunningStatus_RX = InvalidType; - break; - } - return true; - } - else - { - // Then update the index of the pending message. - mPendingMessageIndex++; - -#if USE_1BYTE_PARSING - // Message is not complete. - return false; -#else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); -#endif - } - } - - // What are our chances to fall here? - return false; -} - - -// Private method: check if the received message is on the listened channel -bool MidiInterface::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 MidiInterface::reset_input_attributes() -{ - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - mRunningStatus_RX = InvalidType; -} - - - - - - -#if USE_CALLBACKS - -void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } -void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } -void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } -void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } -void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } -void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } -void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } -void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } -void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } -void MidiInterface::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } -void MidiInterface::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } -void MidiInterface::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } -void MidiInterface::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } -void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } -void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } -void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } -void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } -void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } - - -/*! \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 MidiInterface::disconnectCallbackFromType(MidiType 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 MidiInterface::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)) + MIDI_PITCHBEND_MIN); 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 // MIDI_BUILD_INPUT - - -// ============================================================================= -// MIDI Soft Thru -// ============================================================================= - -#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) - -// This method is called upon reception of a message -// and takes care of Thru filtering and sending. -void MidiInterface::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 - END_MIDI_NAMESPACE diff --git a/src/MIDI.h b/src/MIDI.h index 9b344c0..64f3bf7 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -8,18 +8,21 @@ * license GPL Forty Seven Effects - 2011 */ -#ifndef LIB_MIDI_H_ -#define LIB_MIDI_H_ +#pragma once #include #include "midi_Settings.h" #include "midi_Defs.h" +#include +#include "Arduino.h" + +// ----------------------------------------------------------------------------- BEGIN_MIDI_NAMESPACE /*! \brief The main class for MIDI handling. */ -//template +template class MidiInterface { public: @@ -29,7 +32,6 @@ public: public: void begin(byte inChannel = 1); - // ------------------------------------------------------------------------- // MIDI Output @@ -131,7 +133,7 @@ private: // ========================================================================= // Input Callbacks -#if USE_CALLBACKS +#if MIDI_USE_CALLBACKS public: @@ -179,7 +181,7 @@ private: void (*mActiveSensingCallback)(void); void (*mSystemResetCallback)(void); -#endif // USE_CALLBACKS +#endif // MIDI_USE_CALLBACKS #endif // MIDI_BUILD_INPUT @@ -219,8 +221,12 @@ private: END_MIDI_NAMESPACE +// ----------------------------------------------------------------------------- + #if MIDI_AUTO_INSTANCIATE -extern midi::MidiInterface MIDI; +extern MIDI_NAMESPACE::MidiInterface MIDI; #endif +// ----------------------------------------------------------------------------- + #include "midi_Inline.hpp" diff --git a/src/MIDI.hpp b/src/MIDI.hpp new file mode 100644 index 0000000..f4d7014 --- /dev/null +++ b/src/MIDI.hpp @@ -0,0 +1,1268 @@ +/*! + * @file MIDI.cpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @version 4.0 + * @author Francois Best + * @date 24/02/11 + * license GPL Forty Seven Effects - 2011 + */ + +#pragma once + +BEGIN_MIDI_NAMESPACE + +// ----------------------------------------------------------------------------- + +template +MidiInterface::MidiInterface() +{ +#if MIDI_BUILD_INPUT && MIDI_USE_CALLBACKS + mNoteOffCallback = 0; + mNoteOnCallback = 0; + mAfterTouchPolyCallback = 0; + mControlChangeCallback = 0; + mProgramChangeCallback = 0; + mAfterTouchChannelCallback = 0; + mPitchBendCallback = 0; + mSystemExclusiveCallback = 0; + mTimeCodeQuarterFrameCallback = 0; + mSongPositionCallback = 0; + mSongSelectCallback = 0; + mTuneRequestCallback = 0; + mClockCallback = 0; + mStartCallback = 0; + mContinueCallback = 0; + mStopCallback = 0; + mActiveSensingCallback = 0; + mSystemResetCallback = 0; +#endif +} + +template +MidiInterface::~MidiInterface() +{ +} + +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_OUTPUT + +template +StatusByte MidiInterface::getStatus(kMIDIType inType, + Channel inChannel) const +{ + return ((byte)inType | ((inChannel - 1) & 0x0F)); +} + +#endif // MIDI_BUILD_OUTPUT + +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_INPUT + +/*! \brief Get the last received message's type + + Returns an enumerated type. @see MidiType + */ +template +MidiType MidiInterface::getType() const +{ + return mMessage.type; +} + +/*! \brief Get the channel of the message stored in the structure. + + \return Channel range is 1 to 16. + For non-channel messages, this will return 0. + */ +template +Channel MidiInterface::getChannel() const +{ + return mMessage.channel; +} + +/*! \brief Get the first data byte of the last received message. */ +template +DataByte MidiInterface::getData1() const +{ + return mMessage.data1; +} + +/*! \brief Get the second data byte of the last received message. */ +template +DataByte MidiInterface::getData2() const +{ + return mMessage.data2; +} + +/*! \brief Get the System Exclusive byte array. + + @see getSysExArrayLength to get the array's length in bytes. + */ +template +const byte* MidiInterface::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. + */ +template +unsigned int MidiInterface::getSysExArrayLength() const +{ + const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1; + return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size; +} + +/*! \brief Check if a valid message is stored in the structure. */ +template +bool MidiInterface::check() const +{ + return mMessage.valid; +} + +// ----------------------------------------------------------------------------- + +template +Channel MidiInterface::getInputChannel() const +{ + return mInputChannel; +} + +/*! \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 input. + */ +template +void MidiInterface::setInputChannel(Channel inChannel) +{ + mInputChannel = inChannel; +} + +// ----------------------------------------------------------------------------- + +/*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, + made public so you can handle MidiTypes more easily. + */ +template +MidiType MidiInterface::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 (MidiType)(inStatus & 0xF0); // Channel message, remove channel nibble. + else return (MidiType)inStatus; +} + +#endif // MIDI_BUILD_INPUT + +// ----------------------------------------------------------------------------- + +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) + +template +MidiFilterMode MidiInterface::getFilterMode() const +{ + return mThruFilterMode; +} + +template +bool MidiInterface::getThruState() const +{ + return mThruActivated; +} + +/*! \brief Setter method: turn message mirroring on. */ +template +void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) +{ + mThruActivated = true; + mThruFilterMode = inThruFilterMode; +} + + +/*! \brief Setter method: turn message mirroring off. */ +template +void MidiInterface::turnThruOff() +{ + mThruActivated = false; + mThruFilterMode = Off; +} + +/*! \brief Set the filter for thru mirroring + \param inThruFilterMode a filter mode + + @see MidiFilterMode + */ +template +void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode) +{ + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) + mThruActivated = true; + else + mThruActivated = false; +} + +#endif // MIDI_BUILD_THRU + +END_MIDI_NAMESPACE + +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// +// To sort below these lines. +// +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- + + +BEGIN_MIDI_NAMESPACE + +/*! \brief Default constructor for MidiInterface. */ +MidiInterface::MidiInterface() +{ +#if MIDI_BUILD_INPUT && MIDI_USE_CALLBACKS + + // Initialise callbacks to 0 pointer + mNoteOffCallback = 0; + mNoteOnCallback = 0; + mAfterTouchPolyCallback = 0; + mControlChangeCallback = 0; + mProgramChangeCallback = 0; + mAfterTouchChannelCallback = 0; + mPitchBendCallback = 0; + mSystemExclusiveCallback = 0; + mTimeCodeQuarterFrameCallback = 0; + mSongPositionCallback = 0; + mSongSelectCallback = 0; + mTuneRequestCallback = 0; + mClockCallback = 0; + mStartCallback = 0; + mContinueCallback = 0; + mStopCallback = 0; + mActiveSensingCallback = 0; + mSystemResetCallback = 0; + +#endif +} + + +/*! \brief Default destructor for MidiInterface. + + This is not really useful for the Arduino, as it is never called... + */ +MidiInterface::~MidiInterface() +{ + +} + + +/*! \brief Call the begin method in the setup() function of the Arduino. + + All parameters are set to their default values: + - Input channel set to 1 if no value is specified + - Full thru mirroring + */ +template +void MidiInterface::begin(const byte inChannel) +{ + // Initialise the Serial port + MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); + +#if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS + + mRunningStatus_TX = InvalidType; + +#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS + + +#if MIDI_BUILD_INPUT + + mInputChannel = inChannel; + mRunningStatus_RX = InvalidType; + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = false; + mMessage.type = InvalidType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + +#endif // MIDI_BUILD_INPUT + + +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru + + mThruFilterMode = Full; + mThruActivated = true; + +#endif // Thru + +} + + +// ============================================================================= +// MIDI Output +// ============================================================================= + +#if MIDI_BUILD_OUTPUT + + +/*! \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. + */ +template +void MidiInterface::send(MidiType 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 MIDI_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 MIDI_USE_RUNNING_STATUS + // Check Running Status + if (mRunningStatus_TX != statusbyte) + { + // New message, memorise and send header + mRunningStatus_TX = statusbyte; + MIDI_SERIAL_PORT.write(mRunningStatus_TX); + } +#else + // Don't care about running status, send the Control byte. + MIDI_SERIAL_PORT.write(statusbyte); +#endif + + // Then send data + MIDI_SERIAL_PORT.write(data1); + if (type != ProgramChange && type != AfterTouchChannel) + MIDI_SERIAL_PORT.write(data2); + + return; + } + if (type >= TuneRequest && type <= SystemReset) + sendRealTime(type); // System Real-time and 1 byte. +} + + +/*! \brief Send a Note On message + \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 + */ +template +void MidiInterface::sendNoteOn(byte NoteNumber, + byte Velocity, + byte Channel) +{ + send(NoteOn,NoteNumber,Velocity,Channel); +} + + +/*! \brief Send a Note Off message + \param NoteNumber Pitch value in the MIDI format (0 to 127). + \param Velocity Release velocity (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + + Note: you can send NoteOn with zero velocity to make a NoteOff, this is based + on the Running Status principle, to avoid sending status messages and thus + sending only NoteOn data. This method will always send a real NoteOff message. + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html + */ +template +void MidiInterface::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). + */ +template +void MidiInterface::sendProgramChange(byte ProgramNumber, + byte Channel) +{ + send(ProgramChange,ProgramNumber,0,Channel); +} + + +/*! \brief Send a Control Change message + \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). + + See the detailed controllers numbers & description here: + http://www.somascape.org/midi/tech/spec.html#ctrlnums + */ +template +void MidiInterface::sendControlChange(byte ControlNumber, + byte ControlValue, + byte Channel) +{ + send(ControlChange,ControlNumber,ControlValue,Channel); +} + + +/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) + \param NoteNumber The note to apply AfterTouch to (0 to 127). + \param Pressure The amount of AfterTouch to apply (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::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). + */ +template +void MidiInterface::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 MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX, + center value is 0. + \param Channel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendPitchBend(int PitchValue, + byte Channel) +{ + const unsigned int bend = PitchValue - MIDI_PITCHBEND_MIN; + send(PitchBend,(bend & 0x7F),(bend >> 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). + */ +template +void MidiInterface::sendPitchBend(double PitchValue, + byte Channel) +{ + const int pitchval = PitchValue * MIDI_PITCHBEND_MAX; + 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 for ArrayContainsBoundaries is set to 'false' for compatibility + with previous versions of the library. + */ +template +void MidiInterface::sendSysEx(int length, + const byte *const array, + bool ArrayContainsBoundaries) +{ + if (ArrayContainsBoundaries == false) + { + MIDI_SERIAL_PORT.write(0xF0); + + for (int i=0;i +void MidiInterface::sendTuneRequest() +{ + sendRealTime(TuneRequest); +} + + +/*! \brief Send a MIDI Time Code Quarter Frame. + + \param TypeNibble MTC type + \param ValuesNibble MTC data + See MIDI Specification for more information. + */ +template +void MidiInterface::sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble) +{ + const byte data = ( ((TypeNibble & 0x07) << 4) | (ValuesNibble & 0x0F) ); + sendTimeCodeQuarterFrame(data); +} + + +/*! \brief Send a MIDI Time Code Quarter Frame. + + See MIDI Specification for more information. + \param data if you want to encode directly the nibbles in your program, + you can send the byte here. + */ +template +void MidiInterface::sendTimeCodeQuarterFrame(byte data) +{ + MIDI_SERIAL_PORT.write((byte)TimeCodeQuarterFrame); + MIDI_SERIAL_PORT.write(data); + +#if MIDI_USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif +} + + +/*! \brief Send a Song Position Pointer message. + \param Beats The number of beats since the start of the song. + */ +template +void MidiInterface::sendSongPosition(unsigned int Beats) +{ + MIDI_SERIAL_PORT.write((byte)SongPosition); + MIDI_SERIAL_PORT.write(Beats & 0x7F); + MIDI_SERIAL_PORT.write((Beats >> 7) & 0x7F); + +#if MIDI_USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif +} + + +/*! \brief Send a Song Select message */ +template +void MidiInterface::sendSongSelect(byte SongNumber) +{ + MIDI_SERIAL_PORT.write((byte)SongSelect); + MIDI_SERIAL_PORT.write(SongNumber & 0x7F); + +#if MIDI_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 MidiType + */ +template +void MidiInterface::sendRealTime(MidiType 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: + MIDI_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 MIDI_USE_RUNNING_STATUS + if (Type == TuneRequest) mRunningStatus_TX = InvalidType; +#endif +} + +#endif // MIDI_BUILD_OUTPUT + + +// ============================================================================= +// MIDI Input +// ============================================================================= + +#if MIDI_BUILD_INPUT + +/*! \brief Read a MIDI message from the serial port + using the main input channel (see setInputChannel() for reference). + + \return True if a valid message has been stored in the structure, false if not. + A valid message is a message that matches the input channel. \n\n + If the Thru is enabled and the messages matches the filter, + it is sent back on the MIDI output. + */ +template +bool MidiInterface::read() +{ + return read(mInputChannel); +} + + +/*! \brief Reading/thru-ing method, the same as read() + with a given input channel to read on. + */ +template +bool MidiInterface::read(const byte inChannel) +{ + if (inChannel >= MIDI_CHANNEL_OFF) + return false; // MIDI Input disabled. + + if (parse(inChannel)) + { + if (input_filter(inChannel)) + { + +#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) + thru_filter(inChannel); +#endif + +#if MIDI_USE_CALLBACKS + launchCallback(); +#endif + return true; + } + } + + return false; +} + + +// Private method: MIDI parser +template +bool MidiInterface::parse(byte inChannel) +{ + const byte bytes_available = MIDI_SERIAL_PORT.available(); + + if (bytes_available == 0) + // No data available. + return false; + + + /* Parsing algorithm: + Get a byte from the serial buffer. + * If there is no pending message to be recomposed, start a new one. + - Find type and channel (if pertinent) + - Look for other bytes in buffer, call parser recursively, + until the message is assembled or the buffer is empty. + * Else, add the extracted byte to the pending message, and check validity. + When the message is done, store it. + */ + + const byte extracted = MIDI_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) + { + // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + switch (getTypeFromStatusByte(mRunningStatus_RX)) + { + // Only these types allow Running Status: + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + + // If the status byte is not received, prepend it + // to the pending message + if (extracted < 0x80) + { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, + // so the running status does not apply here. + // It will be updated upon completion of this message. + + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) + { + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mMessage.valid = true; + return true; + } + break; + default: + // No running status + break; + } + + + switch (getTypeFromStatusByte(mPendingMessage[0])) + { + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //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: + // The message can be any lenght + // between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; + mRunningStatus_RX = InvalidType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + 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: + + // Here we will have to extract the one-byte message, + // pass it to the structure for being read outside + // the MIDI class, and recompose the message it was + // interleaved into. Oh, and without killing the running status.. + // This is done by leaving the pending message as is, + // it will be completed on next calls. + + mMessage.type = (MidiType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xF7: + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) + { + // Store System Exclusive array in midimsg structure + for (byte i=0;i> 8; + mMessage.channel = 0; + mMessage.valid = true; + + 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]); + // Don't check if it is a Channel Message + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) + { + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; + break; + + default: + // No running status + mRunningStatus_RX = InvalidType; + break; + } + return true; + } + else + { + // Then update the index of the pending message. + mPendingMessageIndex++; + +#if USE_1BYTE_PARSING + // Message is not complete. + return false; +#else + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); +#endif + } + } + + // What are our chances to fall here? + return false; +} + + +// Private method: check if the received message is on the listened channel +template +bool MidiInterface::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 +template +void MidiInterface::reset_input_attributes() +{ + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; +} + + + + + + +#if MIDI_USE_CALLBACKS + +void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } +void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } +void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } +void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } +void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } +void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } +void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } +void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } +void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } +void MidiInterface::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } +void MidiInterface::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } +void MidiInterface::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } +void MidiInterface::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } +void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } + + +/*! \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. + */ +template +void MidiInterface::disconnectCallbackFromType(MidiType Type) +{ + switch (Type) + { + case NoteOff: mNoteOffCallback = 0; break; + case NoteOn: mNoteOnCallback = 0; break; + case AfterTouchPoly: mAfterTouchPolyCallback = 0; break; + case ControlChange: mControlChangeCallback = 0; break; + case ProgramChange: mProgramChangeCallback = 0; break; + case AfterTouchChannel: mAfterTouchChannelCallback = 0; break; + case PitchBend: mPitchBendCallback = 0; break; + case SystemExclusive: mSystemExclusiveCallback = 0; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = 0; break; + case SongPosition: mSongPositionCallback = 0; break; + case SongSelect: mSongSelectCallback = 0; break; + case TuneRequest: mTuneRequestCallback = 0; break; + case Clock: mClockCallback = 0; break; + case Start: mStartCallback = 0; break; + case Continue: mContinueCallback = 0; break; + case Stop: mStopCallback = 0; break; + case ActiveSensing: mActiveSensingCallback = 0; break; + case SystemReset: mSystemResetCallback = 0; break; + default: + break; + } +} + + +// Private - launch callback function based on received type. +template +void MidiInterface::launchCallback() +{ + // The order is mixed to allow frequent messages to trigger their callback faster. + switch (mMessage.type) + { + // Notes + case NoteOff: if (mNoteOffCallback != 0) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != 0) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != 0) mClockCallback(); break; + case Start: if (mStartCallback != 0) mStartCallback(); break; + case Continue: if (mContinueCallback != 0) mContinueCallback(); break; + case Stop: if (mStopCallback != 0) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != 0) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != 0) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != 0) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) + MIDI_PITCHBEND_MIN); break; // TODO: check this + case AfterTouchPoly: if (mAfterTouchPolyCallback != 0) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != 0) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; + + case ProgramChange: if (mProgramChangeCallback != 0) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != 0) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; + + // Occasional messages + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != 0) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; + case SongSelect: if (mSongSelectCallback != 0) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != 0) mTuneRequestCallback(); break; + + case SystemReset: if (mSystemResetCallback != 0) mSystemResetCallback(); break; + case InvalidType: + default: + break; + } +} + + +#endif // MIDI_USE_CALLBACKS + + +#endif // MIDI_BUILD_INPUT + + +// ============================================================================= +// MIDI Soft Thru +// ============================================================================= + +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) + +// This method is called upon reception of a message +// and takes care of Thru filtering and sending. +template +void MidiInterface::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 + +END_MIDI_NAMESPACE diff --git a/src/midi_Inline.hpp b/src/midi_Inline.hpp index ddd2716..99be57d 100644 --- a/src/midi_Inline.hpp +++ b/src/midi_Inline.hpp @@ -12,156 +12,6 @@ BEGIN_MIDI_NAMESPACE -#if MIDI_BUILD_OUTPUT -StatusByte MidiInterface::getStatus(kMIDIType inType, - Channel inChannel) const -{ - return ((byte)inType | ((inChannel - 1) & 0x0F)); -} - -#endif // MIDI_BUILD_OUTPUT - -// ----------------------------------------------------------------------------- - -#if MIDI_BUILD_INPUT - -/*! \brief Get the last received message's type - - Returns an enumerated type. @see MidiType - */ -MidiType MidiInterface::getType() const -{ - return mMessage.type; -} - -/*! \brief Get the channel of the message stored in the structure. - - \return Channel range is 1 to 16. - For non-channel messages, this will return 0. - */ -Channel MidiInterface::getChannel() const -{ - return mMessage.channel; -} - -/*! \brief Get the first data byte of the last received message. */ -DataByte MidiInterface::getData1() const -{ - return mMessage.data1; -} - -/*! \brief Get the second data byte of the last received message. */ -DataByte MidiInterface::getData2() const -{ - return mMessage.data2; -} - -/*! \brief Get the System Exclusive byte array. - - @see getSysExArrayLength to get the array's length in bytes. - */ -const byte* MidiInterface::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 MidiInterface::getSysExArrayLength() const -{ - const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1; - return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size; -} - -/*! \brief Check if a valid message is stored in the structure. */ -bool MidiInterface::check() const -{ - return mMessage.valid; -} - -// ----------------------------------------------------------------------------- - -Channel MidiInterface::getInputChannel() const -{ - return mInputChannel; -} - -/*! \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 input. -*/ -void MidiInterface::setInputChannel(Channel inChannel) -{ - mInputChannel = inChannel; -} - -// ----------------------------------------------------------------------------- - -/*! \brief Extract an enumerated MIDI type from a status byte. - - This is a utility static method, used internally, - made public so you can handle MidiTypes more easily. - */ -MidiType MidiInterface::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 (MidiType)(inStatus & 0xF0); // Channel message, remove channel nibble. - else return (MidiType)inStatus; -} - -#endif // MIDI_BUILD_INPUT - -// ----------------------------------------------------------------------------- - -#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) - -MidiFilterMode MidiInterface::getFilterMode() const -{ - return mThruFilterMode; -} - -bool MidiInterface::getThruState() const -{ - return mThruActivated; -} - -/*! \brief Setter method: turn message mirroring on. */ -void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) -{ - mThruActivated = true; - mThruFilterMode = inThruFilterMode; -} - - -/*! \brief Setter method: turn message mirroring off. */ -void MidiInterface::turnThruOff() -{ - mThruActivated = false; - mThruFilterMode = Off; -} - -/*! \brief Set the filter for thru mirroring - \param inThruFilterMode a filter mode - - @see MidiFilterMode - */ -void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode) -{ - mThruFilterMode = inThruFilterMode; - if (mThruFilterMode != Off) - mThruActivated = true; - else - mThruActivated = false; -} - -#endif // MIDI_BUILD_THRU END_MIDI_NAMESPACE diff --git a/src/midi_Settings.h b/src/midi_Settings.h index f835c2a..217402a 100644 --- a/src/midi_Settings.h +++ b/src/midi_Settings.h @@ -31,6 +31,8 @@ BEGIN_MIDI_NAMESPACE #define MIDI_BUILD_OUTPUT 1 #define MIDI_BUILD_THRU 1 +#define MIDI_USE_CALLBACKS 1 + // Create a MIDI object automatically on the port defined with MIDI_SERIAL_PORT. #define MIDI_AUTO_INSTANCIATE 1 From 7fcb0d1e44174181a4b1b8f1b6db07ef4145acba Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 6 Sep 2012 10:57:11 +0200 Subject: [PATCH 33/38] Building in Arduino 1.0.1 (templates did not work). --- src/MIDI.cpp | 718 +++++++++++++++++++++++- src/MIDI.h | 149 +++-- src/MIDI.hpp | 1268 ------------------------------------------- src/midi_Inline.hpp | 460 ++++++++++++++++ 4 files changed, 1243 insertions(+), 1352 deletions(-) delete mode 100644 src/MIDI.hpp diff --git a/src/MIDI.cpp b/src/MIDI.cpp index 15334d6..c88d05f 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -12,18 +12,31 @@ // ----------------------------------------------------------------------------- +#if !(MIDI_BUILD_INPUT) && !(MIDI_BUILD_OUTPUT) +# error To use MIDI, you need to enable at least input or output. +#endif + +#if MIDI_BUILD_THRU && !(MIDI_BUILD_OUTPUT) +# error For thru to work, you need to enable output. +#endif +#if MIDI_BUILD_THRU && !(MIDI_BUILD_INPUT) +# error For thru to work, you need to enable input. +#endif + +// ----------------------------------------------------------------------------- + #if MIDI_AUTO_INSTANCIATE - #if MIDI_USE_SOFTWARE_SERIAL - #include "../SoftwareSerial/SoftwareSerial.h" +# if MIDI_USE_SOFTWARE_SERIAL +# include "../SoftwareSerial/SoftwareSerial.h" SoftwareSerial softSerialClass(MIDI_SOFTSERIAL_RX_PIN, MIDI_SOFTSERIAL_TX_PIN); - #undef MIDI_SERIAL_PORT - #define MIDI_SERIAL_PORT softSerialClass - #else - #include "HardwareSerial.h" - #endif // MIDI_USE_SOFTWARE_SERIAL +# undef MIDI_SERIAL_PORT +# define MIDI_SERIAL_PORT softSerialClass +# else +# include "HardwareSerial.h" +# endif // MIDI_USE_SOFTWARE_SERIAL - MIDI_NAMESPACE::MidiInterface MIDI; + MIDI_NAMESPACE::MidiInterface MIDI; #endif // MIDI_AUTO_INSTANCIATE @@ -31,4 +44,693 @@ BEGIN_MIDI_NAMESPACE +// ----------------------------------------------------------------------------- + +/*! \brief Constructor for MidiInterface. */ +MidiInterface::MidiInterface() +{ +#if MIDI_BUILD_INPUT && MIDI_USE_CALLBACKS + mNoteOffCallback = 0; + mNoteOnCallback = 0; + mAfterTouchPolyCallback = 0; + mControlChangeCallback = 0; + mProgramChangeCallback = 0; + mAfterTouchChannelCallback = 0; + mPitchBendCallback = 0; + mSystemExclusiveCallback = 0; + mTimeCodeQuarterFrameCallback = 0; + mSongPositionCallback = 0; + mSongSelectCallback = 0; + mTuneRequestCallback = 0; + mClockCallback = 0; + mStartCallback = 0; + mContinueCallback = 0; + mStopCallback = 0; + mActiveSensingCallback = 0; + mSystemResetCallback = 0; +#endif +} + +/*! \brief Destructor for MidiInterface. + + This is not really useful for the Arduino, as it is never called... + */ +MidiInterface::~MidiInterface() +{ +} + +// ----------------------------------------------------------------------------- + +/*! \brief Call the begin method in the setup() function of the Arduino. + + All parameters are set to their default values: + - Input channel set to 1 if no value is specified + - Full thru mirroring + */ +void MidiInterface::begin(Channel inChannel) +{ + // Initialise the Serial port + MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); + +#if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS + + mRunningStatus_TX = InvalidType; + +#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS + + +#if MIDI_BUILD_INPUT + + mInputChannel = inChannel; + mRunningStatus_RX = InvalidType; + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = false; + mMessage.type = InvalidType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + +#endif // MIDI_BUILD_INPUT + + +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru + + mThruFilterMode = Full; + mThruActivated = true; + +#endif // Thru + +} + + +// ----------------------------------------------------------------------------- +// Output +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_OUTPUT + +/*! \brief Generate and send a MIDI message from the values given. + \param inType The message type (see type defines for reference) + \param inData1 The first data byte. + \param inData2 The second data byte (if the message contains only 1 data byte, + set this one to 0). + \param inChannel The output channel on which the message will be sent + (values from 1 to 16). Note: you cannot send to OMNI. + + This is an internal method, use it only if you need to send raw data + from your code, at your own risks. + */ +void MidiInterface::send(MidiType inType, + DataByte inData1, + DataByte inData2, + Channel inChannel) +{ + // Then test if channel is valid + if (inChannel >= MIDI_CHANNEL_OFF || + inChannel == MIDI_CHANNEL_OMNI || + inType < NoteOff) + { + +#if MIDI_USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif + + return; // Don't send anything + } + + if (inType <= PitchBend) // Channel messages + { + // Protection: remove MSBs on data + inData1 &= 0x7F; + inData2 &= 0x7F; + + const StatusByte status = getStatus(inType, inChannel); + +#if MIDI_USE_RUNNING_STATUS + // Check Running Status + if (mRunningStatus_TX != status) + { + // New message, memorise and send header + mRunningStatus_TX = status; + MIDI_SERIAL_PORT.write(mRunningStatus_TX); + } +#else + // Don't care about running status, send the status byte. + MIDI_SERIAL_PORT.write(status); +#endif + + // Then send data + MIDI_SERIAL_PORT.write(inData1); + if (inType != ProgramChange && inType != AfterTouchChannel) + MIDI_SERIAL_PORT.write(inData2); + + return; + } + else if (inType >= TuneRequest && inType <= SystemReset) + sendRealTime(inType); // System Real-time and 1 byte. +} + +#endif // MIDI_BUILD_OUTPUT + + +// ----------------------------------------------------------------------------- +// Input +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_INPUT + +/*! \brief Read a MIDI message from the serial port + using the main input channel (see setInputChannel() for reference). + + \return True if a valid message has been stored in the structure, false if not. + A valid message is a message that matches the input channel. \n\n + If the Thru is enabled and the messages matches the filter, + it is sent back on the MIDI output. + */ +bool MidiInterface::read() +{ + return read(mInputChannel); +} + +/*! \brief Reading/thru-ing method, the same as read() + with a given input channel to read on. + */ +bool MidiInterface::read(Channel inChannel) +{ + if (inChannel >= MIDI_CHANNEL_OFF) + return false; // MIDI Input disabled. + + if (parse(inChannel)) + { + if (inputFilter(inChannel)) + { + +#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) + thruFilter(inChannel); +#endif + +#if MIDI_USE_CALLBACKS + launchCallback(); +#endif + return true; + } + } + + return false; +} + +// ----------------------------------------------------------------------------- + +// Private method: MIDI parser +bool MidiInterface::parse(Channel inChannel) +{ + const byte bytes_available = MIDI_SERIAL_PORT.available(); + + if (bytes_available == 0) + // No data available. + return false; + + + /* Parsing algorithm: + Get a byte from the serial buffer. + * If there is no pending message to be recomposed, start a new one. + - Find type and channel (if pertinent) + - Look for other bytes in buffer, call parser recursively, + until the message is assembled or the buffer is empty. + * Else, add the extracted byte to the pending message, and check validity. + When the message is done, store it. + */ + + const byte extracted = MIDI_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) + { + // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + switch (getTypeFromStatusByte(mRunningStatus_RX)) + { + // Only these types allow Running Status: + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + + // If the status byte is not received, prepend it + // to the pending message + if (extracted < 0x80) + { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, + // so the running status does not apply here. + // It will be updated upon completion of this message. + + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) + { + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mMessage.valid = true; + return true; + } + break; + default: + // No running status + break; + } + + + switch (getTypeFromStatusByte(mPendingMessage[0])) + { + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //resetInput(); + + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLenght = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLenght = 3; + break; + + case SystemExclusive: + // The message can be any lenght + // between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; + mRunningStatus_RX = InvalidType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + resetInput(); + return false; + break; + } + + // Then update the index of the pending message. + mPendingMessageIndex++; + +#if USE_1BYTE_PARSING + // Message is not complete. + return false; +#else + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); +#endif + + } + else + { + // First, test if this is a status byte + if (extracted >= 0x80) + { + // Reception of status bytes in the middle of an uncompleted message + // are allowed only for interleaved Real Time message or EOX + switch (extracted) + { + case Clock: + case Start: + case Continue: + case Stop: + case ActiveSensing: + case SystemReset: + + // Here we will have to extract the one-byte message, + // pass it to the structure for being read outside + // the MIDI class, and recompose the message it was + // interleaved into. Oh, and without killing the running status.. + // This is done by leaving the pending message as is, + // it will be completed on next calls. + + mMessage.type = (MidiType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xF7: + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) + { + // Store System Exclusive array in midimsg structure + for (byte i=0;i> 8; + mMessage.channel = 0; + mMessage.valid = true; + + resetInput(); + return true; + } + else + { + // Well well well.. error. + resetInput(); + return false; + } + + break; + default: + break; + } + } + + // Add extracted data byte to pending message + mPendingMessage[mPendingMessageIndex] = extracted; + + // Now we are going to check if we have reached the end of the message + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) + { + // "FML" case: fall down here with an overflown SysEx.. + // This means we received the last possible data byte that can fit + // the buffer. If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE. + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) + { + resetInput(); + return false; + } + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + // Don't check if it is a Channel Message + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) + { + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; + break; + + default: + // No running status + mRunningStatus_RX = InvalidType; + break; + } + return true; + } + else + { + // Then update the index of the pending message. + mPendingMessageIndex++; + +#if USE_1BYTE_PARSING + // Message is not complete. + return false; +#else + // Call the parser recursively + // to parse the rest of the message. + return parse(inChannel); +#endif + } + } + + // What are our chances to fall here? + return false; +} + +// Private method: check if the received message is on the listened channel +bool MidiInterface::inputFilter(Channel inChannel) +{ + // This method handles recognition of channel + // (to know if the message is destinated to the Arduino) + + if (mMessage.type == InvalidType) + return false; + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { + // Then we need to know if we listen to it + if ((mMessage.channel == mInputChannel) || + (mInputChannel == MIDI_CHANNEL_OMNI)) + { + return true; + } + else + { + // We don't listen to this channel + return false; + } + } + else + { + // System messages are always received + return true; + } +} + +// Private method: reset input attributes +void MidiInterface::resetInput() +{ + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; +} + +// ----------------------------------------------------------------------------- + +#if MIDI_USE_CALLBACKS + +// Private - launch callback function based on received type. +void MidiInterface::launchCallback() +{ + // The order is mixed to allow frequent messages to trigger their callback faster. + switch (mMessage.type) + { + // Notes + case NoteOff: if (mNoteOffCallback != 0) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != 0) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != 0) mClockCallback(); break; + case Start: if (mStartCallback != 0) mStartCallback(); break; + case Continue: if (mContinueCallback != 0) mContinueCallback(); break; + case Stop: if (mStopCallback != 0) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != 0) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != 0) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != 0) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) + MIDI_PITCHBEND_MIN); break; // TODO: check this + case AfterTouchPoly: if (mAfterTouchPolyCallback != 0) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != 0) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; + + case ProgramChange: if (mProgramChangeCallback != 0) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != 0) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; + + // Occasional messages + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != 0) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; + case SongSelect: if (mSongSelectCallback != 0) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != 0) mTuneRequestCallback(); break; + + case SystemReset: if (mSystemResetCallback != 0) mSystemResetCallback(); break; + case InvalidType: + default: + break; + } +} + +#endif // MIDI_USE_CALLBACKS + +#endif // MIDI_BUILD_INPUT + + +// ----------------------------------------------------------------------------- +// Thru +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_THRU + +// This method is called upon reception of a message +// and takes care of Thru filtering and sending. + +void MidiInterface::thruFilter(Channel inChannel) +{ + + /* + This method handles Soft-Thru filtering. + + Soft-Thru filtering: + - All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off + - Channel messages are passed to the output whether their channel is matching the input channel and the filter setting + + */ + + // If the feature is disabled, don't do anything. + if (!mThruActivated || (mThruFilterMode == Off)) + return; + + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { + const bool filter_condition = ((mMessage.channel == mInputChannel) || + (mInputChannel == MIDI_CHANNEL_OMNI)); + + // Now let's pass it to the output + switch (mThruFilterMode) + { + case Full: + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + return; + break; + case SameChannel: + if (filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + return; + } + break; + case DifferentChannel: + if (!filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + return; + } + break; + case Off: + // Do nothing. + // Technically it's impossible to get there because + // the case was already tested earlier. + break; + default: + break; + } + } + else + { + // Send the message to the output + switch (mMessage.type) + { + // Real Time and 1 byte + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + case TuneRequest: + sendRealTime(mMessage.type); + return; + break; + + case SystemExclusive: + // Send SysEx (0xF0 and 0xF7 are included in the buffer) + sendSysEx(mMessage.data1,mMessage.sysex_array,true); + return; + break; + + case SongSelect: + sendSongSelect(mMessage.data1); + return; + break; + + case SongPosition: + sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7)); + return; + break; + + case TimeCodeQuarterFrame: + sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); + return; + break; + default: + break; + } + } +} + +#endif // MIDI_BUILD_THRU + END_MIDI_NAMESPACE diff --git a/src/MIDI.h b/src/MIDI.h index 64f3bf7..44ec553 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -22,7 +22,6 @@ BEGIN_MIDI_NAMESPACE /*! \brief The main class for MIDI handling. */ -template class MidiInterface { public: @@ -30,7 +29,7 @@ public: ~MidiInterface(); public: - void begin(byte inChannel = 1); + void begin(Channel inChannel = 1); // ------------------------------------------------------------------------- // MIDI Output @@ -38,44 +37,45 @@ public: #if MIDI_BUILD_OUTPUT public: - void sendNoteOn(byte inNoteNumber, - byte inVelocity, - Channel inChannel); - - void sendNoteOff(byte inNoteNumber, - byte inVelocity, - Channel inChannel); - - void sendProgramChange(byte inProgramNumber, + inline void sendNoteOn(DataByte inNoteNumber, + DataByte inVelocity, Channel inChannel); - void sendControlChange(byte inControlNumber, - byte inControlValue, - Channel inChannel); + inline void sendNoteOff(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel); - void sendPitchBend(int inPitchValue, Channel inChannel); - void sendPitchBend(double inPitchValue, Channel inChannel); + inline void sendProgramChange(DataByte inProgramNumber, + Channel inChannel); - void sendPolyPressure(byte inNoteNumber, - byte inPressure, - Channel inChannel); + inline void sendControlChange(DataByte inControlNumber, + DataByte inControlValue, + Channel inChannel); - void sendAfterTouch(byte inPressure, - Channel inChannel); + inline void sendPitchBend(int inPitchValue, Channel inChannel); + inline void sendPitchBend(double inPitchValue, Channel inChannel); - void sendSysEx(unsigned int inLength, - const byte* inArray, - bool inArrayContainsBoundaries = false); + inline void sendPolyPressure(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel); - void sendTimeCodeQuarterFrame(byte inTypeNibble, - byte inValuesNibble); - void sendTimeCodeQuarterFrame(byte inData); + inline void sendAfterTouch(DataByte inPressure, + Channel inChannel); - void sendSongPosition(unsigned int inBeats); - void sendSongSelect(byte inSongNumber); - void sendTuneRequest(); - void sendRealTime(MidiType inType); + inline void sendSysEx(unsigned int inLength, + const byte* inArray, + bool inArrayContainsBoundaries = false); + inline void sendTimeCodeQuarterFrame(DataByte inTypeNibble, + DataByte inValuesNibble); + inline void sendTimeCodeQuarterFrame(DataByte inData); + + inline void sendSongPosition(unsigned int inBeats); + inline void sendSongSelect(DataByte inSongNumber); + inline void sendTuneRequest(); + inline void sendRealTime(MidiType inType); + +public: void send(MidiType inType, DataByte inData1, DataByte inData2, @@ -114,49 +114,46 @@ public: static inline MidiType getTypeFromStatusByte(const byte inStatus); private: + bool inputFilter(Channel inChannel); + bool parse(Channel inChannel); + void resetInput(); - bool input_filter(byte inChannel); - bool parse(byte inChannel); - void reset_input_attributes(); +private: + StatusByte mRunningStatus_RX; + Channel mInputChannel; - // Attributes - byte mRunningStatus_RX; - byte mInputChannel; - - byte mPendingMessage[3]; // SysEx are dumped into mMessage directly. - unsigned int mPendingMessageExpectedLenght; - unsigned int mPendingMessageIndex; // Extended to unsigned int for larger SysEx payloads. - - midimsg mMessage; + byte mPendingMessage[3]; // SysEx are dumped into mMessage directly. + unsigned int mPendingMessageExpectedLenght; + unsigned int mPendingMessageIndex; // Extended to unsigned int for larger SysEx payloads. + Message mMessage; - // ========================================================================= + // ------------------------------------------------------------------------- // Input Callbacks #if MIDI_USE_CALLBACKS public: + inline void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); + inline void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); + inline void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); + inline void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); + inline void setHandleProgramChange(void (*fptr)(byte channel, byte number)); + inline void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); + inline void setHandlePitchBend(void (*fptr)(byte channel, int bend)); + inline void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); + inline void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); + inline void setHandleSongPosition(void (*fptr)(unsigned int beats)); + inline void setHandleSongSelect(void (*fptr)(byte songnumber)); + inline void setHandleTuneRequest(void (*fptr)(void)); + inline void setHandleClock(void (*fptr)(void)); + inline void setHandleStart(void (*fptr)(void)); + inline void setHandleContinue(void (*fptr)(void)); + inline void setHandleStop(void (*fptr)(void)); + inline void setHandleActiveSensing(void (*fptr)(void)); + inline void setHandleSystemReset(void (*fptr)(void)); - 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(MidiType Type); + inline void disconnectCallbackFromType(MidiType inType); private: @@ -186,10 +183,10 @@ private: #endif // MIDI_BUILD_INPUT - // ========================================================================= + // ------------------------------------------------------------------------- // MIDI Soft Thru -#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) +#if MIDI_BUILD_THRU public: inline MidiFilterMode getFilterMode() const; @@ -201,21 +198,21 @@ public: private: + void thruFilter(byte inChannel); - void thru_filter(byte inChannel); - +private: bool mThruActivated : 1; MidiFilterMode mThruFilterMode : 7; -#endif // Thru +#endif // MIDI_BUILD_THRU - - - // Attributes + #if MIDI_USE_RUNNING_STATUS - StatusByte mRunningStatus_TX; -#endif // MIDI_USE_RUNNING_STATUS +private: + StatusByte mRunningStatus_TX; + +#endif // MIDI_USE_RUNNING_STATUS }; @@ -224,8 +221,8 @@ END_MIDI_NAMESPACE // ----------------------------------------------------------------------------- #if MIDI_AUTO_INSTANCIATE -extern MIDI_NAMESPACE::MidiInterface MIDI; -#endif + extern MIDI_NAMESPACE::MidiInterface MIDI; +#endif // MIDI_AUTO_INSTANCIATE // ----------------------------------------------------------------------------- diff --git a/src/MIDI.hpp b/src/MIDI.hpp deleted file mode 100644 index f4d7014..0000000 --- a/src/MIDI.hpp +++ /dev/null @@ -1,1268 +0,0 @@ -/*! - * @file MIDI.cpp - * Project Arduino MIDI Library - * @brief MIDI Library for the Arduino - * @version 4.0 - * @author Francois Best - * @date 24/02/11 - * license GPL Forty Seven Effects - 2011 - */ - -#pragma once - -BEGIN_MIDI_NAMESPACE - -// ----------------------------------------------------------------------------- - -template -MidiInterface::MidiInterface() -{ -#if MIDI_BUILD_INPUT && MIDI_USE_CALLBACKS - mNoteOffCallback = 0; - mNoteOnCallback = 0; - mAfterTouchPolyCallback = 0; - mControlChangeCallback = 0; - mProgramChangeCallback = 0; - mAfterTouchChannelCallback = 0; - mPitchBendCallback = 0; - mSystemExclusiveCallback = 0; - mTimeCodeQuarterFrameCallback = 0; - mSongPositionCallback = 0; - mSongSelectCallback = 0; - mTuneRequestCallback = 0; - mClockCallback = 0; - mStartCallback = 0; - mContinueCallback = 0; - mStopCallback = 0; - mActiveSensingCallback = 0; - mSystemResetCallback = 0; -#endif -} - -template -MidiInterface::~MidiInterface() -{ -} - -// ----------------------------------------------------------------------------- - -#if MIDI_BUILD_OUTPUT - -template -StatusByte MidiInterface::getStatus(kMIDIType inType, - Channel inChannel) const -{ - return ((byte)inType | ((inChannel - 1) & 0x0F)); -} - -#endif // MIDI_BUILD_OUTPUT - -// ----------------------------------------------------------------------------- - -#if MIDI_BUILD_INPUT - -/*! \brief Get the last received message's type - - Returns an enumerated type. @see MidiType - */ -template -MidiType MidiInterface::getType() const -{ - return mMessage.type; -} - -/*! \brief Get the channel of the message stored in the structure. - - \return Channel range is 1 to 16. - For non-channel messages, this will return 0. - */ -template -Channel MidiInterface::getChannel() const -{ - return mMessage.channel; -} - -/*! \brief Get the first data byte of the last received message. */ -template -DataByte MidiInterface::getData1() const -{ - return mMessage.data1; -} - -/*! \brief Get the second data byte of the last received message. */ -template -DataByte MidiInterface::getData2() const -{ - return mMessage.data2; -} - -/*! \brief Get the System Exclusive byte array. - - @see getSysExArrayLength to get the array's length in bytes. - */ -template -const byte* MidiInterface::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. - */ -template -unsigned int MidiInterface::getSysExArrayLength() const -{ - const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1; - return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size; -} - -/*! \brief Check if a valid message is stored in the structure. */ -template -bool MidiInterface::check() const -{ - return mMessage.valid; -} - -// ----------------------------------------------------------------------------- - -template -Channel MidiInterface::getInputChannel() const -{ - return mInputChannel; -} - -/*! \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 input. - */ -template -void MidiInterface::setInputChannel(Channel inChannel) -{ - mInputChannel = inChannel; -} - -// ----------------------------------------------------------------------------- - -/*! \brief Extract an enumerated MIDI type from a status byte. - - This is a utility static method, used internally, - made public so you can handle MidiTypes more easily. - */ -template -MidiType MidiInterface::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 (MidiType)(inStatus & 0xF0); // Channel message, remove channel nibble. - else return (MidiType)inStatus; -} - -#endif // MIDI_BUILD_INPUT - -// ----------------------------------------------------------------------------- - -#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) - -template -MidiFilterMode MidiInterface::getFilterMode() const -{ - return mThruFilterMode; -} - -template -bool MidiInterface::getThruState() const -{ - return mThruActivated; -} - -/*! \brief Setter method: turn message mirroring on. */ -template -void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) -{ - mThruActivated = true; - mThruFilterMode = inThruFilterMode; -} - - -/*! \brief Setter method: turn message mirroring off. */ -template -void MidiInterface::turnThruOff() -{ - mThruActivated = false; - mThruFilterMode = Off; -} - -/*! \brief Set the filter for thru mirroring - \param inThruFilterMode a filter mode - - @see MidiFilterMode - */ -template -void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode) -{ - mThruFilterMode = inThruFilterMode; - if (mThruFilterMode != Off) - mThruActivated = true; - else - mThruActivated = false; -} - -#endif // MIDI_BUILD_THRU - -END_MIDI_NAMESPACE - -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// -// To sort below these lines. -// -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- - - -BEGIN_MIDI_NAMESPACE - -/*! \brief Default constructor for MidiInterface. */ -MidiInterface::MidiInterface() -{ -#if MIDI_BUILD_INPUT && MIDI_USE_CALLBACKS - - // Initialise callbacks to 0 pointer - mNoteOffCallback = 0; - mNoteOnCallback = 0; - mAfterTouchPolyCallback = 0; - mControlChangeCallback = 0; - mProgramChangeCallback = 0; - mAfterTouchChannelCallback = 0; - mPitchBendCallback = 0; - mSystemExclusiveCallback = 0; - mTimeCodeQuarterFrameCallback = 0; - mSongPositionCallback = 0; - mSongSelectCallback = 0; - mTuneRequestCallback = 0; - mClockCallback = 0; - mStartCallback = 0; - mContinueCallback = 0; - mStopCallback = 0; - mActiveSensingCallback = 0; - mSystemResetCallback = 0; - -#endif -} - - -/*! \brief Default destructor for MidiInterface. - - This is not really useful for the Arduino, as it is never called... - */ -MidiInterface::~MidiInterface() -{ - -} - - -/*! \brief Call the begin method in the setup() function of the Arduino. - - All parameters are set to their default values: - - Input channel set to 1 if no value is specified - - Full thru mirroring - */ -template -void MidiInterface::begin(const byte inChannel) -{ - // Initialise the Serial port - MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE); - -#if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS - - mRunningStatus_TX = InvalidType; - -#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS - - -#if MIDI_BUILD_INPUT - - mInputChannel = inChannel; - mRunningStatus_RX = InvalidType; - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = false; - mMessage.type = InvalidType; - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - -#endif // MIDI_BUILD_INPUT - - -#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru - - mThruFilterMode = Full; - mThruActivated = true; - -#endif // Thru - -} - - -// ============================================================================= -// MIDI Output -// ============================================================================= - -#if MIDI_BUILD_OUTPUT - - -/*! \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. - */ -template -void MidiInterface::send(MidiType 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 MIDI_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 MIDI_USE_RUNNING_STATUS - // Check Running Status - if (mRunningStatus_TX != statusbyte) - { - // New message, memorise and send header - mRunningStatus_TX = statusbyte; - MIDI_SERIAL_PORT.write(mRunningStatus_TX); - } -#else - // Don't care about running status, send the Control byte. - MIDI_SERIAL_PORT.write(statusbyte); -#endif - - // Then send data - MIDI_SERIAL_PORT.write(data1); - if (type != ProgramChange && type != AfterTouchChannel) - MIDI_SERIAL_PORT.write(data2); - - return; - } - if (type >= TuneRequest && type <= SystemReset) - sendRealTime(type); // System Real-time and 1 byte. -} - - -/*! \brief Send a Note On message - \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 - */ -template -void MidiInterface::sendNoteOn(byte NoteNumber, - byte Velocity, - byte Channel) -{ - send(NoteOn,NoteNumber,Velocity,Channel); -} - - -/*! \brief Send a Note Off message - \param NoteNumber Pitch value in the MIDI format (0 to 127). - \param Velocity Release velocity (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). - - Note: you can send NoteOn with zero velocity to make a NoteOff, this is based - on the Running Status principle, to avoid sending status messages and thus - sending only NoteOn data. This method will always send a real NoteOff message. - Take a look at the values, names and frequencies of notes here: - http://www.phys.unsw.edu.au/jw/notes.html - */ -template -void MidiInterface::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). - */ -template -void MidiInterface::sendProgramChange(byte ProgramNumber, - byte Channel) -{ - send(ProgramChange,ProgramNumber,0,Channel); -} - - -/*! \brief Send a Control Change message - \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). - - See the detailed controllers numbers & description here: - http://www.somascape.org/midi/tech/spec.html#ctrlnums - */ -template -void MidiInterface::sendControlChange(byte ControlNumber, - byte ControlValue, - byte Channel) -{ - send(ControlChange,ControlNumber,ControlValue,Channel); -} - - -/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) - \param NoteNumber The note to apply AfterTouch to (0 to 127). - \param Pressure The amount of AfterTouch to apply (0 to 127). - \param Channel The channel on which the message will be sent (1 to 16). - */ -template -void MidiInterface::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). - */ -template -void MidiInterface::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 MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX, - center value is 0. - \param Channel The channel on which the message will be sent (1 to 16). - */ -template -void MidiInterface::sendPitchBend(int PitchValue, - byte Channel) -{ - const unsigned int bend = PitchValue - MIDI_PITCHBEND_MIN; - send(PitchBend,(bend & 0x7F),(bend >> 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). - */ -template -void MidiInterface::sendPitchBend(double PitchValue, - byte Channel) -{ - const int pitchval = PitchValue * MIDI_PITCHBEND_MAX; - 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 for ArrayContainsBoundaries is set to 'false' for compatibility - with previous versions of the library. - */ -template -void MidiInterface::sendSysEx(int length, - const byte *const array, - bool ArrayContainsBoundaries) -{ - if (ArrayContainsBoundaries == false) - { - MIDI_SERIAL_PORT.write(0xF0); - - for (int i=0;i -void MidiInterface::sendTuneRequest() -{ - sendRealTime(TuneRequest); -} - - -/*! \brief Send a MIDI Time Code Quarter Frame. - - \param TypeNibble MTC type - \param ValuesNibble MTC data - See MIDI Specification for more information. - */ -template -void MidiInterface::sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble) -{ - const byte data = ( ((TypeNibble & 0x07) << 4) | (ValuesNibble & 0x0F) ); - sendTimeCodeQuarterFrame(data); -} - - -/*! \brief Send a MIDI Time Code Quarter Frame. - - See MIDI Specification for more information. - \param data if you want to encode directly the nibbles in your program, - you can send the byte here. - */ -template -void MidiInterface::sendTimeCodeQuarterFrame(byte data) -{ - MIDI_SERIAL_PORT.write((byte)TimeCodeQuarterFrame); - MIDI_SERIAL_PORT.write(data); - -#if MIDI_USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; -#endif -} - - -/*! \brief Send a Song Position Pointer message. - \param Beats The number of beats since the start of the song. - */ -template -void MidiInterface::sendSongPosition(unsigned int Beats) -{ - MIDI_SERIAL_PORT.write((byte)SongPosition); - MIDI_SERIAL_PORT.write(Beats & 0x7F); - MIDI_SERIAL_PORT.write((Beats >> 7) & 0x7F); - -#if MIDI_USE_RUNNING_STATUS - mRunningStatus_TX = InvalidType; -#endif -} - - -/*! \brief Send a Song Select message */ -template -void MidiInterface::sendSongSelect(byte SongNumber) -{ - MIDI_SERIAL_PORT.write((byte)SongSelect); - MIDI_SERIAL_PORT.write(SongNumber & 0x7F); - -#if MIDI_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 MidiType - */ -template -void MidiInterface::sendRealTime(MidiType 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: - MIDI_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 MIDI_USE_RUNNING_STATUS - if (Type == TuneRequest) mRunningStatus_TX = InvalidType; -#endif -} - -#endif // MIDI_BUILD_OUTPUT - - -// ============================================================================= -// MIDI Input -// ============================================================================= - -#if MIDI_BUILD_INPUT - -/*! \brief Read a MIDI message from the serial port - using the main input channel (see setInputChannel() for reference). - - \return True if a valid message has been stored in the structure, false if not. - A valid message is a message that matches the input channel. \n\n - If the Thru is enabled and the messages matches the filter, - it is sent back on the MIDI output. - */ -template -bool MidiInterface::read() -{ - return read(mInputChannel); -} - - -/*! \brief Reading/thru-ing method, the same as read() - with a given input channel to read on. - */ -template -bool MidiInterface::read(const byte inChannel) -{ - if (inChannel >= MIDI_CHANNEL_OFF) - return false; // MIDI Input disabled. - - if (parse(inChannel)) - { - if (input_filter(inChannel)) - { - -#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) - thru_filter(inChannel); -#endif - -#if MIDI_USE_CALLBACKS - launchCallback(); -#endif - return true; - } - } - - return false; -} - - -// Private method: MIDI parser -template -bool MidiInterface::parse(byte inChannel) -{ - const byte bytes_available = MIDI_SERIAL_PORT.available(); - - if (bytes_available == 0) - // No data available. - return false; - - - /* Parsing algorithm: - Get a byte from the serial buffer. - * If there is no pending message to be recomposed, start a new one. - - Find type and channel (if pertinent) - - Look for other bytes in buffer, call parser recursively, - until the message is assembled or the buffer is empty. - * Else, add the extracted byte to the pending message, and check validity. - When the message is done, store it. - */ - - const byte extracted = MIDI_SERIAL_PORT.read(); - - if (mPendingMessageIndex == 0) - { - // Start a new pending message - mPendingMessage[0] = extracted; - - // Check for running status first - switch (getTypeFromStatusByte(mRunningStatus_RX)) - { - // Only these types allow Running Status: - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - - // If the status byte is not received, prepend it - // to the pending message - if (extracted < 0x80) - { - mPendingMessage[0] = mRunningStatus_RX; - mPendingMessage[1] = extracted; - mPendingMessageIndex = 1; - } - // Else: well, we received another status byte, - // so the running status does not apply here. - // It will be updated upon completion of this message. - - if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) - { - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; - mMessage.data1 = mPendingMessage[1]; - - // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) - mMessage.data2 = mPendingMessage[2]; - else - mMessage.data2 = 0; - - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - mMessage.valid = true; - return true; - } - break; - default: - // No running status - break; - } - - - switch (getTypeFromStatusByte(mPendingMessage[0])) - { - // 1 byte messages - case Start: - case Continue: - case Stop: - case Clock: - case ActiveSensing: - case SystemReset: - case TuneRequest: - // Handle the message type directly here. - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.valid = true; - - // \fix Running Status broken when receiving Clock messages. - // Do not reset all input attributes, Running Status must remain unchanged. - //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: - // The message can be any lenght - // between 3 and MIDI_SYSEX_ARRAY_SIZE bytes - mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; - mRunningStatus_RX = InvalidType; - break; - - case InvalidType: - default: - // This is obviously wrong. Let's get the hell out'a here. - 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: - - // Here we will have to extract the one-byte message, - // pass it to the structure for being read outside - // the MIDI class, and recompose the message it was - // interleaved into. Oh, and without killing the running status.. - // This is done by leaving the pending message as is, - // it will be completed on next calls. - - mMessage.type = (MidiType)extracted; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.channel = 0; - mMessage.valid = true; - return true; - - break; - - // End of Exclusive - case 0xF7: - if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) - { - // Store System Exclusive array in midimsg structure - for (byte i=0;i> 8; - mMessage.channel = 0; - mMessage.valid = true; - - 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]); - // Don't check if it is a Channel Message - mMessage.channel = (mPendingMessage[0] & 0x0F)+1; - - mMessage.data1 = mPendingMessage[1]; - - // Save data2 only if applicable - if (mPendingMessageExpectedLenght == 3) - mMessage.data2 = mPendingMessage[2]; - else - mMessage.data2 = 0; - - // Reset local variables - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - - mMessage.valid = true; - - // Activate running status (if enabled for the received type) - switch (mMessage.type) - { - case NoteOff: - case NoteOn: - case AfterTouchPoly: - case ControlChange: - case ProgramChange: - case AfterTouchChannel: - case PitchBend: - // Running status enabled: store it from received message - mRunningStatus_RX = mPendingMessage[0]; - break; - - default: - // No running status - mRunningStatus_RX = InvalidType; - break; - } - return true; - } - else - { - // Then update the index of the pending message. - mPendingMessageIndex++; - -#if USE_1BYTE_PARSING - // Message is not complete. - return false; -#else - // Call the parser recursively - // to parse the rest of the message. - return parse(inChannel); -#endif - } - } - - // What are our chances to fall here? - return false; -} - - -// Private method: check if the received message is on the listened channel -template -bool MidiInterface::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 -template -void MidiInterface::reset_input_attributes() -{ - mPendingMessageIndex = 0; - mPendingMessageExpectedLenght = 0; - mRunningStatus_RX = InvalidType; -} - - - - - - -#if MIDI_USE_CALLBACKS - -void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } -void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } -void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } -void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } -void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } -void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } -void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } -void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } -void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } -void MidiInterface::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } -void MidiInterface::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } -void MidiInterface::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } -void MidiInterface::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } -void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } -void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } -void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } -void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } -void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } - - -/*! \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. - */ -template -void MidiInterface::disconnectCallbackFromType(MidiType Type) -{ - switch (Type) - { - case NoteOff: mNoteOffCallback = 0; break; - case NoteOn: mNoteOnCallback = 0; break; - case AfterTouchPoly: mAfterTouchPolyCallback = 0; break; - case ControlChange: mControlChangeCallback = 0; break; - case ProgramChange: mProgramChangeCallback = 0; break; - case AfterTouchChannel: mAfterTouchChannelCallback = 0; break; - case PitchBend: mPitchBendCallback = 0; break; - case SystemExclusive: mSystemExclusiveCallback = 0; break; - case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = 0; break; - case SongPosition: mSongPositionCallback = 0; break; - case SongSelect: mSongSelectCallback = 0; break; - case TuneRequest: mTuneRequestCallback = 0; break; - case Clock: mClockCallback = 0; break; - case Start: mStartCallback = 0; break; - case Continue: mContinueCallback = 0; break; - case Stop: mStopCallback = 0; break; - case ActiveSensing: mActiveSensingCallback = 0; break; - case SystemReset: mSystemResetCallback = 0; break; - default: - break; - } -} - - -// Private - launch callback function based on received type. -template -void MidiInterface::launchCallback() -{ - // The order is mixed to allow frequent messages to trigger their callback faster. - switch (mMessage.type) - { - // Notes - case NoteOff: if (mNoteOffCallback != 0) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case NoteOn: if (mNoteOnCallback != 0) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - - // Real-time messages - case Clock: if (mClockCallback != 0) mClockCallback(); break; - case Start: if (mStartCallback != 0) mStartCallback(); break; - case Continue: if (mContinueCallback != 0) mContinueCallback(); break; - case Stop: if (mStopCallback != 0) mStopCallback(); break; - case ActiveSensing: if (mActiveSensingCallback != 0) mActiveSensingCallback(); break; - - // Continuous controllers - case ControlChange: if (mControlChangeCallback != 0) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case PitchBend: if (mPitchBendCallback != 0) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) + MIDI_PITCHBEND_MIN); break; // TODO: check this - case AfterTouchPoly: if (mAfterTouchPolyCallback != 0) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; - case AfterTouchChannel: if (mAfterTouchChannelCallback != 0) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; - - case ProgramChange: if (mProgramChangeCallback != 0) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; - case SystemExclusive: if (mSystemExclusiveCallback != 0) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; - - // Occasional messages - case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != 0) mTimeCodeQuarterFrameCallback(mMessage.data1); break; - case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; - case SongSelect: if (mSongSelectCallback != 0) mSongSelectCallback(mMessage.data1); break; - case TuneRequest: if (mTuneRequestCallback != 0) mTuneRequestCallback(); break; - - case SystemReset: if (mSystemResetCallback != 0) mSystemResetCallback(); break; - case InvalidType: - default: - break; - } -} - - -#endif // MIDI_USE_CALLBACKS - - -#endif // MIDI_BUILD_INPUT - - -// ============================================================================= -// MIDI Soft Thru -// ============================================================================= - -#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) - -// This method is called upon reception of a message -// and takes care of Thru filtering and sending. -template -void MidiInterface::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 - -END_MIDI_NAMESPACE diff --git a/src/midi_Inline.hpp b/src/midi_Inline.hpp index 99be57d..9b01df1 100644 --- a/src/midi_Inline.hpp +++ b/src/midi_Inline.hpp @@ -12,6 +12,466 @@ BEGIN_MIDI_NAMESPACE +// ----------------------------------------------------------------------------- +// Output +// ----------------------------------------------------------------------------- +#if MIDI_BUILD_OUTPUT + +/*! \brief Send a Note On message + \param inNoteNumber Pitch value in the MIDI format (0 to 127). + \param inVelocity Note attack velocity (0 to 127). A NoteOn with 0 velocity + is considered as a NoteOff. + \param inChannel The channel on which the message will be sent (1 to 16). + + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html + */ +void MidiInterface::sendNoteOn(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel) +{ + send(NoteOn, inNoteNumber, inVelocity, inChannel); +} + +/*! \brief Send a Note Off message + \param inNoteNumber Pitch value in the MIDI format (0 to 127). + \param inVelocity Release velocity (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + + Note: you can send NoteOn with zero velocity to make a NoteOff, this is based + on the Running Status principle, to avoid sending status messages and thus + sending only NoteOn data. This method will always send a real NoteOff message. + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html + */ +void MidiInterface::sendNoteOff(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel) +{ + send(NoteOff, inNoteNumber, inVelocity, inChannel); +} + +/*! \brief Send a Program Change message + \param inProgramNumber The Program to select (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + */ +void MidiInterface::sendProgramChange(DataByte inProgramNumber, + Channel inChannel) +{ + send(ProgramChange, inProgramNumber, 0, inChannel); +} + +/*! \brief Send a Control Change message + \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). + + See the detailed controllers numbers & description here: + http://www.somascape.org/midi/tech/spec.html#ctrlnums + */ +void MidiInterface::sendControlChange(DataByte inControlNumber, + DataByte inControlValue, + Channel inChannel) +{ + send(ControlChange, inControlNumber, inControlValue, inChannel); +} + +/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) + \param NoteNumber The note to apply AfterTouch to (0 to 127). + \param Pressure The amount of AfterTouch to apply (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MidiInterface::sendPolyPressure(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel) +{ + send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); +} + +/*! \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 MidiInterface::sendAfterTouch(DataByte inPressure, + Channel inChannel) +{ + send(AfterTouchChannel, inPressure, 0, inChannel); +} + +/*! \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 MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX, + center value is 0. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MidiInterface::sendPitchBend(int inPitchValue, + Channel inChannel) +{ + const unsigned int bend = inPitchValue - MIDI_PITCHBEND_MIN; + send(PitchBend, (bend & 0x7F), (bend >> 7) & 0x7F, inChannel); +} + + +/*! \brief Send a Pitch Bend message using a floating point value. + \param 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 MidiInterface::sendPitchBend(double inPitchValue, + Channel inChannel) +{ + const int value = inPitchValue * MIDI_PITCHBEND_MAX; + sendPitchBend(value, inChannel); +} + +/*! \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 for ArrayContainsBoundaries is set to 'false' for compatibility + with previous versions of the library. + */ +void MidiInterface::sendSysEx(unsigned int inLength, + const byte* inArray, + bool inArrayContainsBoundaries) +{ + if (inArrayContainsBoundaries == false) + { + MIDI_SERIAL_PORT.write(0xF0); + + for (int i=0;i> 7) & 0x7F); + +#if MIDI_USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif +} + +/*! \brief Send a Song Select message */ +void MidiInterface::sendSongSelect(DataByte inSongNumber) +{ + MIDI_SERIAL_PORT.write((byte)SongSelect); + MIDI_SERIAL_PORT.write(inSongNumber & 0x7F); + +#if MIDI_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 MidiType + */ +void MidiInterface::sendRealTime(MidiType inType) +{ + switch (inType) + { + case TuneRequest: // Not really real-time, but one byte anyway. + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + MIDI_SERIAL_PORT.write((byte)inType); + break; + default: + // Invalid Real Time marker + break; + } + + // Do not cancel Running Status for real-time messages as they can be + // interleaved within any message. Though, TuneRequest can be sent here, + // and as it is a System Common message, it must reset Running Status. +#if MIDI_USE_RUNNING_STATUS + if (inType == TuneRequest) mRunningStatus_TX = InvalidType; +#endif +} + +// ----------------------------------------------------------------------------- + +StatusByte MidiInterface::getStatus(MidiType inType, + Channel inChannel) const +{ + return ((byte)inType | ((inChannel - 1) & 0x0F)); +} + +#endif // MIDI_BUILD_OUTPUT + + +// ----------------------------------------------------------------------------- +// Input +// ----------------------------------------------------------------------------- + +#if MIDI_BUILD_INPUT + +/*! \brief Get the last received message's type + + Returns an enumerated type. @see MidiType + */ +MidiType MidiInterface::getType() const +{ + return mMessage.type; +} + +/*! \brief Get the channel of the message stored in the structure. + + \return Channel range is 1 to 16. + For non-channel messages, this will return 0. + */ +Channel MidiInterface::getChannel() const +{ + return mMessage.channel; +} + +/*! \brief Get the first data byte of the last received message. */ +DataByte MidiInterface::getData1() const +{ + return mMessage.data1; +} + +/*! \brief Get the second data byte of the last received message. */ +DataByte MidiInterface::getData2() const +{ + return mMessage.data2; +} + +/*! \brief Get the System Exclusive byte array. + + @see getSysExArrayLength to get the array's length in bytes. + */ +const byte* MidiInterface::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 MidiInterface::getSysExArrayLength() const +{ + const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1; + return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size; +} + +/*! \brief Check if a valid message is stored in the structure. */ +bool MidiInterface::check() const +{ + return mMessage.valid; +} + +// ----------------------------------------------------------------------------- + +Channel MidiInterface::getInputChannel() const +{ + return mInputChannel; +} + +/*! \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 input. + */ +void MidiInterface::setInputChannel(Channel inChannel) +{ + mInputChannel = inChannel; +} + +// ----------------------------------------------------------------------------- + +/*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, + made public so you can handle MidiTypes more easily. + */ +MidiType MidiInterface::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 (MidiType)(inStatus & 0xF0); // Channel message, remove channel nibble. + else return (MidiType)inStatus; +} + +// ----------------------------------------------------------------------------- + +#if MIDI_USE_CALLBACKS + +void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } +void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } +void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } +void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } +void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } +void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } +void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } +void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte* array, byte size)) { mSystemExclusiveCallback = fptr; } +void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } +void MidiInterface::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; } +void MidiInterface::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } +void MidiInterface::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } +void MidiInterface::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } +void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } + +/*! \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 MidiInterface::disconnectCallbackFromType(MidiType inType) +{ + switch (inType) + { + case NoteOff: mNoteOffCallback = 0; break; + case NoteOn: mNoteOnCallback = 0; break; + case AfterTouchPoly: mAfterTouchPolyCallback = 0; break; + case ControlChange: mControlChangeCallback = 0; break; + case ProgramChange: mProgramChangeCallback = 0; break; + case AfterTouchChannel: mAfterTouchChannelCallback = 0; break; + case PitchBend: mPitchBendCallback = 0; break; + case SystemExclusive: mSystemExclusiveCallback = 0; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = 0; break; + case SongPosition: mSongPositionCallback = 0; break; + case SongSelect: mSongSelectCallback = 0; break; + case TuneRequest: mTuneRequestCallback = 0; break; + case Clock: mClockCallback = 0; break; + case Start: mStartCallback = 0; break; + case Continue: mContinueCallback = 0; break; + case Stop: mStopCallback = 0; break; + case ActiveSensing: mActiveSensingCallback = 0; break; + case SystemReset: mSystemResetCallback = 0; break; + default: + break; + } +} + +#endif // MIDI_USE_CALLBACKS + +#endif // MIDI_BUILD_INPUT + + +// ----------------------------------------------------------------------------- +// Thru +// ----------------------------------------------------------------------------- + +#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) + +MidiFilterMode MidiInterface::getFilterMode() const +{ + return mThruFilterMode; +} + +bool MidiInterface::getThruState() const +{ + return mThruActivated; +} + +/*! \brief Setter method: turn message mirroring on. */ +void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) +{ + mThruActivated = true; + mThruFilterMode = inThruFilterMode; +} + + +/*! \brief Setter method: turn message mirroring off. */ +void MidiInterface::turnThruOff() +{ + mThruActivated = false; + mThruFilterMode = Off; +} + +/*! \brief Set the filter for thru mirroring + \param inThruFilterMode a filter mode + + @see MidiFilterMode + */ +void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode) +{ + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) + mThruActivated = true; + else + mThruActivated = false; +} + +#endif // MIDI_BUILD_THRU + +// ----------------------------------------------------------------------------- END_MIDI_NAMESPACE From ea96fbd2113bebf9fda3f8fed7f25f24a55cac1c Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 6 Sep 2012 11:16:44 +0200 Subject: [PATCH 34/38] Removed unused includes. --- src/MIDI.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index 44ec553..12f9d79 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -10,10 +10,8 @@ #pragma once -#include #include "midi_Settings.h" #include "midi_Defs.h" -#include #include "Arduino.h" // ----------------------------------------------------------------------------- From ad9ba0da58aa8d9f1bfa849110d544b769e8e2de Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 6 Sep 2012 11:16:53 +0200 Subject: [PATCH 35/38] Cleaning up examples. --- res/Examples/MIDI_Callbacks/MIDI_Callbacks.ino | 7 +++++-- res/Examples/MIDI_Input/MIDI_Input.ino | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/res/Examples/MIDI_Callbacks/MIDI_Callbacks.ino b/res/Examples/MIDI_Callbacks/MIDI_Callbacks.ino index 36d980e..b2bdb3d 100644 --- a/res/Examples/MIDI_Callbacks/MIDI_Callbacks.ino +++ b/res/Examples/MIDI_Callbacks/MIDI_Callbacks.ino @@ -6,8 +6,8 @@ // see documentation here: // http://arduinomidilib.sourceforge.net/class_m_i_d_i___class.html -void HandleNoteOn(byte channel, byte pitch, byte velocity) { - +void HandleNoteOn(byte channel, byte pitch, byte velocity) +{ // Do whatever you want when you receive a Note On. if (velocity == 0) { @@ -33,4 +33,7 @@ void loop() { MIDI.read(); // There is no need to check if there are messages incoming if they are bound to a Callback function. + // The attached method will be called automatically when the corresponding message has been received. } + + diff --git a/res/Examples/MIDI_Input/MIDI_Input.ino b/res/Examples/MIDI_Input/MIDI_Input.ino index 5ecb07d..0187a62 100644 --- a/res/Examples/MIDI_Input/MIDI_Input.ino +++ b/res/Examples/MIDI_Input/MIDI_Input.ino @@ -30,7 +30,7 @@ void setup() { void loop() { if (MIDI.read()) { // Is there a MIDI message incoming ? switch(MIDI.getType()) { // Get the type of the message we caught - case ProgramChange: // If it is a Program Change + case midi::ProgramChange: // If it is a Program Change BlinkLed(MIDI.getData1()); // Blink the LED a number of times // correponding to the program number // (0 to 127, it can last a while..) From 69604e458be4d593ba4928c72ba2c262123411ba Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 6 Sep 2012 20:41:25 +0200 Subject: [PATCH 36/38] Made unique set of files, to avoid stupid branches. --- src/MIDI.cpp | 15 +++++++++++---- src/MIDI.h | 7 ++++++- src/midi_Defs.h | 14 ++++++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index c88d05f..c223443 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -27,13 +27,20 @@ #if MIDI_AUTO_INSTANCIATE # if MIDI_USE_SOFTWARE_SERIAL -# include "../SoftwareSerial/SoftwareSerial.h" - SoftwareSerial softSerialClass(MIDI_SOFTSERIAL_RX_PIN, - MIDI_SOFTSERIAL_TX_PIN); +# ifndef FSE_AVR +# include "../SoftwareSerial/SoftwareSerial.h" + SoftwareSerial softSerialClass(MIDI_SOFTSERIAL_RX_PIN, + MIDI_SOFTSERIAL_TX_PIN); +# else +# error Todo: implement SoftwareSerial for avr core. # undef MIDI_SERIAL_PORT # define MIDI_SERIAL_PORT softSerialClass # else -# include "HardwareSerial.h" +# ifdef FSE_AVR +# include +# else +# include "HardwareSerial.h" +# endif # endif // MIDI_USE_SOFTWARE_SERIAL MIDI_NAMESPACE::MidiInterface MIDI; diff --git a/src/MIDI.h b/src/MIDI.h index 12f9d79..3b55365 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -12,7 +12,12 @@ #include "midi_Settings.h" #include "midi_Defs.h" -#include "Arduino.h" + +#ifdef FSE_AVR +# include "hardware_Serial.h" +#else +# include "Arduino.h" +#endif // ----------------------------------------------------------------------------- diff --git a/src/midi_Defs.h b/src/midi_Defs.h index ca3c027..249aec5 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -10,9 +10,18 @@ #pragma once -#include #include "midi_Namespace.h" +// ----------------------------------------------------------------------------- + +#ifdef FSE_AVR +# include +#else +# include +#endif + +// ----------------------------------------------------------------------------- + BEGIN_MIDI_NAMESPACE // ----------------------------------------------------------------------------- @@ -26,8 +35,9 @@ BEGIN_MIDI_NAMESPACE // ----------------------------------------------------------------------------- // Type definitions +#ifndef FSE_AVR typedef uint8_t byte; -typedef uint16_t word; +#endif typedef byte StatusByte; typedef byte DataByte; From 573bd35e8a6de3a491f990b5dc412e14dc87aebd Mon Sep 17 00:00:00 2001 From: Francois Best Date: Thu, 6 Sep 2012 20:43:20 +0200 Subject: [PATCH 37/38] Fixed build. --- src/MIDI.cpp | 1 + src/midi_Inline.hpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/MIDI.cpp b/src/MIDI.cpp index c223443..f776846 100644 --- a/src/MIDI.cpp +++ b/src/MIDI.cpp @@ -33,6 +33,7 @@ MIDI_SOFTSERIAL_TX_PIN); # else # error Todo: implement SoftwareSerial for avr core. +# endif # undef MIDI_SERIAL_PORT # define MIDI_SERIAL_PORT softSerialClass # else diff --git a/src/midi_Inline.hpp b/src/midi_Inline.hpp index 9b01df1..f26c3c8 100644 --- a/src/midi_Inline.hpp +++ b/src/midi_Inline.hpp @@ -143,14 +143,14 @@ void MidiInterface::sendSysEx(unsigned int inLength, { MIDI_SERIAL_PORT.write(0xF0); - for (int i=0;i Date: Thu, 6 Sep 2012 20:57:32 +0200 Subject: [PATCH 38/38] Updated keywords and installer. --- res/install_local_mac | 5 +++-- res/keywords.txt | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/res/install_local_mac b/res/install_local_mac index 9b870bf..cf72e3a 100755 --- a/res/install_local_mac +++ b/res/install_local_mac @@ -19,8 +19,9 @@ then mkdir $lib_path # Copy sources - cp ../src/MIDI.cpp $lib_path - cp ../src/MIDI.h $lib_path + cp ../src/MIDI.cpp $lib_path + cp ../src/MIDI.h $lib_path + cp ../src/midi_* $lib_path # Copy resources cp ../res/keywords.txt $lib_path diff --git a/res/keywords.txt b/res/keywords.txt index 5ce9af2..3532e56 100644 --- a/res/keywords.txt +++ b/res/keywords.txt @@ -8,7 +8,7 @@ MIDI KEYWORD1 MIDI.h KEYWORD1 -MIDI_Class KEYWORD1 +MidiInterface KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -76,6 +76,9 @@ getTypeFromStatusByte KEYWORD2 # Constants (LITERAL1) ####################################### +# Namespace, considering it as a literal +midi LITERAL1 + NoteOff LITERAL1 NoteOn LITERAL1 AfterTouchPoly LITERAL1