ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

midiparser.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "audio/midiparser.h"
00024 #include "audio/mididrv.h"
00025 #include "common/textconsole.h"
00026 #include "common/util.h"
00027 
00029 //
00030 // MidiParser implementation
00031 //
00033 
00034 MidiParser::MidiParser() :
00035 _hangingNotesCount(0),
00036 _driver(0),
00037 _timerRate(0x4A0000),
00038 _ppqn(96),
00039 _tempo(500000),
00040 _psecPerTick(5208), // 500000 / 96
00041 _autoLoop(false),
00042 _smartJump(false),
00043 _centerPitchWheelOnUnload(false),
00044 _sendSustainOffOnNotesOff(false),
00045 _numTracks(0),
00046 _activeTrack(255),
00047 _abortParse(false),
00048 _jumpingToTick(false) {
00049     memset(_activeNotes, 0, sizeof(_activeNotes));
00050     memset(_tracks, 0, sizeof(_tracks));
00051     _nextEvent.start = NULL;
00052     _nextEvent.delta = 0;
00053     _nextEvent.event = 0;
00054     _nextEvent.length = 0;
00055 }
00056 
00057 void MidiParser::property(int prop, int value) {
00058     switch (prop) {
00059     case mpAutoLoop:
00060         _autoLoop = (value != 0);
00061         break;
00062     case mpSmartJump:
00063         _smartJump = (value != 0);
00064         break;
00065     case mpCenterPitchWheelOnUnload:
00066         _centerPitchWheelOnUnload = (value != 0);
00067         break;
00068     case mpSendSustainOffOnNotesOff:
00069         _sendSustainOffOnNotesOff = (value != 0);
00070         break;
00071     }
00072 }
00073 
00074 void MidiParser::sendToDriver(uint32 b) {
00075     _driver->send(b);
00076 }
00077 
00078 void MidiParser::setTempo(uint32 tempo) {
00079     _tempo = tempo;
00080     if (_ppqn)
00081         _psecPerTick = (tempo + (_ppqn >> 2)) / _ppqn;
00082 }
00083 
00084 // This is the conventional (i.e. SMF) variable length quantity
00085 uint32 MidiParser::readVLQ(byte * &data) {
00086     byte str;
00087     uint32 value = 0;
00088     int i;
00089 
00090     for (i = 0; i < 4; ++i) {
00091         str = data[0];
00092         ++data;
00093         value = (value << 7) | (str & 0x7F);
00094         if (!(str & 0x80))
00095             break;
00096     }
00097     return value;
00098 }
00099 
00100 void MidiParser::activeNote(byte channel, byte note, bool active) {
00101     if (note >= 128 || channel >= 16)
00102         return;
00103 
00104     if (active)
00105         _activeNotes[note] |= (1 << channel);
00106     else
00107         _activeNotes[note] &= ~(1 << channel);
00108 
00109     // See if there are hanging notes that we can cancel
00110     NoteTimer *ptr = _hangingNotes;
00111     int i;
00112     for (i = ARRAYSIZE(_hangingNotes); i; --i, ++ptr) {
00113         if (ptr->channel == channel && ptr->note == note && ptr->timeLeft) {
00114             ptr->timeLeft = 0;
00115             --_hangingNotesCount;
00116             break;
00117         }
00118     }
00119 }
00120 
00121 void MidiParser::hangingNote(byte channel, byte note, uint32 timeLeft, bool recycle) {
00122     NoteTimer *best = 0;
00123     NoteTimer *ptr = _hangingNotes;
00124     int i;
00125 
00126     if (_hangingNotesCount >= ARRAYSIZE(_hangingNotes)) {
00127         warning("MidiParser::hangingNote(): Exceeded polyphony");
00128         return;
00129     }
00130 
00131     for (i = ARRAYSIZE(_hangingNotes); i; --i, ++ptr) {
00132         if (ptr->channel == channel && ptr->note == note) {
00133             if (ptr->timeLeft && ptr->timeLeft < timeLeft && recycle)
00134                 return;
00135             best = ptr;
00136             if (ptr->timeLeft) {
00137                 if (recycle)
00138                     sendToDriver(0x80 | channel, note, 0);
00139                 --_hangingNotesCount;
00140             }
00141             break;
00142         } else if (!best && ptr->timeLeft == 0) {
00143             best = ptr;
00144         }
00145     }
00146 
00147     // Occassionally we might get a zero or negative note
00148     // length, if the note should be turned on and off in
00149     // the same iteration. For now just set it to 1 and
00150     // we'll turn it off in the next cycle.
00151     if (!timeLeft || timeLeft & 0x80000000)
00152         timeLeft = 1;
00153 
00154     if (best) {
00155         best->channel = channel;
00156         best->note = note;
00157         best->timeLeft = timeLeft;
00158         ++_hangingNotesCount;
00159     } else {
00160         // We checked this up top. We should never get here!
00161         warning("MidiParser::hangingNote(): Internal error");
00162     }
00163 }
00164 
00165 void MidiParser::onTimer() {
00166     uint32 endTime;
00167     uint32 eventTime;
00168 
00169     if (!_position._playPos || !_driver)
00170         return;
00171 
00172     _abortParse = false;
00173     endTime = _position._playTime + _timerRate;
00174 
00175     // Scan our hanging notes for any
00176     // that should be turned off.
00177     if (_hangingNotesCount) {
00178         NoteTimer *ptr = &_hangingNotes[0];
00179         int i;
00180         for (i = ARRAYSIZE(_hangingNotes); i; --i, ++ptr) {
00181             if (ptr->timeLeft) {
00182                 if (ptr->timeLeft <= _timerRate) {
00183                     sendToDriver(0x80 | ptr->channel, ptr->note, 0);
00184                     ptr->timeLeft = 0;
00185                     --_hangingNotesCount;
00186                 } else {
00187                     ptr->timeLeft -= _timerRate;
00188                 }
00189             }
00190         }
00191     }
00192 
00193     while (!_abortParse) {
00194         EventInfo &info = _nextEvent;
00195 
00196         eventTime = _position._lastEventTime + info.delta * _psecPerTick;
00197         if (eventTime > endTime)
00198             break;
00199 
00200         // Process the next info.
00201         _position._lastEventTick += info.delta;
00202         if (info.event < 0x80) {
00203             warning("Bad command or running status %02X", info.event);
00204             _position._playPos = 0;
00205             return;
00206         }
00207 
00208         if (info.command() == 0x8) {
00209             activeNote(info.channel(), info.basic.param1, false);
00210         } else if (info.command() == 0x9) {
00211             if (info.length > 0)
00212                 hangingNote(info.channel(), info.basic.param1, info.length * _psecPerTick - (endTime - eventTime));
00213             else
00214                 activeNote(info.channel(), info.basic.param1, true);
00215         }
00216 
00217         // Player::metaEvent() in SCUMM will delete the parser object,
00218         // so return immediately if that might have happened.
00219         bool ret = processEvent(info);
00220         if (!ret)
00221             return;
00222 
00223         if (!_abortParse) {
00224             _position._lastEventTime = eventTime;
00225             parseNextEvent(_nextEvent);
00226         }
00227     }
00228 
00229     if (!_abortParse) {
00230         _position._playTime = endTime;
00231         _position._playTick = (_position._playTime - _position._lastEventTime) / _psecPerTick + _position._lastEventTick;
00232     }
00233 }
00234 
00235 bool MidiParser::processEvent(const EventInfo &info, bool fireEvents) {
00236     if (info.event == 0xF0) {
00237         // SysEx event
00238         // Check for trailing 0xF7 -- if present, remove it.
00239         if (fireEvents) {
00240             if (info.ext.data[info.length-1] == 0xF7)
00241                 _driver->sysEx(info.ext.data, (uint16)info.length-1);
00242             else
00243                 _driver->sysEx(info.ext.data, (uint16)info.length);
00244         }
00245     } else if (info.event == 0xFF) {
00246         // META event
00247         if (info.ext.type == 0x2F) {
00248             // End of Track must be processed by us,
00249             // as well as sending it to the output device.
00250             if (_autoLoop) {
00251                 jumpToTick(0);
00252                 parseNextEvent(_nextEvent);
00253             } else {
00254                 stopPlaying();
00255                 if (fireEvents)
00256                     _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length);
00257             }
00258             return false;
00259         } else if (info.ext.type == 0x51) {
00260             if (info.length >= 3) {
00261                 setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]);
00262             }
00263         }
00264         if (fireEvents)
00265             _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length);
00266     } else {
00267         if (fireEvents)
00268             sendToDriver(info.event, info.basic.param1, info.basic.param2);
00269     }
00270 
00271     return true;
00272 }
00273 
00274 
00275 void MidiParser::allNotesOff() {
00276     if (!_driver)
00277         return;
00278 
00279     int i, j;
00280 
00281     // Turn off all active notes
00282     for (i = 0; i < 128; ++i) {
00283         for (j = 0; j < 16; ++j) {
00284             if (_activeNotes[i] & (1 << j)) {
00285                 sendToDriver(0x80 | j, i, 0);
00286             }
00287         }
00288     }
00289 
00290     // Turn off all hanging notes
00291     for (i = 0; i < ARRAYSIZE(_hangingNotes); i++) {
00292         if (_hangingNotes[i].timeLeft) {
00293             sendToDriver(0x80 | _hangingNotes[i].channel, _hangingNotes[i].note, 0);
00294             _hangingNotes[i].timeLeft = 0;
00295         }
00296     }
00297     _hangingNotesCount = 0;
00298 
00299     // To be sure, send an "All Note Off" event (but not all MIDI devices
00300     // support this...).
00301 
00302     for (i = 0; i < 16; ++i) {
00303         sendToDriver(0xB0 | i, 0x7b, 0); // All notes off
00304         if (_sendSustainOffOnNotesOff)
00305             sendToDriver(0xB0 | i, 0x40, 0); // Also send a sustain off event (bug #3116608)
00306     }
00307 
00308     memset(_activeNotes, 0, sizeof(_activeNotes));
00309 }
00310 
00311 void MidiParser::resetTracking() {
00312     _position.clear();
00313 }
00314 
00315 bool MidiParser::setTrack(int track) {
00316     if (track < 0 || track >= _numTracks)
00317         return false;
00318     // We allow restarting the track via setTrack when
00319     // it isn't playing anymore. This allows us to reuse
00320     // a MidiParser when a track has finished and will
00321     // be restarted via setTrack by the client again.
00322     // This isn't exactly how setTrack behaved before though,
00323     // the old MidiParser code did not allow setTrack to be
00324     // used to restart a track, which was already finished.
00325     //
00326     // TODO: Check if any engine has problem with this
00327     // handling, if so we need to find a better way to handle
00328     // track restarts. (KYRA relies on this working)
00329     else if (track == _activeTrack && isPlaying())
00330         return true;
00331 
00332     if (_smartJump)
00333         hangAllActiveNotes();
00334     else
00335         allNotesOff();
00336 
00337     resetTracking();
00338     memset(_activeNotes, 0, sizeof(_activeNotes));
00339     _activeTrack = track;
00340     _position._playPos = _tracks[track];
00341     parseNextEvent(_nextEvent);
00342     return true;
00343 }
00344 
00345 void MidiParser::stopPlaying() {
00346     allNotesOff();
00347     resetTracking();
00348 }
00349 
00350 void MidiParser::hangAllActiveNotes() {
00351     // Search for note off events until we have
00352     // accounted for every active note.
00353     uint16 tempActive[128];
00354     memcpy(tempActive, _activeNotes, sizeof (tempActive));
00355 
00356     uint32 advanceTick = _position._lastEventTick;
00357     while (true) {
00358         int i;
00359         for (i = 0; i < 128; ++i)
00360             if (tempActive[i] != 0)
00361                 break;
00362         if (i == 128)
00363             break;
00364         parseNextEvent(_nextEvent);
00365         advanceTick += _nextEvent.delta;
00366         if (_nextEvent.command() == 0x8) {
00367             if (tempActive[_nextEvent.basic.param1] & (1 << _nextEvent.channel())) {
00368                 hangingNote(_nextEvent.channel(), _nextEvent.basic.param1, (advanceTick - _position._lastEventTick) * _psecPerTick, false);
00369                 tempActive[_nextEvent.basic.param1] &= ~(1 << _nextEvent.channel());
00370             }
00371         } else if (_nextEvent.event == 0xFF && _nextEvent.ext.type == 0x2F) {
00372             // warning("MidiParser::hangAllActiveNotes(): Hit End of Track with active notes left");
00373             for (i = 0; i < 128; ++i) {
00374                 for (int j = 0; j < 16; ++j) {
00375                     if (tempActive[i] & (1 << j)) {
00376                         activeNote(j, i, false);
00377                         sendToDriver(0x80 | j, i, 0);
00378                     }
00379                 }
00380             }
00381             break;
00382         }
00383     }
00384 }
00385 
00386 bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool dontSendNoteOn) {
00387     if (_activeTrack >= _numTracks)
00388         return false;
00389 
00390     assert(!_jumpingToTick); // This function is not re-entrant
00391     _jumpingToTick = true;
00392 
00393     Tracker currentPos(_position);
00394     EventInfo currentEvent(_nextEvent);
00395 
00396     resetTracking();
00397     _position._playPos = _tracks[_activeTrack];
00398     parseNextEvent(_nextEvent);
00399     if (tick > 0) {
00400         while (true) {
00401             EventInfo &info = _nextEvent;
00402             if (_position._lastEventTick + info.delta >= tick) {
00403                 _position._playTime += (tick - _position._lastEventTick) * _psecPerTick;
00404                 _position._playTick = tick;
00405                 break;
00406             }
00407 
00408             _position._lastEventTick += info.delta;
00409             _position._lastEventTime += info.delta * _psecPerTick;
00410             _position._playTick = _position._lastEventTick;
00411             _position._playTime = _position._lastEventTime;
00412 
00413             // Some special processing for the fast-forward case
00414             if (info.command() == 0x9 && dontSendNoteOn) {
00415                 // Don't send note on; doing so creates a "warble" with
00416                 // some instruments on the MT-32. Refer to patch #3117577
00417             } else if (info.event == 0xFF && info.ext.type == 0x2F) {
00418                 // End of track
00419                 // This means that we failed to find the right tick.
00420                 _position = currentPos;
00421                 _nextEvent = currentEvent;
00422                 _jumpingToTick = false;
00423                 return false;
00424             } else {
00425                 processEvent(info, fireEvents);
00426             }
00427 
00428             parseNextEvent(_nextEvent);
00429         }
00430     }
00431 
00432     if (stopNotes) {
00433         if (!_smartJump || !currentPos._playPos) {
00434             allNotesOff();
00435         } else {
00436             EventInfo targetEvent(_nextEvent);
00437             Tracker targetPosition(_position);
00438 
00439             _position = currentPos;
00440             _nextEvent = currentEvent;
00441             hangAllActiveNotes();
00442 
00443             _nextEvent = targetEvent;
00444             _position = targetPosition;
00445         }
00446     }
00447 
00448     _abortParse = true;
00449     _jumpingToTick = false;
00450     return true;
00451 }
00452 
00453 void MidiParser::unloadMusic() {
00454     resetTracking();
00455     allNotesOff();
00456     _numTracks = 0;
00457     _activeTrack = 255;
00458     _abortParse = true;
00459 
00460     if (_centerPitchWheelOnUnload) {
00461         // Center the pitch wheels in preparation for the next piece of
00462         // music. It's not safe to do this from within allNotesOff(),
00463         // and might not even be safe here, so we only do it if the
00464         // client has explicitly asked for it.
00465 
00466         if (_driver) {
00467             for (int i = 0; i < 16; ++i) {
00468                 sendToDriver(0xE0 | i, 0, 0x40);
00469             }
00470         }
00471     }
00472 }


Generated on Sat Mar 16 2019 05:01:46 for ResidualVM by doxygen 1.7.1
curved edge   curved edge