diff options
author | Jamieson Christian | 2003-05-18 14:25:33 +0000 |
---|---|---|
committer | Jamieson Christian | 2003-05-18 14:25:33 +0000 |
commit | 79be84af363a6e39b813e539cfd9e525861cd8bc (patch) | |
tree | 6e48d2016c44337938bfdf09fbad6511bfb9ca6a | |
parent | fa82ad65f9abd4256717c28e5def45e2b5dbfb47 (diff) | |
download | scummvm-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.h | 51 | ||||
-rw-r--r-- | sound/midiparser_xmidi.cpp | 465 |
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; } |