/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
//! \brief Declarations related to the MidiParser class
#ifndef SOUND_MIDIPARSER_H
#define SOUND_MIDIPARSER_H
#include "common/scummsys.h"
#include "common/endian.h"
class MidiParser;
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 ©) :
_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 event-based music streams.
/*! The MidiParser provides a framework in which to load,
* parse and traverse event-based music data. Note the
* avoidance of the phrase "MIDI data." Despite its name,
* MidiParser derivatives can be used to manage a wide
* variety of event-based music formats. It is, however,
* based on the premise that the format in question can
* be played in the form of specification MIDI events.
*
* In order to use MidiParser to parse your music format,
* follow these steps:
*
* STEP 1: Write a MidiParser derivative.
* The MidiParser base class provides functionality
* considered common to the task of parsing event-based
* music. In order to parse a particular format, create
* a derived class that implements, at minimum, the
* following format-specific methods:
* - loadMusic
* - parseNextEvent
*
* In addition to the above functions, the derived class
* may also override the default MidiParser behavior for
* the following methods:
* - resetTracking
* - allNotesOff
* - unloadMusic
* - property
* - getTick
*
* Please see the documentation for these individual
* functions for more information on their use.
*
* The naming convention for classes derived from
* MidiParser is MidiParser_XXX, where "XXX" is some
* short designator for the format the class will
* support. For instance, the MidiParser derivative
* for parsing the Standard MIDI File format is
* MidiParser_SMF.
*
* STEP 2: Create an object of your derived class.
* Each MidiParser object can parse at most one (1) song
* at a time. However, a MidiParser object can be reused
* to play another song once it is no longer needed to
* play whatever it was playing. In other words, MidiParser
* objects do not have to be destroyed and recreated from
* one song to the next.
*
* STEP 3: Specify a MidiDriver to send events to.
* MidiParser works by sending MIDI and meta events to a
* MidiDriver. In the simplest configuration, you can plug
* a single MidiParser directly into the output MidiDriver
* being used. However, you can only plug in one at a time;
* otherwise channel conflicts will occur. Furthermore,
* meta events that may be needed to interactively control
* music flow cannot be handled because they are being
* sent directly to the output device.
*
* If you need more control over the MidiParser while it's
* playing, you can create your own "pseudo-MidiDriver" and
* place it in between your MidiParser and the output
* MidiDriver. The MidiParser will send events to your
* pseudo-MidiDriver, which in turn must send them to the
* output MidiDriver (or do whatever special handling is
* required).
*
* To specify the MidiDriver to send music output to,
* use the MidiParser::setMidiDriver method.
*
* STEP 4: Specify the onTimer call rate.
* MidiParser bases the timing of its parsing on an external
* clock. Every time MidiParser::onTimer is called, a bit
* more music is parsed. You must specify how many
* microseconds will occur between each call to onTimer,
* in order to ensure an accurate music tempo.
*
* To set the onTimer call rate, in microseconds,
* use the MidiParser::setTimerRate method. The onTimer
* call rate will typically match the timer rate for
* the output MidiDriver used. This rate can be obtained
* by calling MidiDriver::getBaseTempo.
*
* STEP 5: Load the music.
* MidiParser requires that the music data already be loaded
* into memory. The client code is responsible for memory
* management on this block of memory. That means that the
* client code must ensure that the data remain in memory
* while the MidiParser is using it, and properly freed
* after it is no longer needed. Some MidiParser variants may
* require internal buffers as well; memory management for those
* buffers is the responsibility of the MidiParser object.
*
* To load the music into the MidiParser, use the
* MidiParser::loadMusic method, specifying a memory pointer
* to the music data and the size of the data. (NOTE: Some
* MidiParser variants don't require a size, and 0 is fine.
* However, when writing client code to use MidiParser, it is
* best to assume that a valid size will be required.
*
* Convention requires that each implementation of
* MidiParser::loadMusic automatically set up default tempo
* and current track. This effectively means that the
* MidiParser will start playing as soon as timer events
* start coming in.
*
* STEP 6: Activate a timer source for the MidiParser.
* The easiest timer source to use is the timer of the
* output MidiDriver. You can attach the MidiDriver's
* timer output directly to a MidiParser by calling
* MidiDriver::setTimerCallback. In this case, the timer_proc
* will be the static method MidiParser::timerCallback,
* and timer_param will be a pointer to your MidiParser object.
*
* This configuration only allows one MidiParser to be driven
* by the MidiDriver at a time. To drive more MidiDrivers, you
* will need to create a "pseudo-MidiDriver" as described earlier,
* In such a configuration, the pseudo-MidiDriver should be set
* as the timer recipient in MidiDriver::setTimerCallback, and
* could then call MidiParser::onTimer for each MidiParser object.
*
* STEP 7: Music shall begin to play!
* Congratulations! At this point everything should be hooked up
* and the MidiParser should generate music. Note that there is
* no way to "stop" the MidiParser. You can "pause" the MidiParser
* simply by not sending timer events to it, or you can call
* MidiParser::unloadMusic to permanently stop the music. (This
* method resets everything and detaches the MidiParser from the
* memory block containing the music data.)
*/
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
bool _centerPitchWheelOnUnload; //!< Center the pitch wheels when unloading a song
byte *_tracks[120]; //!< Multi-track MIDI formats are supported, up to 120 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 = READ_BE_UINT32(data);
data += 4;
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 = READ_LE_UINT16(data);
data += 2;
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,
mpCenterPitchWheelOnUnload = 4
};
public:
typedef void (*XMidiCallbackProc)(byte eventData, void *refCon);
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 isPlaying() const { return (_position._play_pos != 0); }
void stopPlaying();
bool setTrack(int track);
bool jumpToTick(uint32 tick, bool fireEvents = false);
uint32 getPPQN() { return _ppqn; }
virtual uint32 getTick() { return _position._play_tick; }
static void defaultXMidiCallback(byte eventData, void *refCon);
static MidiParser *createParser_SMF();
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
};
#endif