diff options
author | Jamieson Christian | 2003-05-23 04:19:47 +0000 |
---|---|---|
committer | Jamieson Christian | 2003-05-23 04:19:47 +0000 |
commit | c6568530bd6d558045c912c6318d1d7d8a60a39d (patch) | |
tree | bec2a496ca22b746d01b49afe74194be2bd756d5 /sound | |
parent | 006d0a5ff81771f8387f13187675601fd52e2668 (diff) | |
download | scummvm-rg350-c6568530bd6d558045c912c6318d1d7d8a60a39d.tar.gz scummvm-rg350-c6568530bd6d558045c912c6318d1d7d8a60a39d.tar.bz2 scummvm-rg350-c6568530bd6d558045c912c6318d1d7d8a60a39d.zip |
Revamped iMuse and Player classes. Player now uses MidiParser to parse its data, which will allow it to parse other MIDI formats. To receive parsed data, Player now derives from MidiDriver to act as a "fake MIDI driver".
Miscellaneous upgrades and fixes to MidiParser, including the Smart Jump (which could not be tested before iMuse started making use of the MidiParser).
*** THIS IS A BIG UPGRADE! EXTENSIVE REGRESSION TESTING IS NEEDED! ***
This has been tested through the intros and a number of other scenes from MI2, FOA and S&M.
NOTE! This upgrade introduces savegame format version V19. Earlier version savegames will load, but the music will simply start over from the beginning. Only V19 and later games will properly restore the position of the music! Don't say you weren't warned....
svn-id: r7849
Diffstat (limited to 'sound')
-rw-r--r-- | sound/mididrv.h | 5 | ||||
-rw-r--r-- | sound/midiparser.cpp | 202 | ||||
-rw-r--r-- | sound/midiparser.h | 63 | ||||
-rw-r--r-- | sound/midiparser_smf.cpp | 43 | ||||
-rw-r--r-- | sound/midiparser_xmidi.cpp | 42 | ||||
-rw-r--r-- | sound/mpu401.h | 2 |
6 files changed, 216 insertions, 141 deletions
diff --git a/sound/mididrv.h b/sound/mididrv.h index 55b5a8dad2..58bbe0dcdf 100644 --- a/sound/mididrv.h +++ b/sound/mididrv.h @@ -29,8 +29,9 @@ class MidiChannel; // Abstract MIDI Driver Class class MidiDriver { - public: + virtual ~MidiDriver() { } + // Error codes returned by open. // Can be converted to a string with getErrorName() enum { @@ -41,7 +42,7 @@ public: }; enum { - PROP_TIMEDIV = 1, +// PROP_TIMEDIV = 1, PROP_OLD_ADLIB = 2 }; diff --git a/sound/midiparser.cpp b/sound/midiparser.cpp index e781238d3b..4680d8295c 100644 --- a/sound/midiparser.cpp +++ b/sound/midiparser.cpp @@ -26,6 +26,8 @@ #include <stdio.h> #include <memory.h> + + ////////////////////////////////////////////////// // // MidiParser implementation @@ -42,15 +44,10 @@ _autoLoop (false), _smartJump (false), _num_tracks (0), _active_track (255), -_play_pos (0), -_play_time (0), -_play_tick (0), -_last_event_time (0), -_last_event_tick (0), -_running_status (0), +_abort_parse (0), _hanging_notes_count (0) { - memset (_active_notes, 0, 128); + memset (_active_notes, 0, sizeof(_active_notes)); } void MidiParser::property (int prop, int value) { @@ -62,6 +59,12 @@ void MidiParser::property (int prop, int value) { } } +void MidiParser::setTempo (uint32 tempo) { + _tempo = tempo; + if (_ppqn) + _psec_per_tick = (tempo + (_ppqn >> 2)) / _ppqn; +} + // This is the conventional (i.e. SMF) variable length quantity uint32 MidiParser::readVLQ (byte * &data) { byte str; @@ -137,10 +140,11 @@ void MidiParser::onTimer() { uint32 end_time; uint32 event_time; - if (!_play_pos || !_driver) + if (!_position._play_pos || !_driver) return; - end_time = _play_time + _timer_rate; + _abort_parse = false; + end_time = _position._play_time + _timer_rate; // Scan our hanging notes for any // that should be turned off. @@ -160,18 +164,18 @@ void MidiParser::onTimer() { } } - while (true) { + while (!_abort_parse) { EventInfo &info = _next_event; - event_time = _last_event_time + info.delta * _psec_per_tick; + event_time = _position._last_event_time + info.delta * _psec_per_tick; if (event_time > end_time) break; // Process the next info. - _last_event_tick += info.delta; + _position._last_event_tick += info.delta; if (info.event < 0x80) { printf ("ERROR! Bad command or running status %02X\n", info.event); - _play_pos = 0; + _position._play_pos = 0; return; } @@ -185,17 +189,16 @@ void MidiParser::onTimer() { // as well as sending it to the output device. allNotesOff(); if (_autoLoop) { - _play_pos = _tracks[_active_track]; + _position._play_pos = _tracks[_active_track]; parseNextEvent (_next_event); } else { - _play_pos = 0; + _position._play_pos = 0; _driver->metaEvent (info.ext.type, info.ext.data, (uint16) info.length); } return; } else if (info.ext.type == 0x51) { if (info.length >= 3) { - _tempo = info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]; - _psec_per_tick = (_tempo + (_ppqn >> 2)) / _ppqn; + setTempo (info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); } } _driver->metaEvent (info.ext.type, info.ext.data, (uint16) info.length); @@ -212,12 +215,16 @@ void MidiParser::onTimer() { } - _last_event_time = event_time; - parseNextEvent (_next_event); + if (!_abort_parse) { + _position._last_event_time = event_time; + parseNextEvent (_next_event); + } } - _play_time = end_time; - _play_tick = (_play_time - _last_event_time) / _psec_per_tick + _last_event_tick; + if (!_abort_parse) { + _position._play_time = end_time; + _position._play_tick = (_position._play_time - _position._last_event_time) / _psec_per_tick + _position._last_event_tick; + } } void MidiParser::allNotesOff() { @@ -230,18 +237,11 @@ void MidiParser::allNotesOff() { for (i = 0; i < ARRAYSIZE(_hanging_notes); ++i) _hanging_notes[i].time_left = 0; _hanging_notes_count = 0; - memset (_active_notes, 0, 128); + memset (_active_notes, 0, sizeof(_active_notes)); } void MidiParser::resetTracking() { - _play_pos = 0; - _tempo = 500000; - _psec_per_tick = 500000 / _ppqn; - _play_time = 0; - _play_tick = 0; - _last_event_time = 0; - _last_event_tick = 0; - _running_status = 0; + _position.clear(); } bool MidiParser::setTrack (int track) { @@ -250,79 +250,107 @@ bool MidiParser::setTrack (int track) { else if (track == _active_track) return true; + if (_smartJump) + hangAllActiveNotes(); + else + allNotesOff(); + resetTracking(); - allNotesOff(); _active_track = track; - _play_pos = _tracks[track]; + _position._play_pos = _tracks[track]; parseNextEvent (_next_event); return true; } -void MidiParser::jumpToTick (uint32 tick) { - if (_active_track >= _num_tracks) - return; - - if (!_smartJump) { - allNotesOff(); - } else { - // Search for note off events until we have - // accounted for every active note. - uint32 advance_tick = _last_event_tick; - while (true) { - int i; - for (i = 0; i < 128; ++i) - if (_active_notes[i] != 0) break; - if (i == 128) break; - parseNextEvent (_next_event); - advance_tick += _next_event.delta; - if (_next_event.command() == 0x8) { - if (_active_notes [_next_event.basic.param1] & (1 << _next_event.channel())) { - hangingNote (_next_event.channel(), _next_event.basic.param1, (advance_tick - _last_event_tick) * _psec_per_tick); - _active_notes [_next_event.basic.param1] &= ~ (1 << _next_event.channel()); - } - } else if (_next_event.event == 0xFF && _next_event.ext.type == 0x2F) { - break; +void MidiParser::hangAllActiveNotes() { + // Search for note off events until we have + // accounted for every active note. + uint32 advance_tick = _position._last_event_tick; + while (true) { + int i; + for (i = 0; i < 128; ++i) + if (_active_notes[i] != 0) break; + if (i == 128) break; + parseNextEvent (_next_event); + advance_tick += _next_event.delta; + if (_next_event.command() == 0x8) { + if (_active_notes [_next_event.basic.param1] & (1 << _next_event.channel())) { + hangingNote (_next_event.channel(), _next_event.basic.param1, (advance_tick - _position._last_event_tick) * _psec_per_tick); + _active_notes [_next_event.basic.param1] &= ~ (1 << _next_event.channel()); } + } else if (_next_event.event == 0xFF && _next_event.ext.type == 0x2F) { + printf ("MidiParser::hangAllActiveNotes(): Hit End of Track with active notes left!\n"); + memset (_active_notes, 0, sizeof (_active_notes)); + break; } } +} - resetTracking(); - _play_pos = _tracks[_active_track]; - parseNextEvent (_next_event); - if (tick == 0) - return; - - while (true) { - EventInfo &info = _next_event; - if (_last_event_tick + info.delta >= tick) { - _play_time += (tick - _last_event_tick) * _psec_per_tick; - _play_tick = tick; - break; - } +bool MidiParser::jumpToTick (uint32 tick, bool fireEvents) { + if (_active_track >= _num_tracks) + return false; - _last_event_tick += info.delta; - _play_tick = _last_event_tick; - _play_time += info.delta * _psec_per_tick; - _last_event_time = _play_time; + Tracker currentPos (_position); + EventInfo currentEvent (_next_event); - if (info.event == 0xFF) { - if (info.ext.type == 0x2F) { // End of track - if (_autoLoop) { - _play_pos = _tracks[_active_track]; - parseNextEvent (_next_event); - } else { - _play_pos = 0; - _driver->metaEvent (0x2F, info.ext.data, (uint16) info.length); - } + resetTracking(); + _position._play_pos = _tracks[_active_track]; + parseNextEvent (_next_event); + if (tick > 0) { + while (true) { + EventInfo &info = _next_event; + if (_position._last_event_tick + info.delta >= tick) { + _position._play_time += (tick - _position._last_event_tick) * _psec_per_tick; + _position._play_tick = tick; break; - } else if (info.ext.type == 0x51) { // Tempo - if (info.length >= 3) { - _tempo = info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]; - _psec_per_tick = (_tempo + (_ppqn >> 2)) / _ppqn; + } + + _position._last_event_tick += info.delta; + _position._last_event_time += info.delta * _psec_per_tick; + _position._play_tick = _position._last_event_tick; + _position._play_time = _position._last_event_time; + + if (info.event == 0xFF) { + if (info.ext.type == 0x2F) { // End of track + if (_autoLoop) { + _position._play_pos = _tracks[_active_track]; + parseNextEvent (_next_event); + } else { + _position = currentPos; + _next_event = currentEvent; + return false; + } + break; + } else if (info.ext.type == 0x51) { // Tempo + if (info.length >= 3) { + setTempo (info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); + } } + } else if (fireEvents) { + if (info.event == 0xF0) + _driver->sysEx (info.ext.data, (uint16) info.length); + else + _driver->send (info.event | info.basic.param1 << 8 | info.basic.param2 << 16); } + + parseNextEvent (_next_event); } + } - parseNextEvent (_next_event); + if (!_smartJump) { + allNotesOff(); + } else { + EventInfo targetEvent (_next_event); + Tracker targetPosition (_position); + + _position = currentPos; + _next_event = currentEvent; + hangAllActiveNotes(); + + _next_event = targetEvent; + _position = targetPosition; } + + _abort_parse = true; + return true; } diff --git a/sound/midiparser.h b/sound/midiparser.h index 8958891894..d8c2c373e4 100644 --- a/sound/midiparser.h +++ b/sound/midiparser.h @@ -28,6 +28,43 @@ class MidiParser; class MidiDriver; + + +////////////////////////////////////////////////// +// +// Support entities +// +////////////////////////////////////////////////// + +struct Tracker { + byte * _play_pos; + uint32 _play_time; + uint32 _play_tick; + uint32 _last_event_time; + uint32 _last_event_tick; + byte _running_status; + + Tracker() { clear(); } + + Tracker (const Tracker ©) : + _play_pos (copy._play_pos), + _play_time (copy._play_time), + _play_tick (copy._play_tick), + _last_event_time (copy._last_event_time), + _last_event_tick (copy._last_event_tick), + _running_status (copy._running_status) + { } + + void clear() { + _play_pos = 0; + _play_time = 0; + _play_tick = 0; + _last_event_time = 0; + _last_event_tick = 0; + _running_status = 0; + } +}; + struct EventInfo { byte * start; // Points to delta uint32 delta; @@ -55,6 +92,15 @@ struct NoteTimer { NoteTimer() : channel(0), note(0), time_left(0) {} }; + + + +////////////////////////////////////////////////// +// +// MidiParser declaration +// +////////////////////////////////////////////////// + class MidiParser { private: uint16 _active_notes[128]; // Each uint16 is a bit mask for channels that have that note on @@ -70,17 +116,13 @@ protected: bool _autoLoop; // For lightweight clients that don't monitor events bool _smartJump; // Support smart expiration of hanging notes when jumping - byte * _tracks[16]; + byte * _tracks[32]; byte _num_tracks; byte _active_track; - byte * _play_pos; - uint32 _play_time; - uint32 _play_tick; - uint32 _last_event_time; - uint32 _last_event_tick; - byte _running_status; + Tracker _position; EventInfo _next_event; + bool _abort_parse; // If a jump or some other interruption occurs protected: static uint32 readVLQ (byte * &data); @@ -90,6 +132,7 @@ protected: void activeNote (byte channel, byte note, bool active); void hangingNote (byte channel, byte note, uint32 ticks_left); + void hangAllActiveNotes(); // Multi-byte read helpers uint32 read4high (byte * &data) { @@ -121,11 +164,13 @@ public: virtual void property (int prop, int value); void setMidiDriver (MidiDriver *driver) { _driver = driver; } - void setTimerRate (uint32 rate) { _timer_rate = rate / 500; } + void setTimerRate (uint32 rate) { _timer_rate = rate; } + void setTempo (uint32 tempo); void onTimer(); bool setTrack (int track); - void jumpToTick (uint32 tick); + bool jumpToTick (uint32 tick, bool fireEvents = false); + uint32 getTick() { return _position._play_tick; } static MidiParser *createParser_SMF(); static MidiParser *createParser_XMIDI(); diff --git a/sound/midiparser_smf.cpp b/sound/midiparser_smf.cpp index d2e56d9dfc..0b9379ab33 100644 --- a/sound/midiparser_smf.cpp +++ b/sound/midiparser_smf.cpp @@ -80,8 +80,8 @@ void MidiParser_SMF::property (int prop, int value) { } void MidiParser_SMF::parseNextEvent (EventInfo &info) { - info.start = _play_pos; - info.delta = readVLQ (_play_pos); + info.start = _position._play_pos; + info.delta = readVLQ (_position._play_pos); // Process the next info. If mpMalformedPitchBends // was set, we must skip over any pitch bend events @@ -89,23 +89,24 @@ void MidiParser_SMF::parseNextEvent (EventInfo &info) { // real pitch bend events, they're just two-byte // prefixes before the real info. do { - if ((_play_pos[0] & 0xF0) >= 0x80) - info.event = *(_play_pos++); + if ((_position._play_pos[0] & 0xF0) >= 0x80) + info.event = *(_position._play_pos++); else - info.event = _running_status; - } while (_malformedPitchBends && (info.event & 0xF0) == 0xE0 && _play_pos++); + info.event = _position._running_status; + } while (_malformedPitchBends && (info.event & 0xF0) == 0xE0 && _position._play_pos++); if (info.event < 0x80) return; + _position._running_status = info.event; switch (info.command()) { case 0xC: case 0xD: - info.basic.param1 = *(_play_pos++); + info.basic.param1 = *(_position._play_pos++); info.basic.param2 = 0; break; case 0x8: case 0x9: case 0xA: case 0xB: case 0xE: - info.basic.param1 = *(_play_pos++); - info.basic.param2 = *(_play_pos++); + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); if (info.command() == 0x9 && info.basic.param2 == 0) info.event = info.channel() | 0x80; info.length = 0; @@ -114,12 +115,12 @@ void MidiParser_SMF::parseNextEvent (EventInfo &info) { case 0xF: // System Common, Meta or SysEx event switch (info.event & 0x0F) { case 0x2: // Song Position Pointer - info.basic.param1 = *(_play_pos++); - info.basic.param2 = *(_play_pos++); + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); break; case 0x3: // Song Select - info.basic.param1 = *(_play_pos++); + info.basic.param1 = *(_position._play_pos++); info.basic.param2 = 0; break; @@ -128,16 +129,16 @@ void MidiParser_SMF::parseNextEvent (EventInfo &info) { break; case 0x0: // SysEx - info.length = readVLQ (_play_pos); - info.ext.data = _play_pos; - _play_pos += info.length; + 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 = *(_play_pos++); - info.length = readVLQ (_play_pos); - info.ext.data = _play_pos; - _play_pos += info.length; + info.ext.type = *(_position._play_pos++); + info.length = readVLQ (_position._play_pos); + info.ext.data = _position._play_pos; + _position._play_pos += info.length; break; } } @@ -192,8 +193,8 @@ bool MidiParser_SMF::loadMusic (byte *data, uint32 size) { } // Now we 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); + if (_num_tracks > ARRAYSIZE(_tracks)) { + printf ("Can only handle %d tracks but was handed %d\n", (int) ARRAYSIZE(_tracks), (int) _num_tracks); return false; } diff --git a/sound/midiparser_xmidi.cpp b/sound/midiparser_xmidi.cpp index acaccff0b2..0d651a3349 100644 --- a/sound/midiparser_xmidi.cpp +++ b/sound/midiparser_xmidi.cpp @@ -76,17 +76,17 @@ uint32 MidiParser_XMIDI::readVLQ2 (byte * &pos) { } void MidiParser_XMIDI::parseNextEvent (EventInfo &info) { - info.start = _play_pos; - info.delta = readVLQ2 (_play_pos) - _inserted_delta; + info.start = _position._play_pos; + info.delta = readVLQ2 (_position._play_pos) - _inserted_delta; // Process the next event. _inserted_delta = 0; - info.event = *(_play_pos++); + info.event = *(_position._play_pos++); switch (info.event >> 4) { case 0x9: // Note On - info.basic.param1 = *(_play_pos++); - info.basic.param2 = *(_play_pos++); - info.length = readVLQ (_play_pos); + 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; @@ -94,24 +94,24 @@ void MidiParser_XMIDI::parseNextEvent (EventInfo &info) { break; case 0xC: case 0xD: - info.basic.param1 = *(_play_pos++); + info.basic.param1 = *(_position._play_pos++); info.basic.param2 = 0; break; case 0x8: case 0xA: case 0xB: case 0xE: - info.basic.param1 = *(_play_pos++); - info.basic.param2 = *(_play_pos++); + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); break; case 0xF: // Meta or SysEx event switch (info.event & 0x0F) { case 0x2: // Song Position Pointer - info.basic.param1 = *(_play_pos++); - info.basic.param2 = *(_play_pos++); + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); break; case 0x3: // Song Select - info.basic.param1 = *(_play_pos++); + info.basic.param1 = *(_position._play_pos++); info.basic.param2 = 0; break; @@ -120,16 +120,16 @@ void MidiParser_XMIDI::parseNextEvent (EventInfo &info) { break; case 0x0: // SysEx - info.length = readVLQ (_play_pos); - info.ext.data = _play_pos; - _play_pos += info.length; + 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 = *(_play_pos++); - info.length = readVLQ (_play_pos); - info.ext.data = _play_pos; - _play_pos += info.length; + 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; @@ -236,8 +236,8 @@ bool MidiParser_XMIDI::loadMusic (byte *data, uint32 size) { // 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); + if (_num_tracks > ARRAYSIZE(_tracks)) { + printf ("Can only handle %d tracks but was handed %d\n", (int) ARRAYSIZE(_tracks), (int) _num_tracks); return false; } diff --git a/sound/mpu401.h b/sound/mpu401.h index 3f4cfecfee..cd495b5040 100644 --- a/sound/mpu401.h +++ b/sound/mpu401.h @@ -88,7 +88,7 @@ public: MidiDriver_MPU401(); void setTimerCallback(void *timer_param, void (*timer_proc) (void *)); - uint32 getBaseTempo(void) { return 0x4A0000; } + uint32 getBaseTempo(void) { return 10000; } // 0x4A0000; } // Now referenced in microseconds between callbacks virtual void sysEx_customInstrument (byte channel, uint32 type, byte *instr); |