aboutsummaryrefslogtreecommitdiff
path: root/sound/midiparser_xmidi.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sound/midiparser_xmidi.cpp')
-rw-r--r--sound/midiparser_xmidi.cpp319
1 files changed, 93 insertions, 226 deletions
diff --git a/sound/midiparser_xmidi.cpp b/sound/midiparser_xmidi.cpp
index 91dbc7b619..d35bf93c02 100644
--- a/sound/midiparser_xmidi.cpp
+++ b/sound/midiparser_xmidi.cpp
@@ -35,64 +35,28 @@
struct NoteTimer {
byte channel;
byte note;
- uint32 time_left;
+ uint32 off_time;
};
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];
+ uint32 _inserted_delta; // Track simulated deltas for note-off events
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);
-
- void playToTime (uint32 psec, bool transmit);
+ void parseNextEvent (EventInfo &info);
public:
~MidiParser_XMIDI() { }
bool loadMusic (byte *data, uint32 size);
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
@@ -102,22 +66,6 @@ public:
//
//////////////////////////////////////////////////
-// 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;
@@ -131,154 +79,104 @@ uint32 MidiParser_XMIDI::readVLQ2 (byte * &pos) {
return value;
}
-void MidiParser_XMIDI::onTimer() {
- if (!_play_pos || !_driver)
- return;
- playToTime (_play_time + _timer_rate, true);
-}
+void MidiParser_XMIDI::parseNextEvent (EventInfo &info) {
+ info.start = _play_pos;
+ info.delta = readVLQ2 (_play_pos) - _inserted_delta;
-void MidiParser_XMIDI::playToTime (uint32 psec, bool transmit) {
- uint32 delta;
- uint32 end_time;
- uint32 event_time;
- byte *pos;
- byte *oldpos;
- byte event;
- uint32 length;
- int i;
- NoteTimer *ptr;
- byte note;
- byte vel;
+ // 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;
-
- end_time = psec;
- pos = _play_pos;
-
- // Send any necessary note off events.
- ptr = &_notes_cache[0];
+ NoteTimer *best = 0;
+ NoteTimer *ptr = &_notes_cache[0];
+ int i;
for (i = ARRAYSIZE(_notes_cache); i; --i, ++ptr) {
- if (ptr->time_left) {
- if (ptr->time_left <= _timer_rate) {
- if (transmit)
- _driver->send (0x80 | ptr->channel | (ptr->note << 8));
- ptr->time_left = 0;
- } else {
- ptr->time_left -= _timer_rate;
- }
- }
+ if (ptr->off_time && ptr->off_time >= _last_event_tick && (!best || ptr->off_time < best->off_time))
+ best = ptr;
}
- while (true) {
- oldpos = pos;
- delta = readVLQ2 (pos);
- event_time = _last_event_time + delta * MICROSECONDS_PER_TICK;
- if (event_time > end_time) {
- pos = oldpos;
- break;
+ // 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.param1 = best->note;
+ info.param2 = 0;
+ best->off_time = 0;
+ _inserted_delta += info.delta;
+ return;
+ }
+
+ // Process the next event.
+ _inserted_delta = 0;
+ info.event = *(_play_pos++);
+ switch (info.event >> 4) {
+ case 0x9: // Note On
+ info.param1 = *(_play_pos++);
+ info.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;
}
- // 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;
- if (transmit)
- _driver->send (event | (note << 8) | (vel << 16));
- break;
- }
- }
+ if (i) {
+ ptr->channel = info.channel();
+ ptr->note = info.param1;
+ ptr->off_time = _last_event_tick + info.delta + note_length;
+ }
+ break;
+
+ case 0xC: case 0xD:
+ info.param1 = *(_play_pos++);
+ info.param2 = 0;
+ break;
+
+ case 0x8: case 0xA: case 0xB: case 0xE:
+ info.param1 = *(_play_pos++);
+ info.param2 = *(_play_pos++);
+ break;
+
+ case 0xF: // Meta or SysEx event
+ switch (info.event & 0x0F) {
+ case 0x2: // Song Position Pointer
+ info.param1 = *(_play_pos++);
+ info.param2 = *(_play_pos++);
break;
- case 0xC: // Program Change
- case 0xD: // Channel Aftertouch
- if (transmit)
- _driver->send (event | (pos[0] << 8));
- ++pos;
+ case 0x3: // Song Select
+ info.param1 = *(_play_pos++);
+ info.param2 = 0;
break;
- case 0x8: // Note Off (do these ever occur in XMIDI??)
- case 0xA: // Key Aftertouch
- case 0xB: // Control Change
- case 0xE: // Pitch Bender Change
- if (transmit)
- _driver->send (event | (pos[0] << 8) | (pos[1] << 16));
- pos += 2;
+ case 0x6: case 0x8: case 0xA: case 0xB: case 0xC: case 0xE:
+ info.param1 = info.param2 = 0;
break;
- case 0xF: // Meta or SysEx event
- switch (event & 0x0F) {
- case 0x2: // Song Position Pointer
- if (transmit)
- _driver->send (event | (pos[0] << 8) | (pos[1] << 16));
- pos += 2;
- break;
-
- case 0x3: // Song Select
- if (transmit)
- _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
- if (transmit)
- _driver->send (event);
- break;
-
- case 0x0: // SysEx
- length = readVLQ (pos);
- if (transmit)
- _driver->sysEx (pos, (uint16)(length - 1));
- pos += length;
- break;
-
- case 0xF: // META event
- event = *pos++;
- length = readVLQ (pos);
-
- if (event == 0x2F) {
- // End of song must be processed by us,
- // as well as sending it to the output device.
- ptr = &_notes_cache[0];
- for (i = ARRAYSIZE(_notes_cache); i; --i, ++ptr) {
- if (ptr->time_left) {
- if (transmit)
- _driver->send (0x80 | ptr->channel | (ptr->note << 8));
- ptr->time_left = 0;
- }
- }
- _play_pos = 0;
- if (transmit)
- _driver->metaEvent (event, pos, (uint16) length);
- return;
- }
+ case 0x0: // SysEx
+ info.length = readVLQ (_play_pos);
+ info.data = _play_pos;
+ _play_pos += info.length;
+ break;
- if (transmit)
- _driver->metaEvent (event, pos, (uint16) length);
- pos += length;
- break;
+ case 0xF: // META event
+ info.type = *(_play_pos++);
+ info.length = readVLQ (_play_pos);
+ info.data = _play_pos;
+ _play_pos += info.length;
+ if (info.type == 0x51 && info.length == 3) {
+ // Tempo event. We want to make these constant 500,000.
+ info.data[0] = 0x07;
+ info.data[1] = 0xA1;
+ info.data[2] = 0x20;
}
+ break;
}
-
- _last_event_time = event_time;
}
-
- _play_time = end_time;
- _play_pos = pos;
}
bool MidiParser_XMIDI::loadMusic (byte *data, uint32 size) {
@@ -336,7 +234,7 @@ bool MidiParser_XMIDI::loadMusic (byte *data, uint32 size) {
return false;
}
- _num_tracks = read2low (pos);
+ _num_tracks = (byte) read2low (pos);
if (chunk_len > 2) {
printf ("Chunk length %d is greater than 2\n", (int) chunk_len);
@@ -415,7 +313,9 @@ bool MidiParser_XMIDI::loadMusic (byte *data, uint32 size) {
// will persist beyond this call, i.e. we do NOT
// copy the data to our own buffer. Take warning....
_data = data;
- _active_track = 255;
+ _ppqn = 60;
+ resetTracking();
+ _inserted_delta = 0;
setTrack (0);
return true;
}
@@ -424,45 +324,12 @@ bool MidiParser_XMIDI::loadMusic (byte *data, uint32 size) {
}
void MidiParser_XMIDI::unloadMusic() {
- int i;
- NoteTimer *ptr;
-
- _play_pos = NULL;
- _data = NULL;
+ resetTracking();
+ allNotesOff();
+ _inserted_delta = 0;
+ _data = 0;
_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;
- }
- }
-}
-
-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) {
- if (_active_track >= _num_tracks)
- return;
- _play_pos = _tracks[_active_track];
- _play_time = 0;
- _last_event_time = 0;
- if (tick > 0) {
- printf ("jumpToTick (%ld) not completely implemented!\n", (long) tick);
- playToTime (tick * MICROSECONDS_PER_TICK - 1, false);
- }
+ _active_track = 255;
}
MidiParser *MidiParser::createParser_XMIDI() { return new MidiParser_XMIDI; }