From 1918dc555f122cb21e6e158b1377e19c5e84ab13 Mon Sep 17 00:00:00 2001 From: Jamieson Christian Date: Thu, 22 May 2003 15:34:30 +0000 Subject: Added "smart-jump" capability to MidiParser. svn-id: r7831 --- sound/midiparser.cpp | 143 ++++++++++++++++++++++++++++++++++++++++----- sound/midiparser.h | 24 +++++++- sound/midiparser_smf.cpp | 13 +++-- sound/midiparser_xmidi.cpp | 69 +++------------------- 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; -- cgit v1.2.3