aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJamieson Christian2003-05-22 15:34:30 +0000
committerJamieson Christian2003-05-22 15:34:30 +0000
commit1918dc555f122cb21e6e158b1377e19c5e84ab13 (patch)
treee56f60af76bf92d216b63c417e22aa9b28f005d5
parent10fad2635a88db971dfac9d22961e2a99e890f20 (diff)
downloadscummvm-rg350-1918dc555f122cb21e6e158b1377e19c5e84ab13.tar.gz
scummvm-rg350-1918dc555f122cb21e6e158b1377e19c5e84ab13.tar.bz2
scummvm-rg350-1918dc555f122cb21e6e158b1377e19c5e84ab13.zip
Added "smart-jump" capability to MidiParser.
svn-id: r7831
-rw-r--r--sound/midiparser.cpp143
-rw-r--r--sound/midiparser.h24
-rw-r--r--sound/midiparser_smf.cpp13
-rw-r--r--sound/midiparser_xmidi.cpp69
4 files changed, 168 insertions, 81 deletions
diff --git a/sound/midiparser.cpp b/sound/midiparser.cpp
index d7bb26a114..e781238d3b 100644
--- a/sound/midiparser.cpp
+++ b/sound/midiparser.cpp
@@ -39,19 +39,26 @@ _ppqn (96),
_tempo (500000),
_psec_per_tick (5208), // 500000 / 96
_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)
-{ }
+_running_status (0),
+_hanging_notes_count (0)
+{
+ memset (_active_notes, 0, 128);
+}
void MidiParser::property (int prop, int value) {
switch (prop) {
case mpAutoLoop:
_autoLoop = (value != 0);
+ case mpSmartJump:
+ _smartJump = (value != 0);
}
}
@@ -71,6 +78,61 @@ uint32 MidiParser::readVLQ (byte * &data) {
return value;
}
+void MidiParser::activeNote (byte channel, byte note, bool active) {
+ if (note >= 128 || channel >= 16)
+ return;
+
+ if (active)
+ _active_notes[note] |= (1 << channel);
+ else
+ _active_notes[note] &= ~(1 << channel);
+
+ // See if there are hanging notes that we can cancel
+ NoteTimer *ptr = _hanging_notes;
+ int i;
+ for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) {
+ if (ptr->channel == channel && ptr->note == note && ptr->time_left) {
+ ptr->time_left = 0;
+ --_hanging_notes_count;
+ break;
+ }
+ }
+}
+
+void MidiParser::hangingNote (byte channel, byte note, uint32 time_left) {
+ NoteTimer *best = 0;
+ NoteTimer *ptr = _hanging_notes;
+ int i;
+
+ if (_hanging_notes_count >= ARRAYSIZE(_hanging_notes)) {
+ printf ("WARNING! MidiParser::hangingNote(): Exceeded polyphony!\n");
+ return;
+ }
+
+ for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) {
+ if (ptr->channel == channel && ptr->note == note) {
+ if (ptr->time_left && ptr->time_left < time_left)
+ return;
+ best = ptr;
+ if (ptr->time_left)
+ --_hanging_notes_count;
+ break;
+ } else if (!best && ptr->time_left == 0) {
+ best = ptr;
+ }
+ }
+
+ if (best) {
+ best->channel = channel;
+ best->note = note;
+ best->time_left = time_left;
+ ++_hanging_notes_count;
+ } else {
+ // We checked this up top. We should never get here!
+ printf ("WARNING! MidiParser::hangingNote(): Internal error!\n");
+ }
+}
+
void MidiParser::onTimer() {
uint32 end_time;
uint32 event_time;
@@ -80,6 +142,24 @@ void MidiParser::onTimer() {
end_time = _play_time + _timer_rate;
+ // Scan our hanging notes for any
+ // that should be turned off.
+ if (_hanging_notes_count) {
+ NoteTimer *ptr = &_hanging_notes[0];
+ int i;
+ for (i = ARRAYSIZE(_hanging_notes); 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;
+ --_hanging_notes_count;
+ } else {
+ ptr->time_left -= _timer_rate;
+ }
+ }
+ }
+ }
+
while (true) {
EventInfo &info = _next_event;
@@ -94,11 +174,10 @@ void MidiParser::onTimer() {
_play_pos = 0;
return;
}
- _running_status = info.event;
if (info.event == 0xF0) {
// SysEx event
- _driver->sysEx (info.ext.data, (uint16) info.ext.length);
+ _driver->sysEx (info.ext.data, (uint16) info.length);
} else if (info.event == 0xFF) {
// META event
if (info.ext.type == 0x2F) {
@@ -110,17 +189,25 @@ void MidiParser::onTimer() {
parseNextEvent (_next_event);
} else {
_play_pos = 0;
- _driver->metaEvent (info.ext.type, info.ext.data, (uint16) info.ext.length);
+ _driver->metaEvent (info.ext.type, info.ext.data, (uint16) info.length);
}
return;
} else if (info.ext.type == 0x51) {
- if (info.ext.length >= 3) {
+ 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;
}
}
- _driver->metaEvent (info.ext.type, info.ext.data, (uint16) info.ext.length);
+ _driver->metaEvent (info.ext.type, info.ext.data, (uint16) info.length);
} else {
+ if (info.command() == 0x8) {
+ activeNote (info.channel(), info.basic.param1, false);
+ } else if (info.command() == 0x9) {
+ if (info.length > 0)
+ hangingNote (info.channel(), info.basic.param1, info.length * _psec_per_tick - (end_time - event_time));
+ else
+ activeNote (info.channel(), info.basic.param1, true);
+ }
_driver->send (info.event | info.basic.param1 << 8 | info.basic.param2 << 16);
}
@@ -130,6 +217,7 @@ void MidiParser::onTimer() {
}
_play_time = end_time;
+ _play_tick = (_play_time - _last_event_time) / _psec_per_tick + _last_event_tick;
}
void MidiParser::allNotesOff() {
@@ -137,9 +225,12 @@ void MidiParser::allNotesOff() {
return;
int i;
- for (i = 0; i < 15; ++i) {
+ for (i = 0; i < 15; ++i)
_driver->send (0x007BB0 | i);
- }
+ for (i = 0; i < ARRAYSIZE(_hanging_notes); ++i)
+ _hanging_notes[i].time_left = 0;
+ _hanging_notes_count = 0;
+ memset (_active_notes, 0, 128);
}
void MidiParser::resetTracking() {
@@ -147,6 +238,7 @@ void MidiParser::resetTracking() {
_tempo = 500000;
_psec_per_tick = 500000 / _ppqn;
_play_time = 0;
+ _play_tick = 0;
_last_event_time = 0;
_last_event_tick = 0;
_running_status = 0;
@@ -169,9 +261,32 @@ bool MidiParser::setTrack (int track) {
void MidiParser::jumpToTick (uint32 tick) {
if (_active_track >= _num_tracks)
return;
- resetTracking();
- allNotesOff();
+ 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;
+ }
+ }
+ }
+
+ resetTracking();
_play_pos = _tracks[_active_track];
parseNextEvent (_next_event);
if (tick == 0)
@@ -181,10 +296,12 @@ void MidiParser::jumpToTick (uint32 tick) {
EventInfo &info = _next_event;
if (_last_event_tick + info.delta >= tick) {
_play_time += (tick - _last_event_tick) * _psec_per_tick;
+ _play_tick = tick;
break;
}
_last_event_tick += info.delta;
+ _play_tick = _last_event_tick;
_play_time += info.delta * _psec_per_tick;
_last_event_time = _play_time;
@@ -195,11 +312,11 @@ void MidiParser::jumpToTick (uint32 tick) {
parseNextEvent (_next_event);
} else {
_play_pos = 0;
- _driver->metaEvent (0x2F, info.ext.data, (uint16) info.ext.length);
+ _driver->metaEvent (0x2F, info.ext.data, (uint16) info.length);
}
break;
} else if (info.ext.type == 0x51) { // Tempo
- if (info.ext.length >= 3) {
+ 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;
}
diff --git a/sound/midiparser.h b/sound/midiparser.h
index af459457b6..8958891894 100644
--- a/sound/midiparser.h
+++ b/sound/midiparser.h
@@ -40,15 +40,27 @@ struct EventInfo {
struct {
byte type; // Used for METAs
byte * data; // Used for SysEx and METAs
- uint32 length; // Used for SysEx and METAs
} ext;
};
+ uint32 length; // Used for SysEx and METAs, and note lengths
byte channel() { return event & 0x0F; }
byte command() { return event >> 4; }
};
+struct NoteTimer {
+ byte channel;
+ byte note;
+ uint32 time_left;
+ NoteTimer() : channel(0), note(0), time_left(0) {}
+};
+
class MidiParser {
+private:
+ uint16 _active_notes[128]; // Each uint16 is a bit mask for channels that have that note on
+ NoteTimer _hanging_notes[32]; // Supports "smart" jump with notes still playing
+ byte _hanging_notes_count;
+
protected:
MidiDriver *_driver;
uint32 _timer_rate;
@@ -56,6 +68,7 @@ protected:
uint32 _tempo; // Microseconds per quarter note
uint32 _psec_per_tick; // Microseconds per tick (_tempo / _ppqn)
bool _autoLoop; // For lightweight clients that don't monitor events
+ bool _smartJump; // Support smart expiration of hanging notes when jumping
byte * _tracks[16];
byte _num_tracks;
@@ -63,9 +76,10 @@ protected:
byte * _play_pos;
uint32 _play_time;
+ uint32 _play_tick;
uint32 _last_event_time;
uint32 _last_event_tick;
- byte _running_status; // Cache of last MIDI command, used in compressed streams
+ byte _running_status;
EventInfo _next_event;
protected:
@@ -74,6 +88,9 @@ protected:
virtual void allNotesOff();
virtual void parseNextEvent (EventInfo &info) = 0;
+ void activeNote (byte channel, byte note, bool active);
+ void hangingNote (byte channel, byte note, uint32 ticks_left);
+
// Multi-byte read helpers
uint32 read4high (byte * &data) {
uint32 val = 0;
@@ -91,7 +108,8 @@ protected:
public:
enum {
mpMalformedPitchBends = 1,
- mpAutoLoop = 2
+ mpAutoLoop = 2,
+ mpSmartJump = 3
};
public:
diff --git a/sound/midiparser_smf.cpp b/sound/midiparser_smf.cpp
index f45ddf491d..d2e56d9dfc 100644
--- a/sound/midiparser_smf.cpp
+++ b/sound/midiparser_smf.cpp
@@ -97,7 +97,7 @@ void MidiParser_SMF::parseNextEvent (EventInfo &info) {
if (info.event < 0x80)
return;
- switch (info.event >> 4) {
+ switch (info.command()) {
case 0xC: case 0xD:
info.basic.param1 = *(_play_pos++);
info.basic.param2 = 0;
@@ -106,6 +106,9 @@ void MidiParser_SMF::parseNextEvent (EventInfo &info) {
case 0x8: case 0x9: case 0xA: case 0xB: case 0xE:
info.basic.param1 = *(_play_pos++);
info.basic.param2 = *(_play_pos++);
+ if (info.command() == 0x9 && info.basic.param2 == 0)
+ info.event = info.channel() | 0x80;
+ info.length = 0;
break;
case 0xF: // System Common, Meta or SysEx event
@@ -125,16 +128,16 @@ void MidiParser_SMF::parseNextEvent (EventInfo &info) {
break;
case 0x0: // SysEx
- info.ext.length = readVLQ (_play_pos);
+ info.length = readVLQ (_play_pos);
info.ext.data = _play_pos;
- _play_pos += info.ext.length;
+ _play_pos += info.length;
break;
case 0xF: // META event
info.ext.type = *(_play_pos++);
- info.ext.length = readVLQ (_play_pos);
+ info.length = readVLQ (_play_pos);
info.ext.data = _play_pos;
- _play_pos += info.ext.length;
+ _play_pos += info.length;
break;
}
}
diff --git a/sound/midiparser_xmidi.cpp b/sound/midiparser_xmidi.cpp
index 84fdf99cf8..acaccff0b2 100644
--- a/sound/midiparser_xmidi.cpp
+++ b/sound/midiparser_xmidi.cpp
@@ -32,13 +32,6 @@
//
//////////////////////////////////////////////////
-struct NoteTimer {
- byte channel;
- byte note;
- uint32 off_time;
- NoteTimer() : channel(0), note(0), off_time(0) {}
-};
-
class MidiParser_XMIDI : public MidiParser {
protected:
byte *_data;
@@ -47,7 +40,6 @@ protected:
protected:
uint32 readVLQ2 (byte * &data);
- void allNotesOff();
void resetTracking();
void parseNextEvent (EventInfo &info);
@@ -87,30 +79,6 @@ void MidiParser_XMIDI::parseNextEvent (EventInfo &info) {
info.start = _play_pos;
info.delta = readVLQ2 (_play_pos) - _inserted_delta;
- // Scan our active notes for the note
- // with the nearest off time. It might turn out
- // to be closer than the next regular event.
- uint32 note_length;
- NoteTimer *best = 0;
- NoteTimer *ptr = &_notes_cache[0];
- int i;
- for (i = ARRAYSIZE(_notes_cache); i; --i, ++ptr) {
- if (ptr->off_time && ptr->off_time >= _last_event_tick && (!best || ptr->off_time < best->off_time))
- best = ptr;
- }
-
- // See if we need to simulate a note off event.
- if (best && (best->off_time - _last_event_tick) <= info.delta) {
- _play_pos = info.start;
- info.delta = best->off_time - _last_event_tick;
- info.event = 0x80 | best->channel;
- info.basic.param1 = best->note;
- info.basic.param2 = 0;
- best->off_time = 0;
- _inserted_delta += info.delta;
- return;
- }
-
// Process the next event.
_inserted_delta = 0;
info.event = *(_play_pos++);
@@ -118,20 +86,10 @@ void MidiParser_XMIDI::parseNextEvent (EventInfo &info) {
case 0x9: // Note On
info.basic.param1 = *(_play_pos++);
info.basic.param2 = *(_play_pos++);
- note_length = readVLQ (_play_pos);
-
- // In addition to sending this back, we must
- // store a note timer so we know when to turn it off.
- ptr = &_notes_cache[0];
- for (i = ARRAYSIZE(_notes_cache); i; --i, ++ptr) {
- if (!ptr->off_time)
- break;
- }
-
- if (i) {
- ptr->channel = info.channel();
- ptr->note = info.basic.param1;
- ptr->off_time = _last_event_tick + info.delta + note_length;
+ info.length = readVLQ (_play_pos);
+ if (info.basic.param2 == 0) {
+ info.event = info.channel() | 0x80;
+ info.length = 0;
}
break;
@@ -162,17 +120,17 @@ void MidiParser_XMIDI::parseNextEvent (EventInfo &info) {
break;
case 0x0: // SysEx
- info.ext.length = readVLQ (_play_pos);
+ info.length = readVLQ (_play_pos);
info.ext.data = _play_pos;
- _play_pos += info.ext.length;
+ _play_pos += info.length;
break;
case 0xF: // META event
info.ext.type = *(_play_pos++);
- info.ext.length = readVLQ (_play_pos);
+ info.length = readVLQ (_play_pos);
info.ext.data = _play_pos;
- _play_pos += info.ext.length;
- if (info.ext.type == 0x51 && info.ext.length == 3) {
+ _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;
@@ -336,15 +294,6 @@ void MidiParser_XMIDI::unloadMusic() {
_active_track = 255;
}
-void MidiParser_XMIDI::allNotesOff() {
- MidiParser::allNotesOff();
-
- // Reset the list of active notes.
- int i;
- for (i = 0; i < ARRAYSIZE(_notes_cache); ++i)
- _notes_cache[i].off_time = 0;
-}
-
void MidiParser_XMIDI::resetTracking() {
MidiParser::resetTracking();
_inserted_delta = 0;