https://github.com/scummvm/scummvm
Raw File
Tip revision: f7e4d1fe80cb7b9e2374f511d3bac626cb806d30 authored by Eugene Sandulenko on 16 December 2023, 21:08:45 UTC
DISTS: Generated Code::Blocks and MSVC project files
Tip revision: f7e4d1f
imuse_player.cpp
/* 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 <http://www.gnu.org/licenses/>.
 *
 */



#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) {
}

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 {
	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;
	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;
}

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);
	setSpeed(reset_vars ? (ptr ? (READ_BE_UINT32(&ptr[4]) && ptr[15] ? ptr[15] : 128) : 128) : _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) {
	_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)) != 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) * 2)
		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) {
			buf[a * 3] = buf[a * 3 + 1] = buf[a * 3 + 2] = '.';
			++a;
		}
		buf[a * 3] = '\0';
		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 && ((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;

	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:
		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;
			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 = 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, 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));
	s.syncAsSint16LE(pf.start, VER(17));
	s.syncAsSint16LE(pf.end, VER(17));
	s.syncAsUint32LE(pf.total_time, VER(17));
	s.syncAsUint32LE(pf.current_time, VER(17));
}

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));
	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);
}

} // End of namespace Scumm
back to top