/* 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 3 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, see . * */ #include "common/util.h" #include "engines/engine.h" #include "scumm/imuse/imuse_internal.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(nullptr), _parser(nullptr), _parts(nullptr), _active(false), _scanning(false), _id(0), _priority(0), _volume(0), _pan(0), _transpose(0), _detune(0), _note_offset(0), _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), _supportsPercussion(false), _se(nullptr), _vol_chan(0), _abort(false), _music_tick(0), _parserType(kParserTypeNone), _transitionTimer(0) { } Player::~Player() { if (_parser) { delete _parser; _parser = nullptr; } } 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); return false; } _isMT32 = _se->isMT32(sound); _isMIDI = _se->isMIDI(sound); _supportsPercussion = _se->supportsPercussion(sound); _parts = nullptr; _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 = nullptr; return false; } debugC(DEBUG_IMUSE, "Starting music %d", sound); return true; } int Player::getMusicTimer() const { return _parser ? (_parser->getTick() * 2 / _parser->getPPQN()) : 0; } bool Player::isFadingOut() const { for (int i = 0; i < ARRAYSIZE(_parameterFaders); ++i) { const ParameterFader &p = _parameterFaders[i]; if (p.param == ParameterFader::pfVolume && _volume + p.cntdwn * p.incr + ((p.irem + p.cntdwn * p.ifrac) / p.ttime) * p.dir == 0) return true; } return false; } void Player::clear() { if (!_active) return; debugC(DEBUG_IMUSE, "Stopping music %d", _id); if (_parser) { _parser->unloadMusic(); _parser->setMidiDriver(nullptr); } uninit_parts(); _se->ImFireAllTriggers(_id); _active = false; _midi = nullptr; _id = 0; _note_offset = 0; _speed = _se->_newSystem ? 64 : 128; } void Player::hook_clear() { _hook.reset(); } 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 == nullptr) return -1; if (!memcmp(ptr, "RO", 2)) { // Old style 'RO' resource if (_parserType != kParserTypeRO) { delete _parser; _parser = MidiParser_createRO(); _parserType = kParserTypeRO; } } else if (!memcmp(ptr, "FORM", 4)) { // Humongous Games XMIDI resource if (_parserType != kParserTypeXMI) { delete _parser; _parser = MidiParser::createParser_XMIDI(); _parserType = kParserTypeXMI; } } else { // SCUMM SMF resource if (_parserType != kParserTypeSMF) { delete _parser; _parser = MidiParser::createParser_SMF(); _parserType = kParserTypeSMF; } } _parser->setMidiDriver(this); _parser->property(MidiParser::mpSmartJump, 1); _parser->loadMusic(ptr, 0); _parser->setTrack(_track_index); ptr = _se->findStartOfSound(sound, IMuseInternal::kMDhd); int defSpeed = _se->_newSystem ? 64 : 128; setSpeed(reset_vars ? (ptr ? (READ_BE_UINT32(&ptr[4]) && ptr[15] ? ptr[15] : defSpeed) : defSpeed) : _speed); return 0; } void Player::loadStartParameters(int sound) { _priority = _se->_newSystem ? 0x40 : 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) { // While the old system (MI1, MI2, DOTT) uses 128 as the default, // making anything below slower and anything above faster, the new // system centers on 64. Consequently, the new system does not accept // values above 127, while the old one accepts anything. int shift = 7; if (_se->_newSystem) { shift = 6; if (speed > 127) return; } _speed = speed; if (_parser) _parser->setTimerRate(((_midi->getBaseTempo() * speed) >> shift) * _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)) != nullptr) 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)) != nullptr) 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 if (_se->_newSystem) part->set_polyphony(param2); else part->set_detune(param2 - 0x40); break; case 18: // GP Slider 3 if (!_se->_newSystem) param2 -= 0x40; part->set_pri(param2); _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 (or also volume fade for Samnmax) 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. // There is at least one (pointless) attempt in INDY4 Amiga to send this, too. if ((_isMIDI && _se->_soundType != MDT_AMIGA) || _isMT32) { part = getPart(p[0] & 0x0F); if (part) { part->_instrument.roland(p - 1); if (part->clearToTransmit()) part->_instrument.send(part->_mc); } } } else { // SysEx manufacturer 0x97 has been spotted in the // Monkey Island 2 AdLib music, so don't make this a // fatal error. See bug #2595. // The Macintosh version of Monkey Island 2 simply // ignores these SysEx events too. 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)) return; if (!_scanning) { for (a = 0; a < len + 1 && a < 19; ++a) { snprintf((char *)&buf[a * 3], 3 * sizeof(char) + 1, " %02X", (int)p[a]); } if (a < len + 1 && (a * 3 < sizeof(buf) - 2)) { if (a * 3 + 2 < int(sizeof(buf))) buf[a * 3] = buf[a * 3 + 1] = buf[a * 3 + 2] = '.'; else warning("Player::sysEx(): Message too long (truncated)"); ++a; } if (a * 3 < sizeof(buf)) buf[a * 3] = '\0'; else warning("Player::sysEx(): Message too long (truncated)"); debugC(DEBUG_IMUSE, "[%02d] SysEx:%s", _id, buf); } if (_se->_sysex) (*_se->_sysex)(this, p, len); } uint16 Player::sysExNoDelay(const byte *msg, uint16 length) { sysEx(msg, length); // The reason for adding this delay was the music track in the MI2 start scene (on the bridge, with Largo) when // played on real hardware (in my case a Roland CM32L). The track starts with several sysex messages (mostly // iMuse control messages, but also a Roland custom timbre sysex message). When played through the Munt emulator // this works totally fine, but the real hardware seems to still "choke" on the sysex data, when the actual song // playback has already started. This will cause a skipping of the first couple of notes, since the midi parser // will not wait, but strictly enforce sync on the next time stamps. // My tests with the dreamm emulator on that scene did sometimes show the same issue (although to a weaker extent), // but most of the time not. So it seems to be rather a delicate and race-condition prone matter. The original // parser handles the timing differently than our general purpose parser and the code execution is also expected // to be much slower, so that might make all the difference here. It is really a flaw of the track. The time stamps // after the sysex messages should have been made a bit more generous. // Now, I have added some delays here that I have taken from the original DOTT MT-32 driver's sysex function which // are supposed to handle the situation when _scanning is enabled. For non-_scanning situations there is no delay in // the original driver, since apparently is wasn't necessary. // We only need to intercept actual hardware sysex messages here. So, for the iMuse control messages, we intercept // just type 0, since that one leads to hardware messages. This is not a perfect solution, but it seems to work // as intended. if (_isMT32 && !_scanning && ((msg[0] == IMUSE_SYSEX_ID && msg[1] == 0) || msg[0] == ROLAND_SYSEX_ID)) return length >= 25 ? 70 : 20; return 0; } void Player::decode_sysex_bytes(const byte *src, byte *dst, int len) { while (len >= 0) { *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, -7, 7); _transpose = b; // MI2 and INDY4 use boundaries of -12/12 for MT-32 and -24/24 for AdLib and PC Speaker, DOTT uses -12/12 for everything. int lim = (_se->_game_id == GID_TENTACLE || _se->isNativeMT32()) ? 12 : 24; for (part = _parts; part; part = part->_next) part->set_transpose(part->_transpose, -lim, lim); 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); // MI2 and INDY4 use boundaries of -12/12 for MT-32 and -24/24 for AdLib and PC Speaker, DOTT uses -12/12 for everything. int lim = (_se->_game_id == GID_TENTACLE || _se->isNativeMT32()) ? 12 : 24; part->set_transpose(b, -lim, lim); } 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; // FIXME: Thread safety? _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 nullptr; } 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 nullptr; } // Insert part into front of parts list part->_prev = nullptr; 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; if (!_se->_dynamicChanAllocation) { turn_off_pedals(); for (part = _parts; part; part = part->_next) part->allNotesOff(); } else { 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 occurring. 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(); } int Player::addParameterFader(int param, int target, int time) { int start; switch (param) { case ParameterFader::pfVolume: if (!time) { setVolume(target); return 0; } start = _volume; break; case ParameterFader::pfTranspose: // It's set to fade to -2400 in the tunnel of love. // debug(0, "parameterTransition(3) outside Tunnel of Love?"); if (!time) { setDetune(target); return 0; } start = _detune; break; case ParameterFader::pfSpeed: // impSpeed start = _speed; 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: 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 = nullptr; int i; for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) { if (ptr->param == param) { best = ptr; break; } else if (!ptr->param) { best = ptr; } } if (best) { best->param = param; best->state = start; best->ttime = best->cntdwn = time; int diff = target - start; best->dir = (diff >= 0) ? 1 : -1; best->incr = diff / time; best->ifrac = ABS(diff) % time; best->irem = 0; } else { debug(0, "IMuse Player %d: Out of parameter faders", _id); return -1; } return 0; } void Player::transitionParameters() { uint32 advance = _midi->getBaseTempo(); _transitionTimer += advance; while (_transitionTimer >= 16667) { _transitionTimer -= 16667; ParameterFader *ptr = &_parameterFaders[0]; for (int i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) { if (!ptr->param) continue; int32 mod = ptr->incr; ptr->irem += ptr->ifrac; if (ptr->irem >= ptr->ttime) { ptr->irem -= ptr->ttime; mod += ptr->dir; } if (!mod) { if (!ptr->cntdwn || !--ptr->cntdwn) ptr->param = 0; continue; } ptr->state += mod; switch (ptr->param) { case ParameterFader::pfVolume: // Volume if (ptr->state >= 0 && ptr->state <= 127) { setVolume((byte)ptr->state); if (ptr->state == 0) { clear(); return; } } break; case ParameterFader::pfTranspose: if (ptr->state >= -9216 && ptr->state <= 9216) setDetune(ptr->state); break; case ParameterFader::pfSpeed: // Speed. if (ptr->state >= 0 && ptr->state <= 127) { setSpeed((byte)ptr->state); } break; default: ptr->param = 0; } if (!ptr->cntdwn || !--ptr->cntdwn) ptr->param = 0; } } } uint Player::getBeatIndex() { 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 = nullptr; } 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); _supportsPercussion = _se->supportsPercussion(_id); } } void Player::metaEvent(byte type, const byte *msg, uint16 len) { if (type == 0x2F) clear(); } //////////////////////////////////////// // // Player save/load functions // //////////////////////////////////////// static void syncWithSerializer(Common::Serializer &s, ParameterFader &pf) { s.syncAsSint16LE(pf.param, VER(17)); if (s.isLoading() && s.getVersion() < 116) { int16 start = 0, end = 0; uint32 tt = 0, ct = 0; s.syncAsSint16LE(start, VER(17)); s.syncAsSint16LE(end, VER(17)); s.syncAsUint32LE(tt, VER(17)); s.syncAsUint32LE(ct, VER(17)); int32 diff = end - start; if (pf.param && diff && tt) { if (tt < 10000) { tt = 10000; ct = tt - diff; } pf.dir = diff / ABS(diff); pf.incr = diff / (tt / 10000); pf.ifrac = ABS(diff) % (tt / 10000); pf.state = (int32)start + diff * (int32)ct / (int32)tt; } else { pf.param = 0; } pf.irem = 0; pf.cntdwn = 0; } else { s.syncAsSByte(pf.dir, VER(116)); s.syncAsSint16LE(pf.incr, VER(116)); s.syncAsUint16LE(pf.ifrac, VER(116)); s.syncAsUint16LE(pf.irem, VER(116)); s.syncAsUint16LE(pf.ttime, VER(116)); s.syncAsUint16LE(pf.cntdwn, VER(116)); s.syncAsSint16LE(pf.state, VER(116)); } } void Player::saveLoadWithSerializer(Common::Serializer &s) { if (!s.isSaving() && _parser) { delete _parser; _parser = nullptr; _parserType = kParserTypeNone; } _music_tick = _parser ? _parser->getTick() : 0; int num; if (s.isSaving()) { num = (_parts ? (_parts - _se->_parts + 1) : 0); s.syncAsUint16LE(num); } else { s.syncAsUint16LE(num); _parts = (num ? &_se->_parts[num - 1] : nullptr); } s.syncAsByte(_active, VER(8)); s.syncAsUint16LE(_id, VER(8)); s.syncAsByte(_priority, VER(8)); s.syncAsByte(_volume, VER(8)); s.syncAsSByte(_pan, VER(8)); s.syncAsByte(_transpose, VER(8)); s.syncAsSByte(_detune, VER(8), VER(115)); s.syncAsSint16LE(_detune, VER(116)); s.syncAsUint16LE(_vol_chan, VER(8)); s.syncAsByte(_vol_eff, VER(8)); s.syncAsByte(_speed, VER(8)); s.skip(2, VER(8), VER(19)); // _song_index s.syncAsUint16LE(_track_index, VER(8)); s.skip(2, VER(8), VER(17)); // _timer_counter s.syncAsUint16LE(_loop_to_beat, VER(8)); s.syncAsUint16LE(_loop_from_beat, VER(8)); s.syncAsUint16LE(_loop_counter, VER(8)); s.syncAsUint16LE(_loop_to_tick, VER(8)); s.syncAsUint16LE(_loop_from_tick, VER(8)); s.skip(4, VER(8), VER(19)); // _tempo s.skip(4, VER(8), VER(17)); // _cur_pos s.skip(4, VER(8), VER(17)); // _next_pos s.skip(4, VER(8), VER(17)); // _song_offset s.skip(2, VER(8), VER(17)); // _tick_index s.skip(2, VER(8), VER(17)); // _beat_index s.skip(2, VER(8), VER(17)); // _ticks_per_beat s.syncAsUint32LE(_music_tick, VER(19)); s.syncAsByte(_hook._jump[0], VER(8)); s.syncAsByte(_hook._transpose, VER(8)); s.syncBytes(_hook._part_onoff, 16, VER(8)); s.syncBytes(_hook._part_volume, 16, VER(8)); s.syncBytes(_hook._part_program, 16, VER(8)); s.syncBytes(_hook._part_transpose, 16, VER(8)); s.syncArray(_parameterFaders, ARRAYSIZE(_parameterFaders), syncWithSerializer); if (_se->_newSystem && s.isLoading() && s.getVersion() < VER(117) && _speed == 128) _speed = 64; } } // End of namespace Scumm