void MidiParser::hangAllActiveNotes() { // Search for note off events until we have // accounted for every active note. uint16 temp_active[128]; memcpy(temp_active, _active_notes, sizeof (temp_active)); uint32 advance_tick = _position._last_event_tick; while (true) { int i; for (i = 0; i < 128; ++i) if (temp_active[i] != 0) break; if (i == 128) break; parseNextEvent(_next_event); advance_tick += _next_event.delta; if (_next_event.command() == 0x8) { if (temp_active[_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, false); temp_active[_next_event.basic.param1] &= ~(1 << _next_event.channel()); } } else if (_next_event.event == 0xFF && _next_event.ext.type == 0x2F) { // warning("MidiParser::hangAllActiveNotes(): Hit End of Track with active notes left"); for (i = 0; i < 128; ++i) { for (int j = 0; j < 16; ++j) { if (temp_active[i] & (1 << j)) { activeNote(j, i, false); sendToDriver(0x80 | j, i, 0); } } } break; } } }
bool MidiParser::setTrack(int track) { if (track < 0 || track >= _num_tracks) return false; // We allow restarting the track via setTrack when // it isn't playing anymore. This allows us to reuse // a MidiParser when a track has finished and will // be restarted via setTrack by the client again. // This isn't exactly how setTrack behaved before though, // the old MidiParser code did not allow setTrack to be // used to restart a track, which was already finished. // // TODO: Check if any engine has problem with this // handling, if so we need to find a better way to handle // track restarts. (KYRA relies on this working) else if (track == _active_track && isPlaying()) return true; if (_smartJump) hangAllActiveNotes(); else allNotesOff(); resetTracking(); memset(_active_notes, 0, sizeof(_active_notes)); _active_track = track; _position._play_pos = _tracks[track]; parseNextEvent(_next_event); return true; }
bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool dontSendNoteOn) { if (_active_track >= _num_tracks) return false; Tracker currentPos(_position); EventInfo currentEvent(_next_event); 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; } _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 _position = currentPos; _next_event = currentEvent; return false; } else { if (info.ext.type == 0x51 && info.length >= 3) // Tempo setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); if (fireEvents) _driver->metaEvent(info.ext.type, info.ext.data, (uint16) info.length); } } else if (fireEvents) { if (info.event == 0xF0) { if (info.ext.data[info.length-1] == 0xF7) _driver->sysEx(info.ext.data, (uint16)info.length-1); else _driver->sysEx(info.ext.data, (uint16)info.length); } else { // The note on sending code is used by the SCUMM engine. Other engine using this code // (such as SCI) have issues with this, as all the notes sent can be heard when a song // is fast-forwarded. Thus, if the engine requests it, don't send note on events. if (info.command() == 0x9 && dontSendNoteOn) { // Don't send note on; doing so creates a "warble" with some instruments on the MT-32. // Refer to patch #3117577 } else { sendToDriver(info.event, info.basic.param1, info.basic.param2); } } } parseNextEvent(_next_event); } } if (stopNotes) { if (!_smartJump || !currentPos._play_pos) { 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; }
void MidiParser::onTimer() { uint32 end_time; uint32 event_time; if (!_position._play_pos || !_driver) return; _abort_parse = false; end_time = _position._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) { sendToDriver(0x80 | ptr->channel, ptr->note, 0); ptr->time_left = 0; --_hanging_notes_count; } else { ptr->time_left -= _timer_rate; } } } } while (!_abort_parse) { EventInfo &info = _next_event; event_time = _position._last_event_time + info.delta * _psec_per_tick; if (event_time > end_time) break; // Process the next info. _position._last_event_tick += info.delta; if (info.event < 0x80) { warning("Bad command or running status %02X", info.event); _position._play_pos = 0; return; } if (info.event == 0xF0) { // SysEx event // Check for trailing 0xF7 -- if present, remove it. if (info.ext.data[info.length-1] == 0xF7) _driver->sysEx(info.ext.data, (uint16)info.length-1); else _driver->sysEx(info.ext.data, (uint16)info.length); } else if (info.event == 0xFF) { // META event if (info.ext.type == 0x2F) { // End of Track must be processed by us, // as well as sending it to the output device. if (_autoLoop) { jumpToTick(0); parseNextEvent(_next_event); } else { stopPlaying(); _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); } return; } else if (info.ext.type == 0x51) { if (info.length >= 3) { 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); } 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); } sendToDriver(info.event, info.basic.param1, info.basic.param2); } if (!_abort_parse) { _position._last_event_time = event_time; parseNextEvent(_next_event); } } 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_S1D::chainEvent(EventInfo &info) { // When we chain an event, we add up the old delta. uint32 delta = info.delta; parseNextEvent(info); info.delta += delta; }
bool MidiParser::jumpToTick(uint32 tick, bool fireEvents) { if (_active_track >= _num_tracks) return false; Tracker currentPos(_position); EventInfo currentEvent(_next_event); 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; } _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 _position = currentPos; _next_event = currentEvent; return false; } else { if (info.ext.type == 0x51 && info.length >= 3) // Tempo setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); if (fireEvents) _driver->metaEvent(info.ext.type, info.ext.data, (uint16) info.length); } } else if (fireEvents) { if (info.event == 0xF0) { if (info.ext.data[info.length-1] == 0xF7) _driver->sysEx(info.ext.data, (uint16)info.length-1); else _driver->sysEx(info.ext.data, (uint16)info.length); } else _driver->send(info.event, info.basic.param1, info.basic.param2); } parseNextEvent(_next_event); } } if (!_smartJump || !currentPos._play_pos) { 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; }