aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/imuse_player.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/scumm/imuse_player.cpp')
-rw-r--r--engines/scumm/imuse_player.cpp1241
1 files changed, 1241 insertions, 0 deletions
diff --git a/engines/scumm/imuse_player.cpp b/engines/scumm/imuse_player.cpp
new file mode 100644
index 0000000000..06ebd289cd
--- /dev/null
+++ b/engines/scumm/imuse_player.cpp
@@ -0,0 +1,1241 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2001 Ludvig Strigeus
+ * Copyright (C) 2001-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+
+#include "common/util.h"
+#include "base/engine.h"
+
+#include "scumm/imuse_internal.h"
+#include "scumm/saveload.h"
+#include "scumm/scumm.h"
+
+#include "sound/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();
+extern MidiParser *MidiParser_createEUP();
+
+uint16 Player::_active_notes[128];
+
+
+
+//////////////////////////////////////////////////
+//
+// IMuse Player implementation
+//
+//////////////////////////////////////////////////
+
+Player::Player() :
+ _midi(0),
+ _parser(0),
+ _passThrough(0),
+ _parts(0),
+ _active(false),
+ _scanning(false),
+ _id(0),
+ _priority(0),
+ _volume(0),
+ _pan(0),
+ _transpose(0),
+ _detune(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),
+ _se(0),
+ _vol_chan(0){
+}
+
+Player::~Player() {
+ if (_parser) {
+ delete _parser;
+ _parser = 0;
+ }
+}
+
+bool Player::startSound(int sound, MidiDriver *midi, bool passThrough) {
+ void *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);
+
+ _parts = NULL;
+ _active = true;
+ _midi = midi;
+ _id = sound;
+ _priority = 0x80;
+ _volume = 0x7F;
+ _vol_chan = 0xFFFF;
+ _vol_eff = (_se->get_channel_volume(0xFFFF) << 7) >> 7;
+ _pan = 0;
+ _transpose = 0;
+ _detune = 0;
+ _passThrough = passThrough;
+
+ for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i)
+ _parameterFaders[i].init();
+ hook_clear();
+
+ if (start_seq_sound(sound) != 0) {
+ _active = false;
+ _midi = NULL;
+ return false;
+ }
+
+#ifdef IMUSE_DEBUG
+ debug(0, "Starting music %d", sound);
+#endif
+ return true;
+}
+
+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 &&
+ _parameterFaders[i].end == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Player::clear() {
+ if (!_active)
+ return;
+
+#ifdef IMUSE_DEBUG
+ debug(0, "Stopping music %d", _id);
+#endif
+
+ if (_parser) {
+ _parser->unloadMusic();
+ delete _parser;
+ _parser = 0;
+ }
+ uninit_parts();
+ _se->ImFireAllTriggers(_id);
+ _active = false;
+ _midi = NULL;
+ _id = 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;
+ if (_parser)
+ delete _parser;
+
+ if (!memcmp(ptr, "RO", 2)) {
+ // Old style 'RO' resource
+ _parser = MidiParser_createRO();
+ } else if (!memcmp(ptr, "SO", 2)) {
+ // Euphony (FM-TOWNS) resource
+ _parser = MidiParser_createEUP();
+ } 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);
+ setSpeed(reset_vars ? 128 : _speed);
+
+ return 0;
+}
+
+void Player::uninit_parts() {
+ if (_parts && _parts->_player != this)
+ error("asd");
+ 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) {
+ if (_passThrough) {
+ _midi->send(b);
+ return;
+ }
+
+ 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
+ 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(byte *p, uint16 len) {
+ byte code;
+ byte a;
+ uint b;
+ byte buf[128];
+ Part *part;
+
+ if (_passThrough) {
+ _midi->sysEx(p, len);
+ return;
+ }
+
+ // Check SysEx manufacturer.
+ a = *p++;
+ --len;
+ if (a != IMUSE_SYSEX_ID) {
+ if (a == ROLAND_SYSEX_ID) {
+ // Roland custom instrument definition.
+ 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 {
+ error("Unknown SysEx manufacturer 0x%02X", (int)a);
+ }
+ return;
+ }
+ --len;
+
+ // Too big?
+ if (len >= sizeof(buf) * 2)
+ return;
+
+#ifdef IMUSE_DEBUG
+ 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';
+ debug(0, "[%02d] SysEx:%s", _id, buf);
+ }
+#endif
+
+ switch (code = *p++) {
+ case 0:
+ if (g_scumm->_gameId != GID_SAMNMAX) {
+ // There are 17 bytes of useful information beyond
+ // what we've read so far. All we know about them is
+ // as follows:
+ // BYTE 00: Channel #
+ // BYTE 02: BIT 01(0x01): Part on?(1 = yes)
+ // BYTE 04: Priority adjustment [guessing]
+ // BYTE 05: Volume(upper 4 bits) [guessing]
+ // BYTE 06: Volume(lower 4 bits) [guessing]
+ // BYTE 09: BIT 04(0x08): Percussion?(1 = yes)
+ // BYTE 15: Program(upper 4 bits)
+ // BYTE 16: Program(lower 4 bits)
+ part = getPart(p[0] & 0x0F);
+ if (part) {
+ part->set_onoff(p[2] & 0x01);
+ part->set_pri(p[4]);
+ part->volume((p[5] & 0x0F) << 4 |(p[6] & 0x0F));
+ part->_percussion = _isMIDI ? ((p[9] & 0x08) > 0) : false;
+ if (part->_percussion) {
+ if (part->_mc) {
+ part->off();
+ _se->reallocateMidiChannels(_midi);
+ }
+ } else {
+ // Even in cases where a program does not seem to be specified,
+ // i.e. bytes 15 and 16 are 0, we send a program change because
+ // 0 is a valid program number. MI2 tests show that in such
+ // cases, a regular program change message always seems to follow
+ // anyway.
+ if (_isMIDI)
+ part->_instrument.program((p[15] & 0x0F) << 4 |(p[16] & 0x0F), _isMT32);
+ part->sendAll();
+ }
+ }
+ } else {
+ // Sam & Max: Trigger Event
+ // Triggers are set by doCommand(ImSetTrigger).
+ // When a SysEx marker is encountered whose sound
+ // ID and marker ID match what was set by ImSetTrigger,
+ // something magical is supposed to happen....
+ for (a = 0; a < ARRAYSIZE(_se->_snm_triggers); ++a) {
+ if (_se->_snm_triggers[a].sound == _id &&
+ _se->_snm_triggers[a].id == *p)
+ {
+ _se->_snm_triggers[a].sound = _se->_snm_triggers[a].id = 0;
+ _se->doCommand(8, _se->_snm_triggers[a].command);
+ break;
+ }
+ }
+ } // end if
+ break;
+
+ case 1:
+ // This SysEx is used in Sam & Max for maybe_jump.
+ if (_scanning)
+ break;
+ maybe_jump(p[0], p[1] - 1, (READ_BE_UINT16(p + 2) - 1) * 4 + p[4], ((p[5] * TICKS_PER_BEAT) >> 2) + p[6]);
+ break;
+
+ case 2: // Start of song. Ignore for now.
+ break;
+
+ case 16: // Adlib instrument definition(Part)
+ a = *p++ & 0x0F;
+ ++p; // Skip hardware type
+ part = getPart(a);
+ if (part) {
+ if (len == 63) {
+ decode_sysex_bytes(p, buf, len - 3);
+ part->set_instrument((byte *)buf);
+ } else {
+ // SPK tracks have len == 49 here, and are not supported
+ part->programChange(254); // Must be invalid, but not 255 (which is reserved)
+ }
+ }
+ break;
+
+ case 17: // Adlib instrument definition(Global)
+ p += 2; // Skip hardware type and... whatever came right before it
+ a = *p++;
+ decode_sysex_bytes(p, buf, len - 4);
+ _se->setGlobalAdlibInstrument(a, buf);
+ break;
+
+ case 33: // Parameter adjust
+ a = *p++ & 0x0F;
+ ++p; // Skip hardware type
+ decode_sysex_bytes(p, buf, len - 3);
+ part = getPart(a);
+ if (part)
+ part->set_param(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2));
+ break;
+
+ case 48: // Hook - jump
+ if (_scanning)
+ break;
+ decode_sysex_bytes(p + 1, buf, len - 2);
+ maybe_jump(buf[0], READ_BE_UINT16(buf + 1), READ_BE_UINT16(buf + 3), READ_BE_UINT16(buf + 5));
+ break;
+
+ case 49: // Hook - global transpose
+ decode_sysex_bytes(p + 1, buf, len - 2);
+ maybe_set_transpose(buf);
+ break;
+
+ case 50: // Hook - part on/off
+ buf[0] = *p++ & 0x0F;
+ decode_sysex_bytes(p, buf + 1, len - 2);
+ maybe_part_onoff(buf);
+ break;
+
+ case 51: // Hook - set volume
+ buf[0] = *p++ & 0x0F;
+ decode_sysex_bytes(p, buf + 1, len - 2);
+ maybe_set_volume(buf);
+ break;
+
+ case 52: // Hook - set program
+ buf[0] = *p++ & 0x0F;
+ decode_sysex_bytes(p, buf + 1, len - 2);
+ maybe_set_program(buf);
+ break;
+
+ case 53: // Hook - set transpose
+ buf[0] = *p++ & 0x0F;
+ decode_sysex_bytes(p, buf + 1, len - 2);
+ maybe_set_transpose_part(buf);
+ break;
+
+ case 64: // Marker
+ p++;
+ len -= 2;
+ while (len--) {
+ _se->handle_marker(_id, *p++);
+ }
+ break;
+
+ case 80: // Loop
+ decode_sysex_bytes(p + 1, buf, len - 2);
+ setLoop(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2),
+ READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6),
+ READ_BE_UINT16(buf + 8));
+ break;
+
+ case 81: // End loop
+ clearLoop();
+ break;
+
+ case 96: // Set instrument
+ part = getPart(p[0] & 0x0F);
+ b = (p[1] & 0x0F) << 12 |(p[2] & 0x0F) << 8 |(p[4] & 0x0F) << 4 |(p[4] & 0x0F);
+ if (part)
+ part->set_instrument(b);
+ break;
+
+ default:
+ error("Unknown SysEx command %d", (int)code);
+ }
+}
+
+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, -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);
+ }
+}
+
+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\n", 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:
+ 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() {
+ 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);
+ }
+}
+
+uint32 Player::getBaseTempo() {
+ return (_midi ? _midi->getBaseTempo() : 0);
+}
+
+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),
+ sizeof(ParameterFader), parameterFaderEntries);
+ return;
+}
+
+} // End of namespace Scumm