Raw File
player_v5m.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/>.
 *
 */

/*
	From Markus Magnuson (superqult) we got this information:
	Mac0
	---
	   4 bytes - 'SOUN'
	BE 4 bytes - block length

		   4 bytes  - 'Mac0'
		BE 4 bytes  - (blockLength - 27)
		   28 bytes - ???

		   do this three times (once for each channel):
			  4 bytes  - 'Chan'
		   BE 4 bytes  - channel length
			  4 bytes  - instrument name (e.g. 'MARI')

			  do this for ((chanLength-24)/4) times:
				 2 bytes  - note duration
				 1 byte   - note value
				 1 byte   - note velocity

			  4 bytes - ???
			  4 bytes - 'Loop'/'Done'
			  4 bytes - ???

	   1 byte - 0x09
	---

	The instruments presumably correspond to the snd resource names in the
	Monkey Island executable:

	Instruments
	"MARI" - MARIMBA
	"PLUC" - PLUCK
	"HARM" - HARMONIC
	"PIPE" - PIPEORGAN
	"TROM" - TROMBONE
	"STRI" - STRINGS
	"HORN" - HORN
	"VIBE" - VIBES
	"SHAK" - SHAKUHACHI
	"PANP" - PANPIPE
	"WHIS" - WHISTLE
	"ORGA" - ORGAN3
	"BONG" - BONGO
	"BASS" - BASS

	---

	Note values <= 1 are silent.
*/

#include "common/macresman.h"
#include "common/translation.h"
#include "engines/engine.h"
#include "gui/message.h"
#include "scumm/players/player_v5m.h"
#include "scumm/scumm.h"

namespace Scumm {

Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
	: Player_Mac(scumm, mixer, 3, 0x07, false) {
	assert(_vm->_game.id == GID_MONKEY);
}

bool Player_V5M::loadMusic(const byte *ptr) {
	Common::MacResManager resource;
	if (!resource.open(_instrumentFile)) {
		return false;
	}

	ptr += 8;
	// TODO: Decipher the unknown bytes in the header. For now, skip 'em
	ptr += 28;

	Common::MacResIDArray idArray = resource.getResIDArray(RES_SND);

	// Load the three channels and their instruments
	for (int i = 0; i < 3; i++) {
		assert(READ_BE_UINT32(ptr) == MKTAG('C', 'h', 'a', 'n'));
		uint32 len = READ_BE_UINT32(ptr + 4);
		uint32 instrument = READ_BE_UINT32(ptr + 8);

		_channel[i]._length = len - 20;
		_channel[i]._data = ptr + 12;
		_channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p'));
		_channel[i]._pos = 0;
		_channel[i]._pitchModifier = 0;
		_channel[i]._velocity = 0;
		_channel[i]._remaining = 0;
		_channel[i]._notesLeft = true;

		for (uint j = 0; j < idArray.size(); j++) {
			Common::String name = resource.getResName(RES_SND, idArray[j]);
			if (instrument == READ_BE_UINT32(name.c_str())) {
				debug(6, "Player_V5M::loadMusic: Channel %d: Loading instrument '%s'", i, name.c_str());
				Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]);

				if (!_channel[i].loadInstrument(stream)) {
					resource.close();
					delete stream;
					return false;
				}

				delete stream;
				break;
			}
		}

		ptr += len;
	}

	resource.close();

	// The last note of each channel is just zeroes. We will adjust this
	// note so that all the channels end at the same time.

	uint32 samples[3];
	uint32 maxSamples = 0;
	for (int i = 0; i < 3; i++) {
		samples[i] = 0;
		for (uint j = 0; j < _channel[i]._length; j += 4) {
			samples[i] += durationToSamples(READ_BE_UINT16(&_channel[i]._data[j]));
		}
		if (samples[i] > maxSamples) {
			maxSamples = samples[i];
		}
	}

	for (int i = 0; i < 3; i++) {
		_lastNoteSamples[i] = maxSamples - samples[i];
	}

	return true;
}

bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
	if (_channel[ch]._pos >= _channel[ch]._length) {
		if (!_channel[ch]._looped) {
			_channel[ch]._notesLeft = false;
			return false;
		}
		// FIXME: Jamieson630: The jump seems to be happening
		// too quickly! There should maybe be a pause after
		// the last Note Off? But I couldn't find one in the
		// MI1 Lookout music, where I was hearing problems.
		_channel[ch]._pos = 0;
	}
	uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
	byte note = _channel[ch]._data[_channel[ch]._pos + 2];
	samples = durationToSamples(duration);

	if (note != 1) {
		_channel[ch]._instrument.newNote();
	}

	if (note > 1) {
		pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
		velocity = _channel[ch]._data[_channel[ch]._pos + 3];
	} else if (note == 1) {
		// This is guesswork, but Monkey Island uses two different
		// "special" note values: 0, which is clearly a rest, and 1
		// which is... I thought at first it was a "soft" key off, to
		// fade out the note, but listening to the music in a Mac
		// emulator (which unfortunately doesn't work all that well),
		// I hear no trace of fading out.
		//
		// It could mean "change the volume on the current note", but
		// I can't hear that either, and it always seems to use the
		// exact same velocity on this note.
		//
		// So it appears it really just is a "hold the current note",
		// but why? Couldn't they just have made the original note
		// longer?

		pitchModifier = _channel[ch]._pitchModifier;
		velocity = _channel[ch]._velocity;
	} else {
		pitchModifier = 0;
		velocity = 0;
	}

	_channel[ch]._pos += 4;

	if (_channel[ch]._pos >= _channel[ch]._length) {
		samples = _lastNoteSamples[ch];
	}
	return true;
}

} // End of namespace Scumm
back to top