aboutsummaryrefslogtreecommitdiff
path: root/audio/softsynth/mt32/MidiStreamParser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'audio/softsynth/mt32/MidiStreamParser.cpp')
-rw-r--r--audio/softsynth/mt32/MidiStreamParser.cpp289
1 files changed, 289 insertions, 0 deletions
diff --git a/audio/softsynth/mt32/MidiStreamParser.cpp b/audio/softsynth/mt32/MidiStreamParser.cpp
new file mode 100644
index 0000000000..74fa0c3e5a
--- /dev/null
+++ b/audio/softsynth/mt32/MidiStreamParser.cpp
@@ -0,0 +1,289 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdio>
+#include <cstring>
+
+#include "internals.h"
+
+#include "MidiStreamParser.h"
+#include "Synth.h"
+
+using namespace MT32Emu;
+
+DefaultMidiStreamParser::DefaultMidiStreamParser(Synth &useSynth, Bit32u initialStreamBufferCapacity) :
+ MidiStreamParser(initialStreamBufferCapacity), synth(useSynth), timestampSet(false) {}
+
+void DefaultMidiStreamParser::setTimestamp(const Bit32u useTimestamp) {
+ timestampSet = true;
+ timestamp = useTimestamp;
+}
+
+void DefaultMidiStreamParser::resetTimestamp() {
+ timestampSet = false;
+}
+
+void DefaultMidiStreamParser::handleShortMessage(const Bit32u message) {
+ do {
+ if (timestampSet) {
+ if (synth.playMsg(message, timestamp)) return;
+ }
+ else {
+ if (synth.playMsg(message)) return;
+ }
+ } while (synth.reportHandler->onMIDIQueueOverflow());
+}
+
+void DefaultMidiStreamParser::handleSysex(const Bit8u *stream, const Bit32u length) {
+ do {
+ if (timestampSet) {
+ if (synth.playSysex(stream, length, timestamp)) return;
+ }
+ else {
+ if (synth.playSysex(stream, length)) return;
+ }
+ } while (synth.reportHandler->onMIDIQueueOverflow());
+}
+
+void DefaultMidiStreamParser::handleSystemRealtimeMessage(const Bit8u realtime) {
+ synth.reportHandler->onMIDISystemRealtime(realtime);
+}
+
+void DefaultMidiStreamParser::printDebug(const char *debugMessage) {
+ synth.printDebug("%s", debugMessage);
+}
+
+MidiStreamParser::MidiStreamParser(Bit32u initialStreamBufferCapacity) :
+ MidiStreamParserImpl(*this, *this, initialStreamBufferCapacity) {}
+
+MidiStreamParserImpl::MidiStreamParserImpl(MidiReceiver &useReceiver, MidiReporter &useReporter, Bit32u initialStreamBufferCapacity) :
+ midiReceiver(useReceiver), midiReporter(useReporter)
+{
+ if (initialStreamBufferCapacity < (Bit32u)SYSEX_BUFFER_SIZE) initialStreamBufferCapacity = SYSEX_BUFFER_SIZE;
+ if (MAX_STREAM_BUFFER_SIZE < initialStreamBufferCapacity) initialStreamBufferCapacity = MAX_STREAM_BUFFER_SIZE;
+ streamBufferCapacity = initialStreamBufferCapacity;
+ streamBuffer = new Bit8u[streamBufferCapacity];
+ streamBufferSize = 0;
+ runningStatus = 0;
+
+ reserved = NULL;
+}
+
+MidiStreamParserImpl::~MidiStreamParserImpl() {
+ delete[] streamBuffer;
+}
+
+void MidiStreamParserImpl::parseStream(const Bit8u *stream, Bit32u length) {
+ while (length > 0) {
+ Bit32u parsedMessageLength = 0;
+ if (0xF8 <= *stream) {
+ // Process System Realtime immediately and go on
+ midiReceiver.handleSystemRealtimeMessage(*stream);
+ parsedMessageLength = 1;
+ // No effect on the running status
+ } else if (streamBufferSize > 0) {
+ // Check if there is something in streamBuffer waiting for being processed
+ if (*streamBuffer == 0xF0) {
+ parsedMessageLength = parseSysexFragment(stream, length);
+ } else {
+ parsedMessageLength = parseShortMessageDataBytes(stream, length);
+ }
+ } else {
+ if (*stream == 0xF0) {
+ runningStatus = 0; // SysEx clears the running status
+ parsedMessageLength = parseSysex(stream, length);
+ } else {
+ parsedMessageLength = parseShortMessageStatus(stream);
+ }
+ }
+
+ // Parsed successfully
+ stream += parsedMessageLength;
+ length -= parsedMessageLength;
+ }
+}
+
+void MidiStreamParserImpl::processShortMessage(const Bit32u message) {
+ // Adds running status to the MIDI message if it doesn't contain one
+ Bit8u status = (Bit8u)message;
+ if (0xF8 <= status) {
+ midiReceiver.handleSystemRealtimeMessage(status);
+ } else if (processStatusByte(status)) {
+ midiReceiver.handleShortMessage((message << 8) | status);
+ } else if (0x80 <= status) { // If no running status available yet, skip this message
+ midiReceiver.handleShortMessage(message);
+ }
+}
+
+// We deal with SysEx messages below 512 bytes long in most cases. Nevertheless, it seems reasonable to support a possibility
+// to load bulk dumps using a single message. However, this is known to fail with a real device due to limited input buffer size.
+bool MidiStreamParserImpl::checkStreamBufferCapacity(const bool preserveContent) {
+ if (streamBufferSize < streamBufferCapacity) return true;
+ if (streamBufferCapacity < MAX_STREAM_BUFFER_SIZE) {
+ Bit8u *oldStreamBuffer = streamBuffer;
+ streamBufferCapacity = MAX_STREAM_BUFFER_SIZE;
+ streamBuffer = new Bit8u[streamBufferCapacity];
+ if (preserveContent) memcpy(streamBuffer, oldStreamBuffer, streamBufferSize);
+ delete[] oldStreamBuffer;
+ return true;
+ }
+ return false;
+}
+
+// Checks input byte whether it is a status byte. If not, replaces it with running status when available.
+// Returns true if the input byte was changed to running status.
+bool MidiStreamParserImpl::processStatusByte(Bit8u &status) {
+ if (status < 0x80) {
+ // First byte isn't status, try running status
+ if (runningStatus < 0x80) {
+ // No running status available yet
+ midiReporter.printDebug("processStatusByte: No valid running status yet, MIDI message ignored");
+ return false;
+ }
+ status = runningStatus;
+ return true;
+ } else if (status < 0xF0) {
+ // Store current status as running for a Voice message
+ runningStatus = status;
+ } else if (status < 0xF8) {
+ // System Common clears running status
+ runningStatus = 0;
+ } // System Realtime doesn't affect running status
+ return false;
+}
+
+// Returns # of bytes parsed
+Bit32u MidiStreamParserImpl::parseShortMessageStatus(const Bit8u stream[]) {
+ Bit8u status = *stream;
+ Bit32u parsedLength = processStatusByte(status) ? 0 : 1;
+ if (0x80 <= status) { // If no running status available yet, skip one byte
+ *streamBuffer = status;
+ ++streamBufferSize;
+ }
+ return parsedLength;
+}
+
+// Returns # of bytes parsed
+Bit32u MidiStreamParserImpl::parseShortMessageDataBytes(const Bit8u stream[], Bit32u length) {
+ const Bit32u shortMessageLength = Synth::getShortMessageLength(*streamBuffer);
+ Bit32u parsedLength = 0;
+
+ // Append incoming bytes to streamBuffer
+ while ((streamBufferSize < shortMessageLength) && (length-- > 0)) {
+ Bit8u dataByte = *(stream++);
+ if (dataByte < 0x80) {
+ // Add data byte to streamBuffer
+ streamBuffer[streamBufferSize++] = dataByte;
+ } else if (dataByte < 0xF8) {
+ // Discard invalid bytes and start over
+ char s[128];
+ sprintf(s, "parseShortMessageDataBytes: Invalid short message: status %02x, expected length %i, actual %i -> ignored", *streamBuffer, shortMessageLength, streamBufferSize);
+ midiReporter.printDebug(s);
+ streamBufferSize = 0; // Clear streamBuffer
+ return parsedLength;
+ } else {
+ // Bypass System Realtime message
+ midiReceiver.handleSystemRealtimeMessage(dataByte);
+ }
+ ++parsedLength;
+ }
+ if (streamBufferSize < shortMessageLength) return parsedLength; // Still lacks data bytes
+
+ // Assemble short message
+ Bit32u shortMessage = streamBuffer[0];
+ for (Bit32u i = 1; i < shortMessageLength; ++i) {
+ shortMessage |= streamBuffer[i] << (i << 3);
+ }
+ midiReceiver.handleShortMessage(shortMessage);
+ streamBufferSize = 0; // Clear streamBuffer
+ return parsedLength;
+}
+
+// Returns # of bytes parsed
+Bit32u MidiStreamParserImpl::parseSysex(const Bit8u stream[], const Bit32u length) {
+ // Find SysEx length
+ Bit32u sysexLength = 1;
+ while (sysexLength < length) {
+ Bit8u nextByte = stream[sysexLength++];
+ if (0x80 <= nextByte) {
+ if (nextByte == 0xF7) {
+ // End of SysEx
+ midiReceiver.handleSysex(stream, sysexLength);
+ return sysexLength;
+ }
+ if (0xF8 <= nextByte) {
+ // The System Realtime message must be processed right after return
+ // but the SysEx is actually fragmented and to be reconstructed in streamBuffer
+ --sysexLength;
+ break;
+ }
+ // Illegal status byte in SysEx message, aborting
+ midiReporter.printDebug("parseSysex: SysEx message lacks end-of-sysex (0xf7), ignored");
+ // Continue parsing from that point
+ return sysexLength - 1;
+ }
+ }
+
+ // Store incomplete SysEx message for further processing
+ streamBufferSize = sysexLength;
+ if (checkStreamBufferCapacity(false)) {
+ memcpy(streamBuffer, stream, sysexLength);
+ } else {
+ // Not enough buffer capacity, don't care about the real buffer content, just mark the first byte
+ *streamBuffer = *stream;
+ streamBufferSize = streamBufferCapacity;
+ }
+ return sysexLength;
+}
+
+// Returns # of bytes parsed
+Bit32u MidiStreamParserImpl::parseSysexFragment(const Bit8u stream[], const Bit32u length) {
+ Bit32u parsedLength = 0;
+ while (parsedLength < length) {
+ Bit8u nextByte = stream[parsedLength++];
+ if (nextByte < 0x80) {
+ // Add SysEx data byte to streamBuffer
+ if (checkStreamBufferCapacity(true)) streamBuffer[streamBufferSize++] = nextByte;
+ continue;
+ }
+ if (0xF8 <= nextByte) {
+ // Bypass System Realtime message
+ midiReceiver.handleSystemRealtimeMessage(nextByte);
+ continue;
+ }
+ if (nextByte != 0xF7) {
+ // Illegal status byte in SysEx message, aborting
+ midiReporter.printDebug("parseSysexFragment: SysEx message lacks end-of-sysex (0xf7), ignored");
+ // Clear streamBuffer and continue parsing from that point
+ streamBufferSize = 0;
+ --parsedLength;
+ break;
+ }
+ // End of SysEx
+ if (checkStreamBufferCapacity(true)) {
+ streamBuffer[streamBufferSize++] = nextByte;
+ midiReceiver.handleSysex(streamBuffer, streamBufferSize);
+ streamBufferSize = 0; // Clear streamBuffer
+ break;
+ }
+ // Encountered streamBuffer overrun
+ midiReporter.printDebug("parseSysexFragment: streamBuffer overrun while receiving SysEx message, ignored. Max allowed size of fragmented SysEx is 32768 bytes.");
+ streamBufferSize = 0; // Clear streamBuffer
+ break;
+ }
+ return parsedLength;
+}