diff --git a/src/MIDI.hpp b/src/MIDI.hpp index be7c28a1..138ccc47 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -830,87 +830,34 @@ inline bool MidiInterface::read(Channel inChannel template bool MidiInterface::parse() { - if (mTransport.available() == 0) - return false; // No data available. - // clear the ErrorParse bit - mLastError &= ~(1UL << ErrorParse); + if (mTransport.available() == 0) + 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. + mLastError &= ~(1UL << ErrorParse); // Clear ErrorParse bit const byte extracted = mTransport.read(); - // Ignore Undefined - if (extracted == Undefined_FD) - return (Settings::Use1ByteParsing) ? false : parse(); - - if (mPendingMessageIndex == 0) - { - // Start a new pending message - mPendingMessage[0] = extracted; - - // Check for running status first - if (isChannelMessage(getTypeFromStatusByte(mRunningStatus_RX))) - { - // Only these types allow Running Status - - // 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. - } - - const MidiType pendingType = getTypeFromStatusByte(mPendingMessage[0]); - - switch (pendingType) - { - // 1 byte messages + if (extracted >= 0x80) { + // Get Status including Undefined ones so we can properly deal with them + const MidiType pendingType = extracted < 0xF0 ? MidiType(extracted & 0xF0) : MidiType(extracted); + switch (pendingType) { + // One byte realtime will complete now case Start: case Continue: case Stop: case Clock: - case Tick: + case Tick: // <---- Is tick relevant anymore?? .... Not part of the MIDI spec case ActiveSensing: case SystemReset: - case TuneRequest: - // Handle the message type directly here. - mMessage.type = pendingType; + // Might as well quickly deal with these now. + mMessage.type = pendingType; mMessage.channel = 0; - mMessage.data1 = 0; - mMessage.data2 = 0; - mMessage.valid = true; - - // Do not reset all input attributes, Running Status must remain unchanged. - // We still need to reset these - mPendingMessageIndex = 0; - mPendingMessageExpectedLength = 0; - + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.length = 1; + mMessage.valid = true; return true; - break; - - // 2 bytes messages - case ProgramChange: - case AfterTouchChannel: - case TimeCodeQuarterFrame: - case SongSelect: - mPendingMessageExpectedLength = 2; - break; - - // 3 bytes messages case NoteOn: case NoteOff: case ControlChange: @@ -919,209 +866,120 @@ bool MidiInterface::parse() case SongPosition: mPendingMessageExpectedLength = 3; break; - + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLength = 2; + mPendingMessage[2] = 0; // Zero unused data + break; + // One byte common will be completed this run (This will also reset running status as this is not realtime) + case TuneRequest: + mPendingMessageExpectedLength = 1; + mPendingMessage[1] = 0; // Zero unused data + mPendingMessage[2] = 0; // Zero unused data + break; case SystemExclusiveStart: - case SystemExclusiveEnd: - // The message can be any length - // between 3 and MidiMessage::sSysExMaxSize bytes mPendingMessageExpectedLength = MidiMessage::sSysExMaxSize; - mRunningStatus_RX = InvalidType; - mMessage.sysexArray[0] = pendingType; + mMessage.sysexArray[0] = SystemExclusiveStart; break; - - case InvalidType: + case SystemExclusiveEnd: + if (mPendingMessage[0] == SystemExclusiveStart) { // If were currently doing SysEx + mPendingMessageExpectedLength = ++mPendingMessageIndex; // Update expected lenght as we have an EOX and within Buffer + break; + } default: - // This is obviously wrong. Let's get the hell out'a here. - mLastError |= 1UL << ErrorParse; // set the ErrorParse bit + mLastError |= 1UL << ErrorParse; // Error: Undefined Status or stray EOX if (mErrorCallback) - mErrorCallback(mLastError); // LCOV_EXCL_LINE - - resetInput(); + mErrorCallback(mLastError); + if ((pendingType != Undefined_F9) && (pendingType != Undefined_FD)) { // Dont reset for 0xFD & 0xF9 (Undefined Realtime) + mPendingMessageIndex = 0; // Message reset for stray EOX and Undefined common F4/F5 + } return false; - break; } + mPendingMessage[0] = extracted; // Status seems good so lets store in pending + if (extracted != SystemExclusiveEnd) mPendingMessageIndex = 1; // Set PendingMessageIndex to 1 (needs to be unchanged for EOX) - if (mPendingMessageIndex >= (mPendingMessageExpectedLength - 1)) - { - // Reception complete - mMessage.type = pendingType; - mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); - mMessage.data1 = mPendingMessage[1]; - mMessage.data2 = 0; // Completed new message has 1 data byte - mMessage.length = 1; - - mPendingMessageIndex = 0; - mPendingMessageExpectedLength = 0; - mMessage.valid = true; - - return true; - } - else - { - // Waiting for more data - mPendingMessageIndex++; - } - - return (Settings::Use1ByteParsing) ? false : parse(); - } - 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 Tick: - 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.length = 1; - mMessage.valid = true; - - return true; - - // Exclusive - case SystemExclusiveStart: - case SystemExclusiveEnd: - if ((mMessage.sysexArray[0] == SystemExclusiveStart) - || (mMessage.sysexArray[0] == SystemExclusiveEnd)) - { - // Store the last byte (EOX) - mMessage.sysexArray[mPendingMessageIndex++] = extracted; - mMessage.type = SystemExclusive; - - // Get length - mMessage.data1 = mPendingMessageIndex & 0xff; // LSB - mMessage.data2 = byte(mPendingMessageIndex >> 8); // MSB - mMessage.channel = 0; - mMessage.length = mPendingMessageIndex; - mMessage.valid = true; - - resetInput(); - - return true; - } - else - { - // Well well well.. error. - mLastError |= 1UL << ErrorParse; // set the error bits - if (mErrorCallback) - mErrorCallback(mLastError); // LCOV_EXCL_LINE - - resetInput(); - return false; - } - // LCOV_EXCL_START - Coverage blind spot - default: - break; - // LCOV_EXCL_STOP - } + } else { + // Check Status + if (mPendingMessageIndex == 0) { + mLastError |= 1UL << ErrorParse; // Error: No Status + if (mErrorCallback) + mErrorCallback(mLastError); + return false; } - - // Add extracted data byte to pending message - if ((mPendingMessage[0] == SystemExclusiveStart) - || (mPendingMessage[0] == SystemExclusiveEnd)) + // Add Data + if (mPendingMessage[0] == SystemExclusive) mMessage.sysexArray[mPendingMessageIndex] = extracted; else mPendingMessage[mPendingMessageIndex] = extracted; - - // Now we are going to check if we have reached the end of the message - if (mPendingMessageIndex >= (mPendingMessageExpectedLength - 1)) - { - // SysEx larger than the allocated buffer size, + mPendingMessageIndex++; + } + // Check for a complete message + if (mPendingMessageIndex >= mPendingMessageExpectedLength) { + // Process SysEx + if (mPendingMessage[0] == SystemExclusiveStart || mPendingMessage[0] == SystemExclusiveEnd) { + + mMessage.type = SystemExclusive; + mMessage.data1 = mPendingMessageIndex & 0xff; // LSB + mMessage.data2 = byte(mPendingMessageIndex >> 8); // MSB + mMessage.channel = 0; + mMessage.length = mPendingMessageIndex; + + // SysEx can be larger than the allocated buffer size (must handle with callbacks only) // Split SysEx like so: - // first: 0xF0 .... 0xF0 - // midlle: 0xF7 .... 0xF0 - // last: 0xF7 .... 0xF7 - if ((mPendingMessage[0] == SystemExclusiveStart) - || (mPendingMessage[0] == SystemExclusiveEnd)) - { - auto lastByte = mMessage.sysexArray[Settings::SysExMaxSize - 1]; - mMessage.sysexArray[Settings::SysExMaxSize - 1] = SystemExclusiveStart; - mMessage.type = SystemExclusive; + // first: 0xF0 .... 0xF0 + // midlle: 0xF7 .... 0xF0 + // last: 0xF7 .... 0xF7 - // Get length - mMessage.data1 = Settings::SysExMaxSize & 0xff; // LSB - mMessage.data2 = byte(Settings::SysExMaxSize >> 8); // MSB - mMessage.channel = 0; - mMessage.length = Settings::SysExMaxSize; - mMessage.valid = true; - - // No need to check against the inputChannel, - // SysEx ignores input channel + // SysEx buffer not full... EOX + if (mPendingMessage[0] == SystemExclusiveEnd) { + mMessage.sysexArray[mPendingMessageIndex - 1] = SystemExclusiveEnd; + mPendingMessageIndex = 0; + if (mMessage.sysexArray[0] == SystemExclusiveEnd) { // This is the last chunk of split SysEx + mMessage.valid = false; + launchCallback(); + } else { // Not Split SysEx + mMessage.valid = true; + } + } else { // SysEx buffer full + if (mMessage.sysexArray[0] == SystemExclusiveStart) { + if (mErrorCallback) + mErrorCallback(1UL << WarningSplitSysEx); // Notify but no need to store this warning? + } + byte lastByte = mMessage.sysexArray[Settings::SysExMaxSize - 1]; + mMessage.sysexArray[Settings::SysExMaxSize - 1] = SystemExclusiveStart; + mMessage.valid = false; launchCallback(); + // Prep next SysEx data chunk to start with 0xF7 mMessage.sysexArray[0] = SystemExclusiveEnd; mMessage.sysexArray[1] = lastByte; - mPendingMessageIndex = 2; - - return false; } - mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); - - if (isChannelMessage(mMessage.type)) - mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); - else - mMessage.channel = 0; - - mMessage.data1 = mPendingMessage[1]; - // Save data2 only if applicable - mMessage.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0; - mMessage.length = mPendingMessageExpectedLength; + return mMessage.valid; + } - // Reset local variables + // Process message + if (mPendingMessage[0] < 0xF0) { // Channel message + mMessage.type = MidiType(mPendingMessage[0] & 0xF0); + mMessage.channel = (mPendingMessage[0] & 0x0F) + 1; + mPendingMessageIndex = 1; // Set to 1 for running status + } else { // Common message + mMessage.type = MidiType(mPendingMessage[0]); + mMessage.channel = 0; mPendingMessageIndex = 0; - mPendingMessageExpectedLength = 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++; + mMessage.data1 = mPendingMessage[1]; + mMessage.data2 = mPendingMessage[2]; + mMessage.length = mPendingMessageExpectedLength; + mMessage.valid = true; - return (Settings::Use1ByteParsing) ? false : parse(); - } + return true; + } else { + // We need more input... + return (Settings::Use1ByteParsing) ? false : parse(); } }