aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJamieson Christian2003-05-18 14:25:33 +0000
committerJamieson Christian2003-05-18 14:25:33 +0000
commit79be84af363a6e39b813e539cfd9e525861cd8bc (patch)
tree6e48d2016c44337938bfdf09fbad6511bfb9ca6a
parentfa82ad65f9abd4256717c28e5def45e2b5dbfb47 (diff)
downloadscummvm-rg350-79be84af363a6e39b813e539cfd9e525861cd8bc.tar.gz
scummvm-rg350-79be84af363a6e39b813e539cfd9e525861cd8bc.tar.bz2
scummvm-rg350-79be84af363a6e39b813e539cfd9e525861cd8bc.zip
New plug-in MIDI parser modules, INCOMPLETE.
svn-id: r7636
-rw-r--r--sound/midiparser.h51
-rw-r--r--sound/midiparser_xmidi.cpp465
2 files changed, 516 insertions, 0 deletions
diff --git a/sound/midiparser.h b/sound/midiparser.h
new file mode 100644
index 0000000000..3324f4e268
--- /dev/null
+++ b/sound/midiparser.h
@@ -0,0 +1,51 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2001-2003 The ScummVM project
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header$
+ *
+ */
+
+#ifndef INCLUDED_MIDIPARSER
+#define INCLUDED_MIDIPARSER
+
+class MidiParser;
+
+#include "common/scummsys.h"
+
+class MidiDriver;
+
+class MidiParser {
+protected:
+ MidiDriver *_driver;
+ uint32 _timer_rate;
+
+public:
+ virtual bool loadMusic (byte *data) = 0;
+ virtual void unloadMusic() = 0;
+
+ void setMidiDriver (MidiDriver *driver) { _driver = driver; }
+ void setTimerRate (uint32 rate) { _timer_rate = rate / 500; }
+ virtual void onTimer() = 0;
+
+ virtual void setTrack (byte track) = 0;
+ virtual void jumpToTick (uint32 tick) = 0;
+
+ static MidiParser *createParser_RIFF();
+ static MidiParser *createParser_XMIDI();
+};
+
+#endif
diff --git a/sound/midiparser_xmidi.cpp b/sound/midiparser_xmidi.cpp
new file mode 100644
index 0000000000..7ac705d3bb
--- /dev/null
+++ b/sound/midiparser_xmidi.cpp
@@ -0,0 +1,465 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2001-2003 The ScummVM project
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header$
+ *
+ */
+
+#include "midiparser.h"
+#include "mididrv.h"
+#include "common/util.h"
+
+#include <stdio.h>
+#include <memory.h>
+
+//////////////////////////////////////////////////
+//
+// The XMIDI version of MidiParser
+//
+//////////////////////////////////////////////////
+
+struct NoteTimer {
+ byte channel;
+ byte note;
+ uint32 time_left;
+};
+
+class MidiParser_XMIDI : public MidiParser {
+protected:
+ byte *_data;
+ uint16 _num_tracks;
+ byte *_tracks [16];
+
+ byte _active_track;
+ byte *_play_pos;
+ uint32 _play_time;
+ uint32 _last_event_time;
+
+ NoteTimer _notes_cache[32];
+
+ int _lock;
+
+protected:
+ uint32 read4high (byte * &data) {
+ uint32 val = 0;
+ int i;
+ for (i = 0; i < 4; ++i) val = (val << 8) | *data++;
+ return val;
+ }
+ uint16 read2low (byte * &data) {
+ uint16 val = 0;
+ int i;
+ for (i = 0; i < 2; ++i) val |= (*data++) << (i * 8);
+ return val;
+ }
+ uint32 readVLQ (byte * &data);
+ uint32 readVLQ2 (byte * &data);
+
+public:
+ bool loadMusic (byte *data);
+ void unloadMusic();
+
+ void setMidiDriver (MidiDriver *driver) { _driver = driver; }
+ void setTimerRate (uint32 rate) { _timer_rate = rate; }
+ void onTimer();
+
+ void setTrack (byte track);
+ void jumpToTick (uint32 tick);
+};
+
+
+
+// This delta time is based on an assumed tempo of
+// 120 quarter notes per minute (500,000 microseconds per quarter node)
+// and 60 ticks per quarter note, i.e. 120 Hz overall.
+// 500,000 / 60 = 8333.33 microseconds per tick.
+#define MICROSECONDS_PER_TICK 8333
+
+
+
+//////////////////////////////////////////////////
+//
+// MidiParser_XMIDI implementation
+//
+// Much of this code is adapted from the XMIDI
+// implementation from the exult project.
+//
+//////////////////////////////////////////////////
+
+// This is the conventional (i.e. SMF) variable length quantity
+uint32 MidiParser_XMIDI::readVLQ (byte * &data) {
+ byte str;
+ uint32 value = 0;
+ int i;
+
+ for (i = 0; i < 4; ++i) {
+ str = data[0];
+ ++data;
+ value = (value << 7) | (str & 0x7F);
+ if (!(str & 0x80))
+ break;
+ }
+ return value;
+}
+
+// This is a special XMIDI variable length quantity
+uint32 MidiParser_XMIDI::readVLQ2 (byte * &pos) {
+ uint32 value = 0;
+ int i;
+
+ for (i = 0; i < 4; ++i) {
+ if (pos[0] & 0x80)
+ break;
+ value += *pos++;
+ }
+ return value;
+}
+
+void MidiParser_XMIDI::onTimer() {
+ uint32 delta;
+ uint32 end_time;
+ uint32 event_time;
+ byte *pos;
+ byte *oldpos;
+ byte event;
+ uint32 length;
+ int i;
+ NoteTimer *ptr;
+ bool active_notes;
+ byte note;
+ byte vel;
+ uint32 note_length;
+
+ if (_lock++) {
+ --_lock;
+ return;
+ }
+
+ if (!_play_pos || !_driver)
+ return;
+
+ end_time = _play_time + _timer_rate;
+ pos = _play_pos;
+
+ // Send any necessary note off events.
+ active_notes = false;
+
+ ptr = &_notes_cache[0];
+ for (i = ARRAYSIZE(_notes_cache); i; --i, ++ptr) {
+ if (ptr->time_left) {
+ if (ptr->time_left <= _timer_rate) {
+ _driver->send (0x80 | ptr->channel | (ptr->note << 8));
+ ptr->time_left = 0;
+ } else {
+ active_notes = true;
+ ptr->time_left -= _timer_rate;
+ }
+ }
+ }
+
+ while (true) {
+ oldpos = pos;
+ delta = readVLQ2 (pos);
+ event_time = _last_event_time + delta * MICROSECONDS_PER_TICK;
+ if (event_time > end_time) {
+ pos = oldpos;
+ break;
+ }
+
+ // Process the next event.
+ event = *pos++;
+ switch (event >> 4) {
+ case 0x9: // Note On
+ note = pos[0];
+ vel = pos[1];
+ pos += 2;
+ note_length = readVLQ (pos) * MICROSECONDS_PER_TICK;
+
+ ptr = &_notes_cache[0];
+ for (i = ARRAYSIZE(_notes_cache); i; --i, ++ptr) {
+ if (!ptr->time_left) {
+ ptr->time_left = note_length;
+ ptr->channel = event & 0x0F;
+ ptr->note = note;
+ _driver->send (event | (note << 8) | (vel << 16));
+ break;
+ }
+ }
+ break;
+
+ case 0xC: // Program Change
+ case 0xD: // Channel Aftertouch
+ _driver->send (event | (pos[0] << 8));
+ ++pos;
+ break;
+
+ case 0x8: // Note Off (do these ever occur in XMIDI??)
+ case 0xA: // Key Aftertouch
+ case 0xB: // Control Change
+ case 0xE: // Pitch Bender Change
+ _driver->send (event | (pos[0] << 8) | (pos[1] << 16));
+ pos += 2;
+ break;
+
+ case 0xF: // Meta or SysEx event
+ switch (event & 0x0F) {
+ case 0x2: // Song Position Pointer
+ _driver->send (event | (pos[0] << 8) | (pos[1] << 16));
+ pos += 2;
+ break;
+
+ case 0x3: // Song Select
+ _driver->send (event | (pos[0] << 8));
+ ++pos;
+ break;
+
+ case 0x6: // Tune Request
+ case 0x8: // MIDI Timing Clock
+ case 0xA: // Sequencer Start
+ case 0xB: // Sequencer Continue
+ case 0xC: // Sequencer Stop
+ case 0xE: // Active Sensing
+ _driver->send (event);
+
+ case 0x0: // SysEx
+ length = readVLQ (pos);
+ _driver->sysEx (pos, (uint16)(length - 1));
+ pos += length;
+ break;
+
+ case 0xF: // META event
+ event = *pos++;
+ // Skip any META events except end of song.
+ if (event == 0x2F) {
+ // End of song. See if there are still holding notes
+ // we need to wait for.
+ if (!active_notes) {
+ _play_pos = 0;
+ setTrack (_active_track + 1);
+ } else {
+ // Still active notes. Back up to the start of this
+ // event once again, and set all note timings to
+ // 1. That way they'll get turned off next clock.
+ ptr = &_notes_cache[0];
+ for (i = ARRAYSIZE(_notes_cache); i; --i, ++ptr) {
+ if (ptr->time_left)
+ ptr->time_left = 1;
+ }
+ _play_pos -= 2;
+ }
+ _lock = 0;
+ return;
+ } else {
+ length = readVLQ (pos);
+ pos += length;
+ }
+ break;
+ }
+ }
+
+ _last_event_time = event_time;
+ }
+
+ _play_time = end_time;
+ _play_pos = pos;
+ _lock = 0;
+}
+
+// This code was adapted from the exult methods
+// XMIDI::ExtractTracks and XMIDI::ExtractTracksFromXmi
+bool MidiParser_XMIDI::loadMusic (byte *data) {
+ uint32 i = 0;
+ byte *start;
+ uint32 len;
+ uint32 chunk_len;
+ char buf[32];
+
+ 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)) {
+ printf ("Warning: XMIDI doesn't have XDIR\n");
+ pos += 4;
+ _num_tracks = 1;
+ } else if (memcmp (pos, "XDIR", 4)) {
+ // Not an XMIDI that we recognise
+ printf ("Expected 'XDIR' but found '%c%c%c%c'\n", 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) {
+ printf ("Invalid chunk length %d for 'INFO' block!\n", (int) chunk_len);
+ return false;
+ }
+
+ _num_tracks = read2low (pos);
+
+ if (chunk_len > 2) {
+ printf ("Chunk length %d is greater than 2\n", (int) chunk_len);
+ pos += chunk_len - 2;
+ }
+ break;
+ }
+
+ // Didn't get to fill the header
+ if (_num_tracks == 0) {
+ printf ("Didn't find a valid track count\n");
+ 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
+ printf ("Expected 'CAT ' but found '%c%c%c%c'\n", 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
+ printf ("Expected 'XMID' but found '%c%c%c%c'\n", 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 > 16) {
+ printf ("Can only handle 16 tracks but was handed %d\n", (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 {
+ printf ("Hit invalid block '%c%c%c%c' while scanning for track locations\n", 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....
+ _data = data;
+ _active_track = 255;
+ setTrack (0);
+ return true;
+ }
+
+ return false;
+}
+
+void MidiParser_XMIDI::unloadMusic() {
+ int i;
+ NoteTimer *ptr;
+
+ while (_lock);
+ ++_lock;
+
+ _play_pos = NULL;
+ _data = NULL;
+ _num_tracks = 0;
+ _active_track = 0;
+ _play_time = 0;
+ _last_event_time = 0;
+
+ // Send any necessary note off events.
+ ptr = &_notes_cache[0];
+ for (i = ARRAYSIZE(_notes_cache); i; --i, ++ptr) {
+ if (ptr->time_left) {
+ _driver->send ((0x80 | ptr->channel) | (ptr->note << 8));
+ ptr->time_left = 0;
+ }
+ }
+
+ _lock = 0;
+}
+
+void MidiParser_XMIDI::setTrack (byte track) {
+ if (track >= _num_tracks || track == _active_track)
+ return;
+ _active_track = track;
+ _play_time = 0;
+ _last_event_time = 0;
+ _play_pos = _tracks[track];
+}
+
+void MidiParser_XMIDI::jumpToTick (uint32 tick) {
+ // TODO: Implement this.
+}
+
+MidiParser *MidiParser::createParser_XMIDI() { return new MidiParser_XMIDI; }