scummvm/sound/midiparser.h
Jamieson Christian 4ac4d76718 Fix for Bug [766426]: V5 Games: Adlib SFX not looped
Modified Smart Jump logic to deal with active notes
whose Note On and Note Off events BOTH occur OUTSIDE
the range of the jump. While this is not a thorough
way to deal with Note On events that occur outside
jump points, it at least deals with the issue of
long, unchanging Adlib SFX used by some earlier
SCUMM games.

svn-id: r8892
2003-07-10 04:34:44 +00:00

258 lines
9.7 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2001-2003 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Header$
*
*/
//! \brief Declarations related to the MidiParser class
#ifndef INCLUDED_MIDIPARSER
#define INCLUDED_MIDIPARSER
class MidiParser;
#include "common/scummsys.h"
class MidiDriver;
//////////////////////////////////////////////////
//
// Support entities
//
//////////////////////////////////////////////////
//! Maintains time and position state within a MIDI stream.
/*! A single Tracker struct is used by MidiParser to keep track
* of its current position in the MIDI stream. The Tracker
* struct, however, allows alternative locations to be cached.
* See MidiParser::jumpToTick() for an example of tracking
* multiple locations within a MIDI stream. NOTE: It is
* important to also maintain pre-parsed EventInfo data for
* each Tracker location.
*/
struct Tracker {
byte * _play_pos; //!< A pointer to the next event to be parsed
uint32 _play_time; //!< Current time in microseconds; may be in between event times
uint32 _play_tick; //!< Current MIDI tick; may be in between event ticks
uint32 _last_event_time; //!< The time, in microseconds, of the last event that was parsed
uint32 _last_event_tick; //!< The tick at which the last parsed event occurs
byte _running_status; //!< Cached MIDI command, for MIDI streams that rely on implied event codes
Tracker() { clear(); }
//! Copy constructor for each duplication of Tracker information.
Tracker (const Tracker &copy) :
_play_pos (copy._play_pos),
_play_time (copy._play_time),
_play_tick (copy._play_tick),
_last_event_time (copy._last_event_time),
_last_event_tick (copy._last_event_tick),
_running_status (copy._running_status)
{ }
//! Clears all data; used by the constructor for initialization.
void clear() {
_play_pos = 0;
_play_time = 0;
_play_tick = 0;
_last_event_time = 0;
_last_event_tick = 0;
_running_status = 0;
}
};
//! Provides comprehensive information on the next event in the MIDI stream.
/*! An EventInfo struct is instantiated by format-specific implementations
* of MidiParser::parseNextEvent() each time another event is needed.
*/
struct EventInfo {
byte * start; //!< Position in the MIDI stream where the event starts.
//!< For delta-based MIDI streams (e.g. SMF and XMIDI), this points to the delta.
uint32 delta; //!< The number of ticks after the previous event that this event should occur.
byte event; //!< Upper 4 bits are the command code, lower 4 bits are the MIDI channel.
//!< For META, event == 0xFF. For SysEx, event == 0xF0.
union {
struct {
byte param1; //!< The first parameter in a simple MIDI message.
byte param2; //!< The second parameter in a simple MIDI message.
} basic;
struct {
byte type; //!< For META events, this indicates the META type.
byte * data; //!< For META and SysEx events, this points to the start of the data.
} ext;
};
uint32 length; //!< For META and SysEx blocks, this indicates the length of the data.
//!< For Note On events, a non-zero value indicates that no Note Off event
//!< will occur, and the MidiParser will have to generate one itself.
//!< For all other events, this value should always be zero.
byte channel() { return event & 0x0F; } //!< Separates the MIDI channel from the event.
byte command() { return event >> 4; } //!< Separates the command code from the event.
};
//! Provides expiration tracking for hanging notes.
/*! Hanging notes are used when a MIDI format does not include explicit Note Off
* events, or when "Smart Jump" is enabled so that active notes are intelligently
* expired when a jump occurs. The NoteTimer struct keeps track of how much
* longer a note should remain active before being turned off.
*/
struct NoteTimer {
byte channel; //!< The MIDI channel on which the note was played
byte note; //!< The note number for the active note
uint32 time_left; //!< The time, in microseconds, remaining before the note should be turned off
NoteTimer() : channel(0), note(0), time_left(0) {}
};
//////////////////////////////////////////////////
//
// MidiParser declaration
//
//////////////////////////////////////////////////
//! A framework and common functionality for parsing MIDI streams.
/*! The MidiParser provides a framework in which to load
* parse and traverse MIDI streams. Loading and parsing
* events for specific formats requires a class to be
* derived from MidiParser.
*
* The MidiParser must be provided with a MidiDriver interface,
* which it uses to transmit events. However, it does NOT
* automatically hook into the MidiDriver's timer callback
* or set up its timer rate from the MidiDriver. The client
* using the MidiParser must set the timer rate and provide
* a means of firing the MidiParser's onTimer() method at
* appropriate intervals. The onTimer() event may be called
* by the client or by manually hooking and unhooking the
* MidiParser to the MidiDriver's timer callback.
*/
class MidiParser {
private:
uint16 _active_notes[128]; //!< Each uint16 is a bit mask for channels that have that note on.
NoteTimer _hanging_notes[32]; //!< Maintains expiration info for up to 32 notes.
//!< Used for "Smart Jump" and MIDI formats that do not include explicit Note Off events.
byte _hanging_notes_count; //!< Count of hanging notes, used to optimize expiration.
protected:
MidiDriver *_driver; //!< The device to which all events will be transmitted.
uint32 _timer_rate; //!< The time in microseconds between onTimer() calls. Obtained from the MidiDriver.
uint32 _ppqn; //!< Pulses Per Quarter Note. (We refer to "pulses" as "ticks".)
uint32 _tempo; //!< Microseconds per quarter note.
uint32 _psec_per_tick; //!< Microseconds per tick (_tempo / _ppqn).
bool _autoLoop; //!< For lightweight clients that don't provide their own flow control.
bool _smartJump; //!< Support smart expiration of hanging notes when jumping
byte * _tracks[32]; //!< Multi-track MIDI formats are supported, up to 32 tracks.
byte _num_tracks; //!< Count of total tracks for multi-track MIDI formats. 1 for single-track formats.
byte _active_track; //!< Keeps track of the currently active track, in multi-track formats.
Tracker _position; //!< The current time/position in the active track.
EventInfo _next_event; //!< The next event to transmit. Events are preparsed
//!< so each event is parsed only once; this permits
//!< simulated events in certain formats.
bool _abort_parse; //!< If a jump or other operation interrupts parsing, flag to abort.
protected:
static uint32 readVLQ (byte * &data);
virtual void resetTracking();
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, bool recycle = true);
void hangAllActiveNotes();
//! Platform independent BE uint32 read-and-advance.
/*! This helper function reads Big Endian 32-bit numbers
* from a memory pointer, at the same time advancing
* the pointer.
*/
uint32 read4high (byte * &data) {
uint32 val = 0;
int i;
for (i = 0; i < 4; ++i) val = (val << 8) | *data++;
return val;
}
//! Platform independent LE uint16 read-and-advance.
/*! This helper function reads Little Endian 16-bit numbers
* from a memory pointer, at the same time advancing
* the pointer.
*/
uint16 read2low (byte * &data) {
uint16 val = 0;
int i;
for (i = 0; i < 2; ++i) val |= (*data++) << (i * 8);
return val;
}
public:
//! Configuration options for MidiParser
/*! The following options can be set to modify MidiParser's
* behavior.
*
* \b mpMalformedPitchBends - Events containing a pitch bend
* command should be treated as single-byte padding before the
* real event. This allows the MidiParser to work with some
* malformed SMF files from Simon 1/2.
*
* \b mpAutoLoop - Sets auto-looping, which can be used by
* lightweight clients that don't provide their own flow control.
*
* \b mpSmartJump - Sets smart jumping, which intelligently
* expires notes that are active when a jump is made, rather
* than just cutting them off.
*/
enum {
mpMalformedPitchBends = 1,
mpAutoLoop = 2,
mpSmartJump = 3
};
public:
MidiParser();
virtual ~MidiParser() { allNotesOff(); }
virtual bool loadMusic (byte *data, uint32 size) = 0;
virtual void unloadMusic();
virtual void property (int prop, int value);
void setMidiDriver (MidiDriver *driver) { _driver = driver; }
void setTimerRate (uint32 rate) { _timer_rate = rate; }
void setTempo (uint32 tempo);
void onTimer();
bool setTrack (int track);
bool jumpToTick (uint32 tick, bool fireEvents = false);
uint32 getTick() { return _position._play_tick; }
static MidiParser *createParser_SMF();
static MidiParser *createParser_XMIDI();
static void timerCallback (void *data) { ((MidiParser *) data)->onTimer(); }
};
#endif