scummvm/engines/scumm/imuse/imuse_player.cpp

1095 lines
23 KiB
C++
Raw Normal View History

/* 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.
*/
#include "common/util.h"
#include "engines/engine.h"
#include "scumm/imuse/imuse_internal.h"
#include "scumm/saveload.h"
#include "scumm/scumm.h"
#include "audio/midiparser.h"
namespace Scumm {
////////////////////////////////////////
//
// Miscellaneous
//
////////////////////////////////////////
#define IMUSE_SYSEX_ID 0x7D
#define YM2612_SYSEX_ID 0x7C
#define ROLAND_SYSEX_ID 0x41
#define PERCUSSION_CHANNEL 9
extern MidiParser *MidiParser_createRO();
uint16 Player::_active_notes[128];
//////////////////////////////////////////////////
//
// IMuse Player implementation
//
//////////////////////////////////////////////////
Player::Player() :
_midi(NULL),
_parser(NULL),
_parts(NULL),
2003-11-08 21:59:32 +00:00
_active(false),
_scanning(false),
_id(0),
_priority(0),
_volume(0),
_pan(0),
_transpose(0),
_detune(0),
_note_offset(0),
2003-11-08 21:59:32 +00:00
_vol_eff(0),
_track_index(0),
_loop_to_beat(0),
_loop_from_beat(0),
_loop_counter(0),
_loop_to_tick(0),
_loop_from_tick(0),
_speed(128),
_isMT32(false),
_isMIDI(false),
2003-11-08 21:59:32 +00:00
_se(0),
_vol_chan(0){
}
Player::~Player() {
if (_parser) {
delete _parser;
_parser = 0;
}
}
bool Player::startSound(int sound, MidiDriver *midi) {
byte *ptr;
int i;
// Not sure what the old code was doing,
// but we'll go ahead and do a similar check.
ptr = _se->findStartOfSound(sound);
if (!ptr) {
error("Player::startSound(): Couldn't find start of sound %d", sound);
2003-12-13 21:06:47 +00:00
return false;
}
2003-11-08 21:59:32 +00:00
_isMT32 = _se->isMT32(sound);
_isMIDI = _se->isMIDI(sound);
_parts = NULL;
_active = true;
_midi = midi;
_id = sound;
loadStartParameters(sound);
for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i)
_parameterFaders[i].init();
hook_clear();
if (start_seq_sound(sound) != 0) {
_active = false;
_midi = NULL;
return false;
}
debugC(DEBUG_IMUSE, "Starting music %d", sound);
return true;
}
2003-10-03 00:59:27 +00:00
int Player::getMusicTimer() const {
return _parser ? (_parser->getTick() * 2 / _parser->getPPQN()) : 0;
}
bool Player::isFadingOut() const {
int i;
for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i) {
if (_parameterFaders[i].param == ParameterFader::pfVolume &&
2003-11-08 21:59:32 +00:00
_parameterFaders[i].end == 0) {
return true;
}
}
return false;
}
void Player::clear() {
if (!_active)
return;
debugC(DEBUG_IMUSE, "Stopping music %d", _id);
if (_parser) {
_parser->unloadMusic();
delete _parser;
_parser = 0;
}
uninit_parts();
_se->ImFireAllTriggers(_id);
_active = false;
_midi = NULL;
_id = 0;
_note_offset = 0;
}
void Player::hook_clear() {
memset(&_hook, 0, sizeof(_hook));
}
int Player::start_seq_sound(int sound, bool reset_vars) {
byte *ptr;
if (reset_vars) {
_loop_to_beat = 1;
_loop_from_beat = 1;
_track_index = 0;
_loop_counter = 0;
_loop_to_tick = 0;
_loop_from_tick = 0;
}
ptr = _se->findStartOfSound(sound);
if (ptr == NULL)
return -1;
delete _parser;
if (!memcmp(ptr, "RO", 2)) {
// Old style 'RO' resource
_parser = MidiParser_createRO();
} else if (!memcmp(ptr, "FORM", 4)) {
// Humongous Games XMIDI resource
_parser = MidiParser::createParser_XMIDI();
} else {
// SCUMM SMF resource
_parser = MidiParser::createParser_SMF();
}
_parser->setMidiDriver(this);
_parser->property(MidiParser::mpSmartJump, 1);
_parser->loadMusic(ptr, 0);
_parser->setTrack(_track_index);
ptr = _se->findStartOfSound(sound, IMuseInternal::kMDhd);
setSpeed(reset_vars ? (ptr ? (READ_BE_UINT32(&ptr[4]) && ptr[15] ? ptr[15] : 128) : 128) : _speed);
return 0;
}
void Player::loadStartParameters(int sound) {
_priority = 0x80;
_volume = 0x7F;
_vol_chan = 0xFFFF;
_vol_eff = (_se->get_channel_volume(0xFFFF) << 7) >> 7;
_pan = 0;
_transpose = 0;
_detune = 0;
byte *ptr = _se->findStartOfSound(sound, IMuseInternal::kMDhd);
uint32 size = 0;
if (ptr) {
ptr += 4;
size = READ_BE_UINT32(ptr);
ptr += 4;
// MDhd chunks don't get used in MI1 and contain only zeroes.
// We check for volume, priority and speed settings of zero here.
if (size && (ptr[2] | ptr[3] | ptr[7])) {
_priority = ptr[2];
_volume = ptr[3];
_pan = ptr[4];
_transpose = ptr[5];
_detune = ptr[6];
setSpeed(ptr[7]);
}
}
}
void Player::uninit_parts() {
assert(!_parts || _parts->_player == this);
while (_parts)
_parts->uninit();
// In case another player is waiting to allocate parts
if (_midi)
_se->reallocateMidiChannels(_midi);
}
void Player::setSpeed(byte speed) {
_speed = speed;
if (_parser)
_parser->setTimerRate(((_midi->getBaseTempo() * speed) >> 7) * _se->_tempoFactor / 100);
}
void Player::send(uint32 b) {
byte cmd = (byte)(b & 0xF0);
byte chan = (byte)(b & 0x0F);
byte param1 = (byte)((b >> 8) & 0xFF);
byte param2 = (byte)((b >> 16) & 0xFF);
Part *part;
switch (cmd >> 4) {
case 0x8: // Key Off
if (!_scanning) {
if ((part = getPart(chan)) != 0)
part->noteOff(param1);
} else {
_active_notes[param1] &= ~(1 << chan);
}
break;
case 0x9: // Key On
param1 += _note_offset;
if (!_scanning) {
if (_isMT32 && !_se->isNativeMT32())
param2 = (((param2 * 3) >> 2) + 32) & 0x7F;
if ((part = getPart(chan)) != 0)
part->noteOn(param1, param2);
} else {
_active_notes[param1] |= (1 << chan);
}
break;
case 0xB: // Control Change
part = (param1 == 123 ? getActivePart(chan) : getPart(chan));
if (!part)
break;
switch (param1) {
case 0: // Bank select. Not supported
break;
case 1: // Modulation Wheel
part->modulationWheel(param2);
break;
case 7: // Volume
part->volume(param2);
break;
case 10: // Pan Position
part->set_pan(param2 - 0x40);
break;
case 16: // Pitchbend Factor(non-standard)
part->pitchBendFactor(param2);
break;
case 17: // GP Slider 2
part->set_detune(param2 - 0x40);
break;
case 18: // GP Slider 3
part->set_pri(param2 - 0x40);
_se->reallocateMidiChannels(_midi);
break;
case 64: // Sustain Pedal
part->sustain(param2 != 0);
break;
case 91: // Effects Level
part->effectLevel(param2);
break;
case 93: // Chorus Level
part->chorusLevel(param2);
break;
case 116: // XMIDI For Loop. Not supported
// Used in the ending sequence of puttputt
break;
case 117: // XMIDI Next/Break. Not supported
// Used in the ending sequence of puttputt
break;
case 123: // All Notes Off
part->allNotesOff();
break;
default:
error("Player::send(): Invalid control change %d", param1);
}
break;
case 0xC: // Program Change
part = getPart(chan);
if (part) {
if (_isMIDI) {
if (param1 < 128)
part->programChange(param1);
} else {
if (param1 < 32)
part->load_global_instrument(param1);
}
}
break;
case 0xE: // Pitch Bend
part = getPart(chan);
if (part)
part->pitchBend(((param2 << 7) | param1) - 0x2000);
break;
case 0xA: // Aftertouch
case 0xD: // Channel Pressure
case 0xF: // Sequence Controls
break;
default:
if (!_scanning) {
error("Player::send(): Invalid command %d", cmd);
clear();
}
}
return;
}
void Player::sysEx(const byte *p, uint16 len) {
byte a;
byte buf[128];
Part *part;
// Check SysEx manufacturer.
a = *p++;
--len;
if (a != IMUSE_SYSEX_ID) {
if (a == ROLAND_SYSEX_ID) {
// Roland custom instrument definition.
if (_isMIDI || _isMT32) {
part = getPart(p[0] & 0x0F);
if (part) {
part->_instrument.roland(p - 1);
if (part->clearToTransmit())
part->_instrument.send(part->_mc);
}
}
} else if (a == YM2612_SYSEX_ID) {
// FM-TOWNS custom instrument definition
_midi->sysEx_customInstrument(p[0], 'EUP ', p + 1);
} else {
// SysEx manufacturer 0x97 has been spotted in the
// Monkey Island 2 AdLib music, so don't make this a
// fatal error. See bug #1481383.
if (a == 0)
warning("Unknown SysEx manufacturer 0x00 0x%02X 0x%02X", p[0], p[1]);
else
warning("Unknown SysEx manufacturer 0x%02X", (int)a);
}
return;
}
--len;
// Too big?
if (len >= sizeof(buf) * 2)
return;
if (!_scanning) {
for (a = 0; a < len + 1 && a < 19; ++a) {
sprintf((char *)&buf[a*3], " %02X", p[a]);
} // next for
if (a < len + 1) {
buf[a*3] = buf[a*3+1] = buf[a*3+2] = '.';
++a;
} // end if
buf[a*3] = '\0';
debugC(DEBUG_IMUSE, "[%02d] SysEx:%s", _id, buf);
}
if (_se->_sysex)
(*_se->_sysex)(this, p, len);
}
void Player::decode_sysex_bytes(const byte *src, byte *dst, int len) {
while (len >= 0) {
2006-11-12 02:24:25 +00:00
*dst++ = ((src[0] << 4) & 0xFF) | (src[1] & 0xF);
src += 2;
len -= 2;
}
}
void Player::maybe_jump(byte cmd, uint track, uint beat, uint tick) {
// Is this the hook I'm waiting for?
if (cmd && _hook._jump[0] != cmd)
return;
// Reset hook?
if (cmd != 0 && cmd < 0x80) {
_hook._jump[0] = _hook._jump[1];
_hook._jump[1] = 0;
}
jump(track, beat, tick);
}
void Player::maybe_set_transpose(byte *data) {
byte cmd;
cmd = data[0];
// Is this the hook I'm waiting for?
if (cmd && _hook._transpose != cmd)
return;
// Reset hook?
if (cmd != 0 && cmd < 0x80)
_hook._transpose = 0;
setTranspose(data[1], (int8)data[2]);
}
void Player::maybe_part_onoff(byte *data) {
byte cmd, *p;
uint chan;
Part *part;
cmd = data[1];
chan = data[0];
p = &_hook._part_onoff[chan];
// Is this the hook I'm waiting for?
if (cmd && *p != cmd)
return;
if (cmd != 0 && cmd < 0x80)
*p = 0;
part = getPart(chan);
if (part)
part->set_onoff(data[2] != 0);
}
void Player::maybe_set_volume(byte *data) {
byte cmd;
byte *p;
uint chan;
Part *part;
cmd = data[1];
chan = data[0];
p = &_hook._part_volume[chan];
// Is this the hook I'm waiting for?
if (cmd && *p != cmd)
return;
// Reset hook?
if (cmd != 0 && cmd < 0x80)
*p = 0;
part = getPart(chan);
if (part)
part->volume(data[2]);
}
void Player::maybe_set_program(byte *data) {
byte cmd;
byte *p;
uint chan;
Part *part;
cmd = data[1];
chan = data[0];
// Is this the hook I'm waiting for?
p = &_hook._part_program[chan];
if (cmd && *p != cmd)
return;
if (cmd != 0 && cmd < 0x80)
*p = 0;
part = getPart(chan);
if (part)
part->programChange(data[2]);
}
void Player::maybe_set_transpose_part(byte *data) {
byte cmd;
byte *p;
uint chan;
cmd = data[1];
chan = data[0];
// Is this the hook I'm waiting for?
p = &_hook._part_transpose[chan];
if (cmd && *p != cmd)
return;
// Reset hook?
if (cmd != 0 && cmd < 0x80)
*p = 0;
part_set_transpose(chan, data[2], (int8)data[3]);
}
int Player::setTranspose(byte relative, int b) {
Part *part;
if (b > 24 || b < -24 || relative > 1)
return -1;
if (relative)
b = transpose_clamp(_transpose + b, -24, 24);
_transpose = b;
for (part = _parts; part; part = part->_next) {
part->set_transpose(part->_transpose);
}
return 0;
}
void Player::part_set_transpose(uint8 chan, byte relative, int8 b) {
Part *part;
if (b > 24 || b < -24)
return;
part = getPart(chan);
if (!part)
return;
if (relative)
b = transpose_clamp(b + part->_transpose, -7, 7);
part->set_transpose(b);
}
bool Player::jump(uint track, uint beat, uint tick) {
if (!_parser)
return false;
if (_parser->setTrack(track))
_track_index = track;
if (!_parser->jumpToTick((beat - 1) * TICKS_PER_BEAT + tick))
return false;
turn_off_pedals();
return true;
}
bool Player::setLoop(uint count, uint tobeat, uint totick, uint frombeat, uint fromtick) {
if (tobeat + 1 >= frombeat)
return false;
if (tobeat == 0)
tobeat = 1;
_loop_counter = 0; // Because of possible interrupts
_loop_to_beat = tobeat;
_loop_to_tick = totick;
_loop_from_beat = frombeat;
_loop_from_tick = fromtick;
_loop_counter = count;
return true;
}
void Player::clearLoop() {
_loop_counter = 0;
}
void Player::turn_off_pedals() {
Part *part;
for (part = _parts; part; part = part->_next) {
if (part->_pedal)
part->sustain(false);
}
}
Part *Player::getActivePart(uint8 chan) {
Part *part = _parts;
while (part) {
if (part->_chan == chan)
return part;
part = part->_next;
}
return 0;
}
Part *Player::getPart(uint8 chan) {
Part *part = getActivePart(chan);
if (part)
return part;
part = _se->allocate_part(_priority, _midi);
if (!part) {
debug(1, "No parts available");
return NULL;
}
// Insert part into front of parts list
part->_prev = NULL;
part->_next = _parts;
if (_parts)
_parts->_prev = part;
_parts = part;
part->_chan = chan;
part->setup(this);
return part;
}
void Player::setPriority(int pri) {
Part *part;
_priority = pri;
for (part = _parts; part; part = part->_next) {
part->set_pri(part->_pri);
}
_se->reallocateMidiChannels(_midi);
}
void Player::setPan(int pan) {
Part *part;
_pan = pan;
for (part = _parts; part; part = part->_next) {
part->set_pan(part->_pan);
}
}
void Player::setDetune(int detune) {
Part *part;
_detune = detune;
for (part = _parts; part; part = part->_next) {
part->set_detune(part->_detune);
}
}
void Player::setOffsetNote(int offset) {
_note_offset = offset;
}
int Player::scan(uint totrack, uint tobeat, uint totick) {
if (!_active || !_parser)
return -1;
if (tobeat == 0)
tobeat++;
turn_off_parts();
memset(_active_notes, 0, sizeof(_active_notes));
_scanning = true;
// If the scan involves a track switch, scan to the end of
// the current track so that our state when starting the
// new track is fully up to date.
if (totrack != _track_index)
_parser->jumpToTick((uint32)-1, true);
_parser->setTrack(totrack);
if (!_parser->jumpToTick((tobeat - 1) * TICKS_PER_BEAT + totick, true)) {
_scanning = false;
return -1;
}
_scanning = false;
_se->reallocateMidiChannels(_midi);
play_active_notes();
if (_track_index != totrack) {
_track_index = totrack;
_loop_counter = 0;
}
return 0;
}
void Player::turn_off_parts() {
Part *part;
for (part = _parts; part; part = part->_next)
part->off();
_se->reallocateMidiChannels(_midi);
}
void Player::play_active_notes() {
int i, j;
uint mask;
Part *part;
for (i = 0; i < 16; ++i) {
part = getPart(i);
if (part) {
mask = 1 << i;
for (j = 0; j < 128; ++j) {
if (_active_notes[j] & mask)
part->noteOn(j, 80);
}
}
}
}
int Player::setVolume(byte vol) {
Part *part;
if (vol > 127)
return -1;
_volume = vol;
_vol_eff = _se->get_channel_volume(_vol_chan) * (vol + 1) >> 7;
for (part = _parts; part; part = part->_next) {
part->volume(part->_vol);
}
return 0;
}
int Player::getParam(int param, byte chan) {
switch (param) {
case 0:
return (byte)_priority;
case 1:
return (byte)_volume;
case 2:
return (byte)_pan;
case 3:
return (byte)_transpose;
case 4:
return (byte)_detune;
case 5:
return _speed;
case 6:
return _track_index;
case 7:
return getBeatIndex();
case 8:
return (_parser ? _parser->getTick() % TICKS_PER_BEAT : 0); // _tick_index;
case 9:
return _loop_counter;
case 10:
return _loop_to_beat;
case 11:
return _loop_to_tick;
case 12:
return _loop_from_beat;
case 13:
return _loop_from_tick;
case 14:
case 15:
case 16:
case 17:
return query_part_param(param, chan);
case 18:
case 19:
case 20:
case 21:
case 22:
case 23:
return _hook.query_param(param, chan);
default:
return -1;
}
}
int Player::query_part_param(int param, byte chan) {
Part *part;
part = _parts;
while (part) {
if (part->_chan == chan) {
switch (param) {
case 14:
return part->_on;
case 15:
return part->_vol;
case 16:
// FIXME: Need to know where this occurs...
error("Trying to cast instrument (%d, %d) -- please tell Fingolfin", param, chan);
// In old versions of the code, this used to return part->_program.
// This was changed in revision 2.29 of imuse.cpp (where this code used
// to reside).
// return (int)part->_instrument;
case 17:
return part->_transpose;
default:
return -1;
}
}
part = part->_next;
}
return 129;
}
void Player::onTimer() {
// First handle any parameter transitions
// that are occuring.
transitionParameters();
// Since the volume parameter can cause
// the player to be deactivated, check
// to make sure we're still active.
if (!_active || !_parser)
return;
uint32 target_tick = _parser->getTick();
uint beat_index = target_tick / TICKS_PER_BEAT + 1;
uint tick_index = target_tick % TICKS_PER_BEAT;
if (_loop_counter &&(beat_index > _loop_from_beat ||
(beat_index == _loop_from_beat && tick_index >= _loop_from_tick)))
{
_loop_counter--;
jump(_track_index, _loop_to_beat, _loop_to_tick);
}
_parser->onTimer();
}
// "time" is referenced as hundredths of a second.
// IS THAT CORRECT??
// We convert it to microseconds before proceeding
int Player::addParameterFader(int param, int target, int time) {
int start;
switch (param) {
case ParameterFader::pfVolume:
// HACK: If volume is set to 0 with 0 time,
// set it so immediately but DON'T clear
// the player. This fixes a problem with
// music being cleared inappropriately
// in S&M when playing with the Dinosaur.
if (!target && !time) {
setVolume(0);
return 0;
}
// Volume fades are handled differently.
start = _volume;
break;
case ParameterFader::pfTranspose:
// FIXME: Is this transpose? And what's the scale?
// It's set to fade to -2400 in the tunnel of love.
// debug(0, "parameterTransition(3) outside Tunnel of Love?");
start = _transpose;
// target /= 200;
break;
case ParameterFader::pfSpeed: // impSpeed
// FIXME: Is the speed from 0-100?
// Right now I convert it to 0-128.
start = _speed;
// target = target * 128 / 100;
break;
case 127:
{ // FIXME? I *think* this clears all parameter faders.
ParameterFader *ptr = &_parameterFaders[0];
int i;
for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr)
ptr->param = 0;
return 0;
}
break;
default:
2006-11-12 02:24:25 +00:00
debug(0, "Player::addParameterFader(%d, %d, %d): Unknown parameter", param, target, time);
return 0; // Should be -1, but we'll let the script think it worked.
}
ParameterFader *ptr = &_parameterFaders[0];
ParameterFader *best = 0;
int i;
for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) {
if (ptr->param == param) {
best = ptr;
start = ptr->end;
break;
} else if (!ptr->param) {
best = ptr;
}
}
if (best) {
best->param = param;
best->start = start;
best->end = target;
if (!time)
best->total_time = 1;
else
best->total_time = (uint32)time * 10000;
best->current_time = 0;
} else {
debug(0, "IMuse Player %d: Out of parameter faders", _id);
return -1;
}
return 0;
}
void Player::transitionParameters() {
uint32 advance = _midi->getBaseTempo();
int value;
ParameterFader *ptr = &_parameterFaders[0];
int i;
for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) {
if (!ptr->param)
continue;
ptr->current_time += advance;
if (ptr->current_time > ptr->total_time)
ptr->current_time = ptr->total_time;
value = (int32)ptr->start + (int32)(ptr->end - ptr->start) * (int32)ptr->current_time / (int32)ptr->total_time;
switch (ptr->param) {
case ParameterFader::pfVolume:
// Volume.
if (!value && !ptr->end) {
clear();
return;
}
setVolume((byte)value);
break;
case ParameterFader::pfTranspose:
// FIXME: Is this really transpose?
setTranspose(0, value / 100);
setDetune(value % 100);
break;
case ParameterFader::pfSpeed: // impSpeed:
// Speed.
setSpeed((byte)value);
break;
default:
ptr->param = 0;
}
if (ptr->current_time >= ptr->total_time)
ptr->param = 0;
}
}
uint Player::getBeatIndex() {
2005-05-29 18:14:59 +00:00
return (_parser ? (_parser->getTick() / TICKS_PER_BEAT + 1) : 0);
}
void Player::removePart(Part *part) {
// Unlink
if (part->_next)
part->_next->_prev = part->_prev;
if (part->_prev)
part->_prev->_next = part->_next;
else
_parts = part->_next;
part->_next = part->_prev = 0;
}
void Player::fixAfterLoad() {
_midi = _se->getBestMidiDriver(_id);
if (!_midi) {
clear();
} else {
start_seq_sound(_id, false);
setSpeed(_speed);
if (_parser)
_parser->jumpToTick(_music_tick); // start_seq_sound already switched tracks
_isMT32 = _se->isMT32(_id);
_isMIDI = _se->isMIDI(_id);
}
}
void Player::metaEvent(byte type, byte *msg, uint16 len) {
if (type == 0x2F)
clear();
}
////////////////////////////////////////
//
// Player save/load functions
//
////////////////////////////////////////
void Player::saveLoadWithSerializer(Serializer *ser) {
static const SaveLoadEntry playerEntries[] = {
MKLINE(Player, _active, sleByte, VER(8)),
MKLINE(Player, _id, sleUint16, VER(8)),
MKLINE(Player, _priority, sleByte, VER(8)),
MKLINE(Player, _volume, sleByte, VER(8)),
MKLINE(Player, _pan, sleInt8, VER(8)),
MKLINE(Player, _transpose, sleByte, VER(8)),
MKLINE(Player, _detune, sleInt8, VER(8)),
MKLINE(Player, _vol_chan, sleUint16, VER(8)),
MKLINE(Player, _vol_eff, sleByte, VER(8)),
MKLINE(Player, _speed, sleByte, VER(8)),
MK_OBSOLETE(Player, _song_index, sleUint16, VER(8), VER(19)),
MKLINE(Player, _track_index, sleUint16, VER(8)),
MK_OBSOLETE(Player, _timer_counter, sleUint16, VER(8), VER(17)),
MKLINE(Player, _loop_to_beat, sleUint16, VER(8)),
MKLINE(Player, _loop_from_beat, sleUint16, VER(8)),
MKLINE(Player, _loop_counter, sleUint16, VER(8)),
MKLINE(Player, _loop_to_tick, sleUint16, VER(8)),
MKLINE(Player, _loop_from_tick, sleUint16, VER(8)),
MK_OBSOLETE(Player, _tempo, sleUint32, VER(8), VER(19)),
MK_OBSOLETE(Player, _cur_pos, sleUint32, VER(8), VER(17)),
MK_OBSOLETE(Player, _next_pos, sleUint32, VER(8), VER(17)),
MK_OBSOLETE(Player, _song_offset, sleUint32, VER(8), VER(17)),
MK_OBSOLETE(Player, _tick_index, sleUint16, VER(8), VER(17)),
MK_OBSOLETE(Player, _beat_index, sleUint16, VER(8), VER(17)),
MK_OBSOLETE(Player, _ticks_per_beat, sleUint16, VER(8), VER(17)),
MKLINE(Player, _music_tick, sleUint32, VER(19)),
MKLINE(Player, _hook._jump[0], sleByte, VER(8)),
MKLINE(Player, _hook._transpose, sleByte, VER(8)),
MKARRAY(Player, _hook._part_onoff[0], sleByte, 16, VER(8)),
MKARRAY(Player, _hook._part_volume[0], sleByte, 16, VER(8)),
MKARRAY(Player, _hook._part_program[0], sleByte, 16, VER(8)),
MKARRAY(Player, _hook._part_transpose[0], sleByte, 16, VER(8)),
MKEND()
};
const SaveLoadEntry parameterFaderEntries[] = {
MKLINE(ParameterFader, param, sleInt16, VER(17)),
MKLINE(ParameterFader, start, sleInt16, VER(17)),
MKLINE(ParameterFader, end, sleInt16, VER(17)),
MKLINE(ParameterFader, total_time, sleUint32, VER(17)),
MKLINE(ParameterFader, current_time, sleUint32, VER(17)),
MKEND()
};
if (!ser->isSaving() && _parser) {
delete _parser;
_parser = 0;
}
_music_tick = _parser ? _parser->getTick() : 0;
int num;
if (ser->isSaving()) {
num = (_parts ? (_parts - _se->_parts + 1) : 0);
ser->saveUint16(num);
} else {
num = ser->loadUint16();
_parts = (num ? &_se->_parts[num - 1] : 0);
}
ser->saveLoadEntries(this, playerEntries);
ser->saveLoadArrayOf(_parameterFaders, ARRAYSIZE(_parameterFaders),
2003-11-08 21:59:32 +00:00
sizeof(ParameterFader), parameterFaderEntries);
return;
}
} // End of namespace Scumm