aboutsummaryrefslogtreecommitdiff
path: root/audio/midiparser_xmidi.cpp
diff options
context:
space:
mode:
authorMax Horn2011-02-09 01:09:01 +0000
committerMax Horn2011-02-09 01:09:01 +0000
commit42ab839dd6c8a1570b232101eb97f4e54de57935 (patch)
tree3b763d8913a87482b793e0348c88b9a5f40eecc9 /audio/midiparser_xmidi.cpp
parent386203a3d6ce1abf457c9110d695408ec5f01b85 (diff)
downloadscummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.tar.gz
scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.tar.bz2
scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.zip
AUDIO: Rename sound/ dir to audio/
svn-id: r55850
Diffstat (limited to 'audio/midiparser_xmidi.cpp')
-rw-r--r--audio/midiparser_xmidi.cpp375
1 files changed, 375 insertions, 0 deletions
diff --git a/audio/midiparser_xmidi.cpp b/audio/midiparser_xmidi.cpp
new file mode 100644
index 0000000000..edc7c7a943
--- /dev/null
+++ b/audio/midiparser_xmidi.cpp
@@ -0,0 +1,375 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "audio/midiparser.h"
+#include "audio/mididrv.h"
+#include "common/util.h"
+
+/**
+ * The XMIDI version of MidiParser.
+ *
+ * Much of this code is adapted from the XMIDI implementation from the exult
+ * project.
+ */
+class MidiParser_XMIDI : public MidiParser {
+protected:
+ NoteTimer _notes_cache[32];
+ uint32 _inserted_delta; // Track simulated deltas for note-off events
+
+ struct Loop {
+ byte *pos;
+ byte repeat;
+ };
+
+ Loop _loop[4];
+ int _loopCount;
+
+ XMidiCallbackProc _callbackProc;
+ void *_callbackData;
+
+protected:
+ uint32 readVLQ2(byte * &data);
+ void resetTracking();
+ void parseNextEvent(EventInfo &info);
+
+public:
+ MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _inserted_delta(0), _callbackProc(proc), _callbackData(data) {}
+ ~MidiParser_XMIDI() { }
+
+ bool loadMusic(byte *data, uint32 size);
+};
+
+
+// This is a special XMIDI variable length quantity
+uint32 MidiParser_XMIDI::readVLQ2(byte * &pos) {
+ uint32 value = 0;
+ while (!(pos[0] & 0x80)) {
+ value += *pos++;
+ }
+ return value;
+}
+
+void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
+ info.start = _position._play_pos;
+ info.delta = readVLQ2(_position._play_pos) - _inserted_delta;
+
+ // Process the next event.
+ _inserted_delta = 0;
+ info.event = *(_position._play_pos++);
+ switch (info.event >> 4) {
+ case 0x9: // Note On
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ info.length = readVLQ(_position._play_pos);
+ if (info.basic.param2 == 0) {
+ info.event = info.channel() | 0x80;
+ info.length = 0;
+ }
+ break;
+
+ case 0xC:
+ case 0xD:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = 0;
+ break;
+
+ case 0x8:
+ case 0xA:
+ case 0xE:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ break;
+
+ case 0xB:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+
+ // This isn't a full XMIDI implementation, but it should
+ // hopefully be "good enough" for most things.
+
+ switch (info.basic.param1) {
+ // Simplified XMIDI looping.
+ case 0x74: { // XMIDI_CONTROLLER_FOR_LOOP
+ byte *pos = _position._play_pos;
+ if (_loopCount < ARRAYSIZE(_loop) - 1)
+ _loopCount++;
+ else
+ warning("XMIDI: Exceeding maximum loop count %d", ARRAYSIZE(_loop));
+
+ _loop[_loopCount].pos = pos;
+ _loop[_loopCount].repeat = info.basic.param2;
+ break;
+ }
+
+ case 0x75: // XMIDI_CONTORLLER_NEXT_BREAK
+ if (_loopCount >= 0) {
+ if (info.basic.param2 < 64) {
+ // End the current loop.
+ _loopCount--;
+ } else {
+ // Repeat 0 means "loop forever".
+ if (_loop[_loopCount].repeat) {
+ if (--_loop[_loopCount].repeat == 0)
+ _loopCount--;
+ else
+ _position._play_pos = _loop[_loopCount].pos;
+ } else {
+ _position._play_pos = _loop[_loopCount].pos;
+ }
+ }
+ }
+ break;
+
+ case 0x77: // XMIDI_CONTROLLER_CALLBACK_TRIG
+ if (_callbackProc)
+ _callbackProc(info.basic.param2, _callbackData);
+ break;
+
+ case 0x6e: // XMIDI_CONTROLLER_CHAN_LOCK
+ case 0x6f: // XMIDI_CONTROLLER_CHAN_LOCK_PROT
+ case 0x70: // XMIDI_CONTROLLER_VOICE_PROT
+ case 0x71: // XMIDI_CONTROLLER_TIMBRE_PROT
+ case 0x72: // XMIDI_CONTROLLER_BANK_CHANGE
+ case 0x73: // XMIDI_CONTROLLER_IND_CTRL_PREFIX
+ case 0x76: // XMIDI_CONTROLLER_CLEAR_BB_COUNT
+ case 0x78: // XMIDI_CONTROLLER_SEQ_BRANCH_INDEX
+ default:
+ if (info.basic.param1 >= 0x6e && info.basic.param1 <= 0x78) {
+ warning("Unsupported XMIDI controller %d (0x%2x)",
+ info.basic.param1, info.basic.param1);
+ }
+ }
+
+ // Should we really keep passing the XMIDI controller events to
+ // the MIDI driver, or should we turn them into some kind of
+ // NOP events? (Dummy meta events, perhaps?) Ah well, it has
+ // worked so far, so it shouldn't cause any damage...
+
+ break;
+
+ case 0xF: // Meta or SysEx event
+ switch (info.event & 0x0F) {
+ case 0x2: // Song Position Pointer
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ break;
+
+ case 0x3: // Song Select
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = 0;
+ break;
+
+ case 0x6:
+ case 0x8:
+ case 0xA:
+ case 0xB:
+ case 0xC:
+ case 0xE:
+ info.basic.param1 = info.basic.param2 = 0;
+ break;
+
+ case 0x0: // SysEx
+ info.length = readVLQ(_position._play_pos);
+ info.ext.data = _position._play_pos;
+ _position._play_pos += info.length;
+ break;
+
+ case 0xF: // META event
+ info.ext.type = *(_position._play_pos++);
+ info.length = readVLQ(_position._play_pos);
+ info.ext.data = _position._play_pos;
+ _position._play_pos += info.length;
+ if (info.ext.type == 0x51 && info.length == 3) {
+ // Tempo event. We want to make these constant 500,000.
+ info.ext.data[0] = 0x07;
+ info.ext.data[1] = 0xA1;
+ info.ext.data[2] = 0x20;
+ }
+ break;
+
+ default:
+ warning("MidiParser_XMIDI::parseNextEvent: Unsupported event code %x", info.event);
+ }
+ }
+}
+
+bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) {
+ uint32 i = 0;
+ byte *start;
+ uint32 len;
+ uint32 chunk_len;
+ char buf[32];
+
+ _loopCount = -1;
+
+ unloadMusic();
+ byte *pos = data;
+
+ if (!memcmp(pos, "FORM", 4)) {
+ pos += 4;
+
+ // Read length of
+ len = read4high(pos);
+ start = pos;
+
+ // XDIRless XMIDI, we can handle them here.
+ if (!memcmp(pos, "XMID", 4)) {
+ warning("XMIDI doesn't have XDIR");
+ pos += 4;
+ _num_tracks = 1;
+ } else if (memcmp(pos, "XDIR", 4)) {
+ // Not an XMIDI that we recognise
+ warning("Expected 'XDIR' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ } else {
+ // Seems Valid
+ pos += 4;
+ _num_tracks = 0;
+
+ for (i = 4; i < len; i++) {
+ // Read 4 bytes of type
+ memcpy(buf, pos, 4);
+ pos += 4;
+
+ // Read length of chunk
+ chunk_len = read4high(pos);
+
+ // Add eight bytes
+ i += 8;
+
+ if (memcmp(buf, "INFO", 4)) {
+ // Must align
+ pos += (chunk_len + 1) & ~1;
+ i += (chunk_len + 1) & ~1;
+ continue;
+ }
+
+ // Must be at least 2 bytes long
+ if (chunk_len < 2) {
+ warning("Invalid chunk length %d for 'INFO' block", (int)chunk_len);
+ return false;
+ }
+
+ _num_tracks = (byte)read2low(pos);
+
+ if (chunk_len > 2) {
+ warning("Chunk length %d is greater than 2", (int)chunk_len);
+ pos += chunk_len - 2;
+ }
+ break;
+ }
+
+ // Didn't get to fill the header
+ if (_num_tracks == 0) {
+ warning("Didn't find a valid track count");
+ return false;
+ }
+
+ // Ok now to start part 2
+ // Goto the right place
+ pos = start + ((len + 1) & ~1);
+
+ if (memcmp(pos, "CAT ", 4)) {
+ // Not an XMID
+ warning("Expected 'CAT ' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ }
+ pos += 4;
+
+ // Now read length of this track
+ len = read4high(pos);
+
+ if (memcmp(pos, "XMID", 4)) {
+ // Not an XMID
+ warning("Expected 'XMID' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ }
+ pos += 4;
+
+ }
+
+ // Ok it's an XMIDI.
+ // We're going to identify and store the location for each track.
+ if (_num_tracks > ARRAYSIZE(_tracks)) {
+ warning("Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_num_tracks);
+ return false;
+ }
+
+ int tracks_read = 0;
+ while (tracks_read < _num_tracks) {
+ if (!memcmp(pos, "FORM", 4)) {
+ // Skip this plus the 4 bytes after it.
+ pos += 8;
+ } else if (!memcmp(pos, "XMID", 4)) {
+ // Skip this.
+ pos += 4;
+ } else if (!memcmp(pos, "TIMB", 4)) {
+ // Custom timbres?
+ // We don't support them.
+ // Read the length, skip it, and hope there was nothing there.
+ pos += 4;
+ len = read4high(pos);
+ pos += (len + 1) & ~1;
+ } else if (!memcmp(pos, "EVNT", 4)) {
+ // Ahh! What we're looking for at last.
+ _tracks[tracks_read] = pos + 8; // Skip the EVNT and length bytes
+ pos += 4;
+ len = read4high(pos);
+ pos += (len + 1) & ~1;
+ ++tracks_read;
+ } else {
+ warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ }
+ }
+
+ // If we got this far, we successfully established
+ // the locations for each of our tracks.
+ // Note that we assume the original data passed in
+ // will persist beyond this call, i.e. we do NOT
+ // copy the data to our own buffer. Take warning....
+ _ppqn = 60;
+ resetTracking();
+ setTempo(500000);
+ _inserted_delta = 0;
+ setTrack(0);
+ return true;
+ }
+
+ return false;
+}
+
+void MidiParser_XMIDI::resetTracking() {
+ MidiParser::resetTracking();
+ _inserted_delta = 0;
+}
+
+void MidiParser::defaultXMidiCallback(byte eventData, void *data) {
+ warning("MidiParser: defaultXMidiCallback(%d)", eventData);
+}
+
+MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data) {
+ return new MidiParser_XMIDI(proc, data);
+}