Added multiple instanciation.
This commit is contained in:
parent
1b24b1d5d6
commit
a63afb0868
764
src/MIDI.cpp
764
src/MIDI.cpp
|
|
@ -36,779 +36,21 @@
|
||||||
# endif
|
# endif
|
||||||
# undef MIDI_SERIAL_PORT
|
# undef MIDI_SERIAL_PORT
|
||||||
# define MIDI_SERIAL_PORT softSerialClass
|
# define MIDI_SERIAL_PORT softSerialClass
|
||||||
|
MIDI_NAMESPACE::MidiInterface<SoftwareSerial> MIDI(MIDI_SERIAL_PORT);
|
||||||
|
|
||||||
# else
|
# else
|
||||||
# ifdef FSE_AVR
|
# ifdef FSE_AVR
|
||||||
# include <hardware_Serial.h>
|
# include <hardware_Serial.h>
|
||||||
# else
|
# else
|
||||||
# include "HardwareSerial.h"
|
# include "HardwareSerial.h"
|
||||||
# endif
|
# endif
|
||||||
|
MIDI_NAMESPACE::MidiInterface<HardwareSerial> MIDI((HardwareSerial&)Serial);
|
||||||
# endif // MIDI_USE_SOFTWARE_SERIAL
|
# endif // MIDI_USE_SOFTWARE_SERIAL
|
||||||
|
|
||||||
MIDI_NAMESPACE::MidiInterface MIDI;
|
|
||||||
|
|
||||||
#endif // MIDI_AUTO_INSTANCIATE
|
#endif // MIDI_AUTO_INSTANCIATE
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
BEGIN_MIDI_NAMESPACE
|
BEGIN_MIDI_NAMESPACE
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/*! \brief Constructor for MidiInterface. */
|
|
||||||
MidiInterface::MidiInterface()
|
|
||||||
{
|
|
||||||
#if MIDI_BUILD_INPUT && MIDI_USE_CALLBACKS
|
|
||||||
mNoteOffCallback = 0;
|
|
||||||
mNoteOnCallback = 0;
|
|
||||||
mAfterTouchPolyCallback = 0;
|
|
||||||
mControlChangeCallback = 0;
|
|
||||||
mProgramChangeCallback = 0;
|
|
||||||
mAfterTouchChannelCallback = 0;
|
|
||||||
mPitchBendCallback = 0;
|
|
||||||
mSystemExclusiveCallback = 0;
|
|
||||||
mTimeCodeQuarterFrameCallback = 0;
|
|
||||||
mSongPositionCallback = 0;
|
|
||||||
mSongSelectCallback = 0;
|
|
||||||
mTuneRequestCallback = 0;
|
|
||||||
mClockCallback = 0;
|
|
||||||
mStartCallback = 0;
|
|
||||||
mContinueCallback = 0;
|
|
||||||
mStopCallback = 0;
|
|
||||||
mActiveSensingCallback = 0;
|
|
||||||
mSystemResetCallback = 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! \brief Destructor for MidiInterface.
|
|
||||||
|
|
||||||
This is not really useful for the Arduino, as it is never called...
|
|
||||||
*/
|
|
||||||
MidiInterface::~MidiInterface()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/*! \brief Call the begin method in the setup() function of the Arduino.
|
|
||||||
|
|
||||||
All parameters are set to their default values:
|
|
||||||
- Input channel set to 1 if no value is specified
|
|
||||||
- Full thru mirroring
|
|
||||||
*/
|
|
||||||
void MidiInterface::begin(Channel inChannel)
|
|
||||||
{
|
|
||||||
// Initialise the Serial port
|
|
||||||
MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE);
|
|
||||||
|
|
||||||
#if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS
|
|
||||||
|
|
||||||
mRunningStatus_TX = InvalidType;
|
|
||||||
|
|
||||||
#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS
|
|
||||||
|
|
||||||
|
|
||||||
#if MIDI_BUILD_INPUT
|
|
||||||
|
|
||||||
mInputChannel = inChannel;
|
|
||||||
mRunningStatus_RX = InvalidType;
|
|
||||||
mPendingMessageIndex = 0;
|
|
||||||
mPendingMessageExpectedLenght = 0;
|
|
||||||
|
|
||||||
mMessage.valid = false;
|
|
||||||
mMessage.type = InvalidType;
|
|
||||||
mMessage.channel = 0;
|
|
||||||
mMessage.data1 = 0;
|
|
||||||
mMessage.data2 = 0;
|
|
||||||
|
|
||||||
#endif // MIDI_BUILD_INPUT
|
|
||||||
|
|
||||||
|
|
||||||
#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru
|
|
||||||
|
|
||||||
mThruFilterMode = Full;
|
|
||||||
mThruActivated = true;
|
|
||||||
|
|
||||||
#endif // Thru
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Output
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#if MIDI_BUILD_OUTPUT
|
|
||||||
|
|
||||||
/*! \brief Generate and send a MIDI message from the values given.
|
|
||||||
\param inType The message type (see type defines for reference)
|
|
||||||
\param inData1 The first data byte.
|
|
||||||
\param inData2 The second data byte (if the message contains only 1 data byte,
|
|
||||||
set this one to 0).
|
|
||||||
\param inChannel The output channel on which the message will be sent
|
|
||||||
(values from 1 to 16). Note: you cannot send to OMNI.
|
|
||||||
|
|
||||||
This is an internal method, use it only if you need to send raw data
|
|
||||||
from your code, at your own risks.
|
|
||||||
*/
|
|
||||||
void MidiInterface::send(MidiType inType,
|
|
||||||
DataByte inData1,
|
|
||||||
DataByte inData2,
|
|
||||||
Channel inChannel)
|
|
||||||
{
|
|
||||||
// Then test if channel is valid
|
|
||||||
if (inChannel >= MIDI_CHANNEL_OFF ||
|
|
||||||
inChannel == MIDI_CHANNEL_OMNI ||
|
|
||||||
inType < NoteOff)
|
|
||||||
{
|
|
||||||
|
|
||||||
#if MIDI_USE_RUNNING_STATUS
|
|
||||||
mRunningStatus_TX = InvalidType;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return; // Don't send anything
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inType <= PitchBend) // Channel messages
|
|
||||||
{
|
|
||||||
// Protection: remove MSBs on data
|
|
||||||
inData1 &= 0x7F;
|
|
||||||
inData2 &= 0x7F;
|
|
||||||
|
|
||||||
const StatusByte status = getStatus(inType, inChannel);
|
|
||||||
|
|
||||||
#if MIDI_USE_RUNNING_STATUS
|
|
||||||
// Check Running Status
|
|
||||||
if (mRunningStatus_TX != status)
|
|
||||||
{
|
|
||||||
// New message, memorise and send header
|
|
||||||
mRunningStatus_TX = status;
|
|
||||||
MIDI_SERIAL_PORT.write(mRunningStatus_TX);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Don't care about running status, send the status byte.
|
|
||||||
MIDI_SERIAL_PORT.write(status);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Then send data
|
|
||||||
MIDI_SERIAL_PORT.write(inData1);
|
|
||||||
if (inType != ProgramChange && inType != AfterTouchChannel)
|
|
||||||
MIDI_SERIAL_PORT.write(inData2);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (inType >= TuneRequest && inType <= SystemReset)
|
|
||||||
sendRealTime(inType); // System Real-time and 1 byte.
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! \brief Encode System Exclusive messages.
|
|
||||||
SysEx messages are encoded to guarantee transmission of data bytes higher than
|
|
||||||
127 without breaking the MIDI protocol. Use this static method to convert the
|
|
||||||
data you want to send.
|
|
||||||
\param inData The data to encode.
|
|
||||||
\param outSysEx The output buffer where to store the encoded message.
|
|
||||||
\param inLength The lenght of the input buffer.
|
|
||||||
\return The lenght of the encoded output buffer.
|
|
||||||
@see decodeSysEx
|
|
||||||
*/
|
|
||||||
byte MidiInterface::encodeSysEx(const byte* inData,
|
|
||||||
byte* outSysEx,
|
|
||||||
byte inLength)
|
|
||||||
{
|
|
||||||
byte retlen = 0;
|
|
||||||
byte compteur;
|
|
||||||
byte count7 = 0;
|
|
||||||
|
|
||||||
outSysEx[0] = 0;
|
|
||||||
for (compteur = 0; compteur < inLength; compteur++) {
|
|
||||||
byte c = inData[compteur] & 0x7F;
|
|
||||||
byte msb = inData[compteur] >> 7;
|
|
||||||
outSysEx[0] |= msb << count7;
|
|
||||||
outSysEx[1 + count7] = c;
|
|
||||||
|
|
||||||
if (count7++ == 6) {
|
|
||||||
outSysEx += 8;
|
|
||||||
retlen += 8;
|
|
||||||
outSysEx[0] = 0;
|
|
||||||
count7 = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return retlen + count7 + ((count7 != 0)?1:0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // MIDI_BUILD_OUTPUT
|
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Input
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#if MIDI_BUILD_INPUT
|
|
||||||
|
|
||||||
/*! \brief Read a MIDI message from the serial port
|
|
||||||
using the main input channel (see setInputChannel() for reference).
|
|
||||||
|
|
||||||
\return True if a valid message has been stored in the structure, false if not.
|
|
||||||
A valid message is a message that matches the input channel. \n\n
|
|
||||||
If the Thru is enabled and the messages matches the filter,
|
|
||||||
it is sent back on the MIDI output.
|
|
||||||
*/
|
|
||||||
bool MidiInterface::read()
|
|
||||||
{
|
|
||||||
return read(mInputChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! \brief Reading/thru-ing method, the same as read()
|
|
||||||
with a given input channel to read on.
|
|
||||||
*/
|
|
||||||
bool MidiInterface::read(Channel inChannel)
|
|
||||||
{
|
|
||||||
if (inChannel >= MIDI_CHANNEL_OFF)
|
|
||||||
return false; // MIDI Input disabled.
|
|
||||||
|
|
||||||
if (parse(inChannel))
|
|
||||||
{
|
|
||||||
if (inputFilter(inChannel))
|
|
||||||
{
|
|
||||||
|
|
||||||
#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU)
|
|
||||||
thruFilter(inChannel);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if MIDI_USE_CALLBACKS
|
|
||||||
launchCallback();
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Private method: MIDI parser
|
|
||||||
bool MidiInterface::parse(Channel inChannel)
|
|
||||||
{
|
|
||||||
const byte bytes_available = MIDI_SERIAL_PORT.available();
|
|
||||||
|
|
||||||
if (bytes_available == 0)
|
|
||||||
// No data available.
|
|
||||||
return false;
|
|
||||||
|
|
||||||
|
|
||||||
/* Parsing algorithm:
|
|
||||||
Get a byte from the serial buffer.
|
|
||||||
* If there is no pending message to be recomposed, start a new one.
|
|
||||||
- Find type and channel (if pertinent)
|
|
||||||
- Look for other bytes in buffer, call parser recursively,
|
|
||||||
until the message is assembled or the buffer is empty.
|
|
||||||
* Else, add the extracted byte to the pending message, and check validity.
|
|
||||||
When the message is done, store it.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const byte extracted = MIDI_SERIAL_PORT.read();
|
|
||||||
|
|
||||||
if (mPendingMessageIndex == 0)
|
|
||||||
{
|
|
||||||
// Start a new pending message
|
|
||||||
mPendingMessage[0] = extracted;
|
|
||||||
|
|
||||||
// Check for running status first
|
|
||||||
switch (getTypeFromStatusByte(mRunningStatus_RX))
|
|
||||||
{
|
|
||||||
// Only these types allow Running Status:
|
|
||||||
case NoteOff:
|
|
||||||
case NoteOn:
|
|
||||||
case AfterTouchPoly:
|
|
||||||
case ControlChange:
|
|
||||||
case ProgramChange:
|
|
||||||
case AfterTouchChannel:
|
|
||||||
case PitchBend:
|
|
||||||
|
|
||||||
// If the status byte is not received, prepend it
|
|
||||||
// to the pending message
|
|
||||||
if (extracted < 0x80)
|
|
||||||
{
|
|
||||||
mPendingMessage[0] = mRunningStatus_RX;
|
|
||||||
mPendingMessage[1] = extracted;
|
|
||||||
mPendingMessageIndex = 1;
|
|
||||||
}
|
|
||||||
// Else: well, we received another status byte,
|
|
||||||
// so the running status does not apply here.
|
|
||||||
// It will be updated upon completion of this message.
|
|
||||||
|
|
||||||
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1))
|
|
||||||
{
|
|
||||||
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
|
|
||||||
mMessage.channel = (mPendingMessage[0] & 0x0F)+1;
|
|
||||||
mMessage.data1 = mPendingMessage[1];
|
|
||||||
|
|
||||||
// Save data2 only if applicable
|
|
||||||
if (mPendingMessageExpectedLenght == 3)
|
|
||||||
mMessage.data2 = mPendingMessage[2];
|
|
||||||
else
|
|
||||||
mMessage.data2 = 0;
|
|
||||||
|
|
||||||
mPendingMessageIndex = 0;
|
|
||||||
mPendingMessageExpectedLenght = 0;
|
|
||||||
mMessage.valid = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// No running status
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
switch (getTypeFromStatusByte(mPendingMessage[0]))
|
|
||||||
{
|
|
||||||
// 1 byte messages
|
|
||||||
case Start:
|
|
||||||
case Continue:
|
|
||||||
case Stop:
|
|
||||||
case Clock:
|
|
||||||
case ActiveSensing:
|
|
||||||
case SystemReset:
|
|
||||||
case TuneRequest:
|
|
||||||
// Handle the message type directly here.
|
|
||||||
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
|
|
||||||
mMessage.channel = 0;
|
|
||||||
mMessage.data1 = 0;
|
|
||||||
mMessage.data2 = 0;
|
|
||||||
mMessage.valid = true;
|
|
||||||
|
|
||||||
// \fix Running Status broken when receiving Clock messages.
|
|
||||||
// Do not reset all input attributes, Running Status must remain unchanged.
|
|
||||||
//resetInput();
|
|
||||||
|
|
||||||
// We still need to reset these
|
|
||||||
mPendingMessageIndex = 0;
|
|
||||||
mPendingMessageExpectedLenght = 0;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// 2 bytes messages
|
|
||||||
case ProgramChange:
|
|
||||||
case AfterTouchChannel:
|
|
||||||
case TimeCodeQuarterFrame:
|
|
||||||
case SongSelect:
|
|
||||||
mPendingMessageExpectedLenght = 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// 3 bytes messages
|
|
||||||
case NoteOn:
|
|
||||||
case NoteOff:
|
|
||||||
case ControlChange:
|
|
||||||
case PitchBend:
|
|
||||||
case AfterTouchPoly:
|
|
||||||
case SongPosition:
|
|
||||||
mPendingMessageExpectedLenght = 3;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SystemExclusive:
|
|
||||||
// The message can be any lenght
|
|
||||||
// between 3 and MIDI_SYSEX_ARRAY_SIZE bytes
|
|
||||||
mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE;
|
|
||||||
mRunningStatus_RX = InvalidType;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case InvalidType:
|
|
||||||
default:
|
|
||||||
// This is obviously wrong. Let's get the hell out'a here.
|
|
||||||
resetInput();
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then update the index of the pending message.
|
|
||||||
mPendingMessageIndex++;
|
|
||||||
|
|
||||||
#if USE_1BYTE_PARSING
|
|
||||||
// Message is not complete.
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
// Call the parser recursively
|
|
||||||
// to parse the rest of the message.
|
|
||||||
return parse(inChannel);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// First, test if this is a status byte
|
|
||||||
if (extracted >= 0x80)
|
|
||||||
{
|
|
||||||
// Reception of status bytes in the middle of an uncompleted message
|
|
||||||
// are allowed only for interleaved Real Time message or EOX
|
|
||||||
switch (extracted)
|
|
||||||
{
|
|
||||||
case Clock:
|
|
||||||
case Start:
|
|
||||||
case Continue:
|
|
||||||
case Stop:
|
|
||||||
case ActiveSensing:
|
|
||||||
case SystemReset:
|
|
||||||
|
|
||||||
// Here we will have to extract the one-byte message,
|
|
||||||
// pass it to the structure for being read outside
|
|
||||||
// the MIDI class, and recompose the message it was
|
|
||||||
// interleaved into. Oh, and without killing the running status..
|
|
||||||
// This is done by leaving the pending message as is,
|
|
||||||
// it will be completed on next calls.
|
|
||||||
|
|
||||||
mMessage.type = (MidiType)extracted;
|
|
||||||
mMessage.data1 = 0;
|
|
||||||
mMessage.data2 = 0;
|
|
||||||
mMessage.channel = 0;
|
|
||||||
mMessage.valid = true;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
// End of Exclusive
|
|
||||||
case 0xF7:
|
|
||||||
if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive)
|
|
||||||
{
|
|
||||||
// Store System Exclusive array in midimsg structure
|
|
||||||
for (byte i=0;i<MIDI_SYSEX_ARRAY_SIZE;i++)
|
|
||||||
mMessage.sysex_array[i] = mPendingMessage[i];
|
|
||||||
|
|
||||||
mMessage.type = SystemExclusive;
|
|
||||||
|
|
||||||
// Get length
|
|
||||||
mMessage.data1 = (mPendingMessageIndex+1) & 0xFF;
|
|
||||||
mMessage.data2 = (mPendingMessageIndex+1) >> 8;
|
|
||||||
mMessage.channel = 0;
|
|
||||||
mMessage.valid = true;
|
|
||||||
|
|
||||||
resetInput();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Well well well.. error.
|
|
||||||
resetInput();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add extracted data byte to pending message
|
|
||||||
mPendingMessage[mPendingMessageIndex] = extracted;
|
|
||||||
|
|
||||||
// Now we are going to check if we have reached the end of the message
|
|
||||||
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1))
|
|
||||||
{
|
|
||||||
// "FML" case: fall down here with an overflown SysEx..
|
|
||||||
// This means we received the last possible data byte that can fit
|
|
||||||
// the buffer. If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE.
|
|
||||||
if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive)
|
|
||||||
{
|
|
||||||
resetInput();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
|
|
||||||
// Don't check if it is a Channel Message
|
|
||||||
mMessage.channel = (mPendingMessage[0] & 0x0F)+1;
|
|
||||||
|
|
||||||
mMessage.data1 = mPendingMessage[1];
|
|
||||||
|
|
||||||
// Save data2 only if applicable
|
|
||||||
if (mPendingMessageExpectedLenght == 3)
|
|
||||||
mMessage.data2 = mPendingMessage[2];
|
|
||||||
else
|
|
||||||
mMessage.data2 = 0;
|
|
||||||
|
|
||||||
// Reset local variables
|
|
||||||
mPendingMessageIndex = 0;
|
|
||||||
mPendingMessageExpectedLenght = 0;
|
|
||||||
|
|
||||||
mMessage.valid = true;
|
|
||||||
|
|
||||||
// Activate running status (if enabled for the received type)
|
|
||||||
switch (mMessage.type)
|
|
||||||
{
|
|
||||||
case NoteOff:
|
|
||||||
case NoteOn:
|
|
||||||
case AfterTouchPoly:
|
|
||||||
case ControlChange:
|
|
||||||
case ProgramChange:
|
|
||||||
case AfterTouchChannel:
|
|
||||||
case PitchBend:
|
|
||||||
// Running status enabled: store it from received message
|
|
||||||
mRunningStatus_RX = mPendingMessage[0];
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// No running status
|
|
||||||
mRunningStatus_RX = InvalidType;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Then update the index of the pending message.
|
|
||||||
mPendingMessageIndex++;
|
|
||||||
|
|
||||||
#if USE_1BYTE_PARSING
|
|
||||||
// Message is not complete.
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
// Call the parser recursively
|
|
||||||
// to parse the rest of the message.
|
|
||||||
return parse(inChannel);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// What are our chances to fall here?
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private method: check if the received message is on the listened channel
|
|
||||||
bool MidiInterface::inputFilter(Channel inChannel)
|
|
||||||
{
|
|
||||||
// This method handles recognition of channel
|
|
||||||
// (to know if the message is destinated to the Arduino)
|
|
||||||
|
|
||||||
if (mMessage.type == InvalidType)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// First, check if the received message is Channel
|
|
||||||
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
|
|
||||||
{
|
|
||||||
// Then we need to know if we listen to it
|
|
||||||
if ((mMessage.channel == mInputChannel) ||
|
|
||||||
(mInputChannel == MIDI_CHANNEL_OMNI))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We don't listen to this channel
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// System messages are always received
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private method: reset input attributes
|
|
||||||
void MidiInterface::resetInput()
|
|
||||||
{
|
|
||||||
mPendingMessageIndex = 0;
|
|
||||||
mPendingMessageExpectedLenght = 0;
|
|
||||||
mRunningStatus_RX = InvalidType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#if MIDI_USE_CALLBACKS
|
|
||||||
|
|
||||||
// Private - launch callback function based on received type.
|
|
||||||
void MidiInterface::launchCallback()
|
|
||||||
{
|
|
||||||
// The order is mixed to allow frequent messages to trigger their callback faster.
|
|
||||||
switch (mMessage.type)
|
|
||||||
{
|
|
||||||
// Notes
|
|
||||||
case NoteOff: if (mNoteOffCallback != 0) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
|
|
||||||
case NoteOn: if (mNoteOnCallback != 0) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
|
|
||||||
|
|
||||||
// Real-time messages
|
|
||||||
case Clock: if (mClockCallback != 0) mClockCallback(); break;
|
|
||||||
case Start: if (mStartCallback != 0) mStartCallback(); break;
|
|
||||||
case Continue: if (mContinueCallback != 0) mContinueCallback(); break;
|
|
||||||
case Stop: if (mStopCallback != 0) mStopCallback(); break;
|
|
||||||
case ActiveSensing: if (mActiveSensingCallback != 0) mActiveSensingCallback(); break;
|
|
||||||
|
|
||||||
// Continuous controllers
|
|
||||||
case ControlChange: if (mControlChangeCallback != 0) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
|
|
||||||
case PitchBend: if (mPitchBendCallback != 0) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) + MIDI_PITCHBEND_MIN); break; // TODO: check this
|
|
||||||
case AfterTouchPoly: if (mAfterTouchPolyCallback != 0) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
|
|
||||||
case AfterTouchChannel: if (mAfterTouchChannelCallback != 0) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break;
|
|
||||||
|
|
||||||
case ProgramChange: if (mProgramChangeCallback != 0) mProgramChangeCallback(mMessage.channel,mMessage.data1); break;
|
|
||||||
case SystemExclusive: if (mSystemExclusiveCallback != 0) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break;
|
|
||||||
|
|
||||||
// Occasional messages
|
|
||||||
case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != 0) mTimeCodeQuarterFrameCallback(mMessage.data1); break;
|
|
||||||
case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break;
|
|
||||||
case SongSelect: if (mSongSelectCallback != 0) mSongSelectCallback(mMessage.data1); break;
|
|
||||||
case TuneRequest: if (mTuneRequestCallback != 0) mTuneRequestCallback(); break;
|
|
||||||
|
|
||||||
case SystemReset: if (mSystemResetCallback != 0) mSystemResetCallback(); break;
|
|
||||||
case InvalidType:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // MIDI_USE_CALLBACKS
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/*! \brief Decode System Exclusive messages.
|
|
||||||
SysEx messages are encoded to guarantee transmission of data bytes higher than
|
|
||||||
127 without breaking the MIDI protocol. Use this static method to reassemble
|
|
||||||
your received message.
|
|
||||||
\param inSysEx The SysEx data received from MIDI in.
|
|
||||||
\param outData The output buffer where to store the decrypted message.
|
|
||||||
\param inLength The lenght of the input buffer.
|
|
||||||
\return The lenght of the output buffer.
|
|
||||||
@see encodeSysEx @see getSysExArrayLength
|
|
||||||
*/
|
|
||||||
byte MidiInterface::decodeSysEx(const byte* inSysEx,
|
|
||||||
byte* outData,
|
|
||||||
byte inLength)
|
|
||||||
{
|
|
||||||
|
|
||||||
byte cnt;
|
|
||||||
byte cnt2 = 0;
|
|
||||||
byte bits = 0;
|
|
||||||
|
|
||||||
for (cnt = 0; cnt < inLength; ++cnt) {
|
|
||||||
|
|
||||||
if ((cnt % 8) == 0) {
|
|
||||||
bits = inSysEx[cnt];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
outData[cnt2++] = inSysEx[cnt] | ((bits & 1) << 7);
|
|
||||||
bits >>= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return cnt2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // MIDI_BUILD_INPUT
|
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Thru
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#if MIDI_BUILD_THRU
|
|
||||||
|
|
||||||
// This method is called upon reception of a message
|
|
||||||
// and takes care of Thru filtering and sending.
|
|
||||||
|
|
||||||
void MidiInterface::thruFilter(Channel inChannel)
|
|
||||||
{
|
|
||||||
|
|
||||||
/*
|
|
||||||
This method handles Soft-Thru filtering.
|
|
||||||
|
|
||||||
Soft-Thru filtering:
|
|
||||||
- All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off
|
|
||||||
- Channel messages are passed to the output whether their channel is matching the input channel and the filter setting
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// If the feature is disabled, don't do anything.
|
|
||||||
if (!mThruActivated || (mThruFilterMode == Off))
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
// First, check if the received message is Channel
|
|
||||||
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
|
|
||||||
{
|
|
||||||
const bool filter_condition = ((mMessage.channel == mInputChannel) ||
|
|
||||||
(mInputChannel == MIDI_CHANNEL_OMNI));
|
|
||||||
|
|
||||||
// Now let's pass it to the output
|
|
||||||
switch (mThruFilterMode)
|
|
||||||
{
|
|
||||||
case Full:
|
|
||||||
send(mMessage.type,
|
|
||||||
mMessage.data1,
|
|
||||||
mMessage.data2,
|
|
||||||
mMessage.channel);
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
case SameChannel:
|
|
||||||
if (filter_condition)
|
|
||||||
{
|
|
||||||
send(mMessage.type,
|
|
||||||
mMessage.data1,
|
|
||||||
mMessage.data2,
|
|
||||||
mMessage.channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DifferentChannel:
|
|
||||||
if (!filter_condition)
|
|
||||||
{
|
|
||||||
send(mMessage.type,
|
|
||||||
mMessage.data1,
|
|
||||||
mMessage.data2,
|
|
||||||
mMessage.channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Off:
|
|
||||||
// Do nothing.
|
|
||||||
// Technically it's impossible to get there because
|
|
||||||
// the case was already tested earlier.
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Send the message to the output
|
|
||||||
switch (mMessage.type)
|
|
||||||
{
|
|
||||||
// Real Time and 1 byte
|
|
||||||
case Clock:
|
|
||||||
case Start:
|
|
||||||
case Stop:
|
|
||||||
case Continue:
|
|
||||||
case ActiveSensing:
|
|
||||||
case SystemReset:
|
|
||||||
case TuneRequest:
|
|
||||||
sendRealTime(mMessage.type);
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SystemExclusive:
|
|
||||||
// Send SysEx (0xF0 and 0xF7 are included in the buffer)
|
|
||||||
sendSysEx(mMessage.data1,mMessage.sysex_array,true);
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SongSelect:
|
|
||||||
sendSongSelect(mMessage.data1);
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SongPosition:
|
|
||||||
sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7));
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TimeCodeQuarterFrame:
|
|
||||||
sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2);
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // MIDI_BUILD_THRU
|
|
||||||
|
|
||||||
END_MIDI_NAMESPACE
|
END_MIDI_NAMESPACE
|
||||||
|
|
|
||||||
13
src/MIDI.h
13
src/MIDI.h
|
|
@ -13,22 +13,17 @@
|
||||||
#include "midi_Settings.h"
|
#include "midi_Settings.h"
|
||||||
#include "midi_Defs.h"
|
#include "midi_Defs.h"
|
||||||
|
|
||||||
#ifdef FSE_AVR
|
|
||||||
# include "hardware_Serial.h"
|
|
||||||
#else
|
|
||||||
# include "Arduino.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
BEGIN_MIDI_NAMESPACE
|
BEGIN_MIDI_NAMESPACE
|
||||||
|
|
||||||
/*! \brief The main class for MIDI handling.
|
/*! \brief The main class for MIDI handling.
|
||||||
*/
|
*/
|
||||||
|
template<class SerialPort>
|
||||||
class MidiInterface
|
class MidiInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MidiInterface();
|
MidiInterface(SerialPort& inSerial);
|
||||||
~MidiInterface();
|
~MidiInterface();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -221,6 +216,8 @@ private:
|
||||||
|
|
||||||
#endif // MIDI_USE_RUNNING_STATUS
|
#endif // MIDI_USE_RUNNING_STATUS
|
||||||
|
|
||||||
|
private:
|
||||||
|
SerialPort& mSerial;
|
||||||
};
|
};
|
||||||
|
|
||||||
END_MIDI_NAMESPACE
|
END_MIDI_NAMESPACE
|
||||||
|
|
@ -228,7 +225,7 @@ END_MIDI_NAMESPACE
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
#if MIDI_AUTO_INSTANCIATE
|
#if MIDI_AUTO_INSTANCIATE
|
||||||
extern MIDI_NAMESPACE::MidiInterface MIDI;
|
extern MIDI_NAMESPACE::MidiInterface<MIDI_SERIAL_CLASS> MIDI;
|
||||||
#endif // MIDI_AUTO_INSTANCIATE
|
#endif // MIDI_AUTO_INSTANCIATE
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -194,4 +194,9 @@ struct Message
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \
|
||||||
|
midi::MidiInterface<Type> Name((Type&)SerialPort);
|
||||||
|
|
||||||
END_MIDI_NAMESPACE
|
END_MIDI_NAMESPACE
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -12,8 +12,6 @@
|
||||||
|
|
||||||
#include "midi_Namespace.h"
|
#include "midi_Namespace.h"
|
||||||
|
|
||||||
BEGIN_MIDI_NAMESPACE
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Here are a few settings you can change to customize
|
// Here are a few settings you can change to customize
|
||||||
|
|
@ -33,21 +31,26 @@ BEGIN_MIDI_NAMESPACE
|
||||||
|
|
||||||
#define MIDI_USE_CALLBACKS 1
|
#define MIDI_USE_CALLBACKS 1
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Create a MIDI object automatically on the port defined with MIDI_SERIAL_PORT.
|
// Create a MIDI object automatically on the port defined with MIDI_SERIAL_PORT.
|
||||||
#define MIDI_AUTO_INSTANCIATE 1
|
// You can turn this off by adding #define MIDI_AUTO_INSTANCIATE 0 just before
|
||||||
|
// including <MIDI.h> in your sketch.
|
||||||
|
#ifndef MIDI_AUTO_INSTANCIATE
|
||||||
|
# ifndef FSE_AVR
|
||||||
|
# define MIDI_AUTO_INSTANCIATE 1
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Serial port configuration
|
// Default serial port configuration (if MIDI_AUTO_INSTANCIATE is set)
|
||||||
|
|
||||||
// Set the default port to use for MIDI.
|
// Set the default port to use for MIDI.
|
||||||
#define MIDI_SERIAL_PORT Serial
|
#if MIDI_AUTO_INSTANCIATE
|
||||||
|
# define MIDI_DEFAULT_SERIAL_PORT Serial
|
||||||
// Software serial options
|
# define MIDI_SERIAL_CLASS HardwareSerial
|
||||||
#define MIDI_USE_SOFTWARE_SERIAL 0
|
# include "Arduino.h"
|
||||||
|
# include "HardwareSerial.h"
|
||||||
#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
|
#endif
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
@ -62,4 +65,8 @@ BEGIN_MIDI_NAMESPACE
|
||||||
#define MIDI_BAUDRATE 31250
|
#define MIDI_BAUDRATE 31250
|
||||||
#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes.
|
#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes.
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
BEGIN_MIDI_NAMESPACE
|
||||||
|
|
||||||
END_MIDI_NAMESPACE
|
END_MIDI_NAMESPACE
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue