[Scummvm-cvs-logs] scummvm master -> 97d7bf9b9aaa1aac4939d2a690f65fde7e844b63

lordhoto lordhoto at gmail.com
Fri Dec 14 20:59:02 CET 2012


This automated email contains information about 27 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
8bb595453b SCUMM: Added support for Macintosh music in Monkey Island 1
107a1af125 SCUMM: Initialize the Macintosh MI1 instruments, along with the channels.
b1d10e6a62 SCUMM: Add support for Mac Loom music and sound
148a6d3347 SCUMM: Move most of the Macintosh player code into its own class
493644295d SCUMM: Remove unnecessary check for Mac Loom
0b63ea1d8d SCUMM: Hopefully fix warning
b02ecf7a8a SCUMM: Try harder to open the Loom Macintosh executable.
0dcd4ba5a7 SCUMM: Move Mac player initialization to its own function
b75349383e SCUMM: Remove Mac version of MI1 from special case.
f784d683e0 SCUMM: Save/load Mac music engine state for Loom and MI1
f0c1d8dcc4 SCUMM: Add hack to preserve savegame compatibility with Mac MI1
b6a42e9faa SCUMM: Store sample rate in Mac MI1 / Loom savegames
4626e21149 SCUMM: Remove commented out code.
ee65532a5e SCUMM: Avoid "pops" at the end of the note in Mac MI1/Loom music
f3c9b21806 SCUMM: Fix whitespace
4f18a92f5a SCUMM: Prevent music channels from drifting out of sync in Mac MI1
34a8b5049e SCUMM: Use more correct (I think) way of converting samples
ae823b5c6a SCUMM: Fix regression that caused "pops" in MI1 jungle music
94b0881427 SCUMM: Lock the sound resource while the music is playing
cb21d7309e SCUMM: Add Windows encoding of the Mac Loom filename
9e995991e7 SCUMM: Fix crash when Macintosh instruments aren't available
fc0288e9d5 SCUMM: Simplify checkMusicAvailable() a bit
2aa8d0d65d SCUMM: Try harder to find Mac Monkey Island instruments
d06f69f94b SCUMM: Fix memory leak when loading old MI1 Mac savegames
076bcbc5c6 SCUMM: Added TODO comment about Mac MI1 music
d3cf4d10f2 SCUMM: Handle note value 1 as "hold current note" in MI1 Mac
97d7bf9b9a Merge pull request #291 from eriktorbjorn/mac-mi-music-final


Commit: 8bb595453b7bce6e219acc94c105a1f76e5ff734
    https://github.com/scummvm/scummvm/commit/8bb595453b7bce6e219acc94c105a1f76e5ff734
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-11T04:56:06-08:00

Commit Message:
SCUMM: Added support for Macintosh music in Monkey Island 1

This is based on the old Mac0-to-General MIDI conversion that we used
to do (and which this patch removes), as well as the code for playing
the Monkey Island 2 and Fate of Atlantis Macintosh music. I'm not sure
how accurate it is, particularly in tempo and volume, but at this
point it seems to work pretty well. Looping music is perhaps a bit
off, but it was before as well.

There is an annoying drawn out note in the music when you're following
the shopkeeper, but that appears to have been there in the original as
well.

Changed paths:
  A engines/scumm/player_v5m.cpp
  A engines/scumm/player_v5m.h
    engines/scumm/module.mk
    engines/scumm/scumm.cpp
    engines/scumm/sound.cpp



diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 8499c9b..474d517 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -48,6 +48,7 @@ MODULE_OBJS := \
 	player_v2cms.o \
 	player_v3a.o \
 	player_v4a.o \
+	player_v5m.o \
 	resource_v2.o \
 	resource_v3.o \
 	resource_v4.o \
diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp
new file mode 100644
index 0000000..2ad0d10
--- /dev/null
+++ b/engines/scumm/player_v5m.cpp
@@ -0,0 +1,425 @@
+/* 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.
+ *
+ */
+
+/*
+	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/player_v5m.h"
+#include "scumm/scumm.h"
+
+#define RES_SND MKTAG('s', 'n', 'd', ' ')
+
+namespace Scumm {
+
+Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
+	: _vm(scumm),
+	  _mixer(mixer),
+	  _sampleRate(_mixer->getOutputRate()),
+	  _soundPlaying(-1) {
+
+	assert(scumm);
+	assert(mixer);
+	assert(_vm->_game.id == GID_MONKEY);
+
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		_channel[i]._looped = false;
+		_channel[i]._length = 0;
+		_channel[i]._data = NULL;
+		_channel[i]._pos = 0;
+		_channel[i]._pitchModifier = 0;
+		_channel[i]._velocity = 0;
+		_channel[i]._remaining = 0;
+		_channel[i]._notesLeft = false;
+	}
+
+	_pitchTable[116] = 1664510;
+	_pitchTable[117] = 1763487;
+	_pitchTable[118] = 1868350;
+	_pitchTable[119] = 1979447;
+	_pitchTable[120] = 2097152;
+	_pitchTable[121] = 2221855;
+	_pitchTable[122] = 2353973;
+	_pitchTable[123] = 2493948;
+	_pitchTable[124] = 2642246;
+	_pitchTable[125] = 2799362;
+	_pitchTable[126] = 2965820;
+	_pitchTable[127] = 3142177;
+	for (i = 115; i >= 0; --i) {
+		_pitchTable[i] = _pitchTable[i + 12] / 2;
+	}
+
+	setMusicVolume(255);
+
+	Common::MacResManager resource;
+	if (!resource.exists("Monkey Island")) {
+		GUI::MessageDialog dialog(_(
+			"Could not find the 'Monkey Island' Macintosh executable to read the\n"
+			"instruments from. Music will be disabled."), _("OK"));
+		dialog.runModal();
+		return;
+	}
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_V5M::~Player_V5M() {
+	Common::StackLock lock(_mutex);
+	_mixer->stopHandle(_soundHandle);
+	stopAllSounds_Internal();
+}
+
+void Player_V5M::setMusicVolume(int vol) {
+	debug(5, "Player_V5M::setMusicVolume(%d)", vol);
+}
+
+void Player_V5M::stopAllSounds_Internal() {
+	_soundPlaying = -1;
+	for (int i = 0; i < 3; i++) {
+		// The channel data is managed by the resource manager, so
+		// don't delete that.
+		delete[] _channel[i]._instrument._data;
+		_channel[i]._instrument._data = NULL;
+
+		_channel[i]._remaining = 0;
+		_channel[i]._notesLeft = false;
+	}
+}
+
+void Player_V5M::stopAllSounds() {
+	Common::StackLock lock(_mutex);
+	debug(5, "Player_V5M::stopAllSounds()");
+	stopAllSounds_Internal();
+}
+
+void Player_V5M::stopSound(int nr) {
+	Common::StackLock lock(_mutex);
+	debug(5, "Player_V5M::stopSound(%d)", nr);
+
+	if (nr == _soundPlaying) {
+		stopAllSounds();
+	}
+}
+
+void Player_V5M::startSound(int nr) {
+	Common::StackLock lock(_mutex);
+	debug(5, "Player_V5M::startSound(%d)", nr);
+
+	Common::MacResManager resource;
+	if (!resource.open("Monkey Island")) {
+		return;
+	}
+
+	const byte *ptr = _vm->getResourceAddress(rtSound, nr);
+	assert(ptr);
+
+	const byte *src = ptr;
+	uint i;
+
+	src += 8;
+	// TODO: Decipher the unknown bytes in the header. For now, skip 'em
+	src += 28;
+
+	Common::MacResIDArray idArray = resource.getResIDArray(RES_SND);
+
+	// Load the three channels and their instruments
+	for (i = 0; i < 3; i++) {
+		assert(READ_BE_UINT32(src) == MKTAG('C', 'h', 'a', 'n'));
+		uint32 len = READ_BE_UINT32(src + 4);
+		uint32 instrument = READ_BE_UINT32(src + 8);
+
+		_channel[i]._length = len - 24;
+		_channel[i]._data = src + 12;
+		_channel[i]._looped = (READ_BE_UINT32(src + 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, "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();
+					return;
+				}
+
+				break;
+			}
+		}
+
+		src += len;
+	}
+
+	resource.close();
+	_soundPlaying = nr;
+}
+
+bool Player_V5M::Channel::loadInstrument(Common::SeekableReadStream *stream) {
+	// Load the sound
+	uint16 soundType = stream->readUint16BE();
+	if (soundType != 1) {
+		warning("Player_V5M::loadInstrument: Unsupported sound type %d", soundType);
+		return false;
+	}
+	uint16 typeCount = stream->readUint16BE();
+	if (typeCount != 1) {
+		warning("Player_V5M::loadInstrument: Unsupported data type count %d", typeCount);
+		return false;
+	}
+	uint16 dataType = stream->readUint16BE();
+	if (dataType != 5) {
+		warning("Player_V5M::loadInstrument: Unsupported data type %d", dataType);
+		return false;
+	}
+
+	stream->readUint32BE();	// initialization option
+
+	uint16 cmdCount = stream->readUint16BE();
+	if (cmdCount != 1) {
+		warning("Player_V5M::loadInstrument: Unsupported command count %d", cmdCount);
+		return false;
+	}
+	uint16 command = stream->readUint16BE();
+	if (command != 0x8050 && command != 0x8051) {
+		warning("Player_V5M::loadInstrument: Unsupported command 0x%04X", command);
+		return false;
+	}
+
+	stream->readUint16BE(); // 0
+	uint32 soundHeaderOffset = stream->readUint32BE();
+
+	stream->seek(soundHeaderOffset);
+
+	uint32 soundDataOffset = stream->readUint32BE();
+	uint32 size = stream->readUint32BE();
+	uint32 rate = stream->readUint32BE() >> 16;
+	uint32 loopStart = stream->readUint32BE();
+	uint32 loopEnd = stream->readUint32BE();
+	byte encoding = stream->readByte();
+	byte baseFreq = stream->readByte();
+
+	if (encoding != 0) {
+		warning("Player_V5M::loadInstrument: Unsupported encoding %d", encoding);
+		return false;
+	}
+
+	stream->skip(soundDataOffset);
+
+	byte *data = new byte[size];
+	stream->read(data, size);
+
+	_instrument._data = data;
+	_instrument._size = size;
+	_instrument._rate = rate;
+	_instrument._loopStart = loopStart;
+	_instrument._loopEnd = loopEnd;
+	_instrument._baseFreq = baseFreq;
+
+	return true;
+}
+
+int Player_V5M::getMusicTimer() {
+	return 0;
+}
+
+int Player_V5M::getSoundStatus(int nr) const {
+	return _soundPlaying == nr;
+}
+
+int Player_V5M::readBuffer(int16 *data, const int numSamples) {
+	Common::StackLock lock(_mutex);
+
+	memset(data, 0, numSamples * 2);
+	if (_soundPlaying == -1) {
+		return numSamples;
+	}
+
+	bool notesLeft = false;
+
+	for (int i = 0; i < 3; i++) {
+		uint samplesLeft = numSamples;
+		int16 *ptr = data;
+
+		while (samplesLeft > 0) {
+			int generated;
+			if (_channel[i]._remaining == 0) {
+				uint16 duration;
+				byte note, velocity;
+				if (_channel[i].getNextNote(duration, note, velocity)) {
+					if (note > 1) {
+						const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq;
+						assert(pitchIdx >= 0);
+						// I don't want to use floating-point arithmetics here,
+						// but I ran into overflow problems with the church
+						// music. It's only once per note, so it should be ok.
+						double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
+						_channel[i]._pitchModifier = mult * _pitchTable[pitchIdx];
+						_channel[i]._velocity = velocity;
+					} else {
+						_channel[i]._pitchModifier = 0;
+						_channel[i]._velocity = 0;
+					}
+
+					// The correct formula should be:
+					//
+					// (duration * 473 * _sampleRate) / (4 * 480 * 480)
+					//
+					// But that's likely to cause integer overflow, so
+					// we do it in two steps and hope that the rounding
+					// error won't be noticeable.
+					//
+					// The original code is a bit unclear on if it should
+					// be 473 or 437, but since the comments indicated
+					// 473 I'm assuming 437 was a typo.
+					_channel[i]._remaining = (duration * _sampleRate) / (4 * 480);
+					_channel[i]._remaining = (_channel[i]._remaining * 473) / 480;
+				} else {
+					_channel[i]._pitchModifier = 0;
+					_channel[i]._velocity = 0;
+					_channel[i]._remaining = samplesLeft;
+				}
+			}
+			generated = MIN(_channel[i]._remaining, samplesLeft);
+			if (_channel[i]._velocity != 0) {
+				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated);
+			}
+			ptr += generated;
+			samplesLeft -= generated;
+			_channel[i]._remaining -= generated;
+		}
+
+		if (_channel[i]._notesLeft) {
+			notesLeft = true;
+		}
+	}
+
+	if (!notesLeft) {
+		stopAllSounds_Internal();
+	}
+
+	return numSamples;
+}
+
+bool Player_V5M::Channel::getNextNote(uint16 &duration, byte &note, byte &velocity) {
+	_instrument.newNote();
+	if (_pos >= _length) {
+		if (!_looped) {
+			_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.
+		_pos = 0;
+	}
+	duration = READ_BE_UINT16(&_data[_pos]);
+	note = _data[_pos + 2];
+	velocity = _data[_pos + 3];
+	_pos += 4;
+	return true;
+}
+
+void Player_V5M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) {
+	int samplesLeft = numSamples;
+	while (samplesLeft) {
+		_subPos += pitchModifier;
+		while (_subPos >= 0x10000) {
+			_subPos -= 0x10000;
+			_pos++;
+			if (_pos >= _loopEnd) {
+				_pos = _loopStart;
+			}
+		}
+
+		int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255;
+		if (sample > 32767) {
+			sample = 32767;
+		} else if (sample < -32768) {
+			sample = -32768;
+		}
+
+		*data++ = sample; // (_data[_pos] * 127) / 100;
+		samplesLeft--;
+	}
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player_v5m.h b/engines/scumm/player_v5m.h
new file mode 100644
index 0000000..c245554
--- /dev/null
+++ b/engines/scumm/player_v5m.h
@@ -0,0 +1,113 @@
+/* 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.
+ *
+ */
+
+#ifndef SCUMM_PLAYER_V5M_H
+#define SCUMM_PLAYER_V5M_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm V5 Macintosh music driver.
+ */
+ class Player_V5M : public Audio::AudioStream, public MusicEngine {
+public:
+	Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer);
+	virtual ~Player_V5M();
+
+	// MusicEngine API
+	virtual void setMusicVolume(int vol);
+	virtual void startSound(int sound);
+	virtual void stopSound(int sound);
+	virtual void stopAllSounds();
+	virtual int  getMusicTimer();
+	virtual int  getSoundStatus(int sound) const;
+
+	// AudioStream API
+	virtual int readBuffer(int16 *buffer, const int numSamples);
+	virtual bool isStereo() const { return false; }
+	virtual bool endOfData() const { return false; }
+	virtual int getRate() const { return _sampleRate; }
+
+private:
+	ScummEngine *const _vm;
+	Common::Mutex _mutex;
+	Audio::Mixer *const _mixer;
+	Audio::SoundHandle _soundHandle;
+	const uint32 _sampleRate;
+	int _soundPlaying;
+
+	void stopAllSounds_Internal();
+
+	struct Instrument {
+		byte *_data;
+		uint32 _size;
+		uint32 _rate;
+		uint32 _loopStart;
+		uint32 _loopEnd;
+		byte _baseFreq;
+
+		uint _pos;
+		uint _subPos;
+
+		void newNote() {
+			_pos = 0;
+			_subPos = 0;
+		}
+
+		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples);
+	};
+
+	struct Channel {
+		Instrument _instrument;
+		bool _looped;
+		uint32 _length;
+		const byte *_data;
+
+		uint _pos;
+		int _pitchModifier;
+		byte _velocity;
+		uint32 _remaining;
+
+		bool _notesLeft;
+
+		bool loadInstrument(Common::SeekableReadStream *stream);
+		bool getNextNote(uint16 &duration, byte &value, byte &velocity);
+ 	};
+
+	Channel _channel[3];
+	int _pitchTable[128];
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 2c79fb8..67ca071 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -62,6 +62,7 @@
 #include "scumm/player_v2a.h"
 #include "scumm/player_v3a.h"
 #include "scumm/player_v4a.h"
+#include "scumm/player_v5m.h"
 #include "scumm/resource.h"
 #include "scumm/he/resource_he.h"
 #include "scumm/scumm_v0.h"
@@ -1819,6 +1820,8 @@ void ScummEngine::setupMusic(int midi) {
 #endif
 	} else if (_game.platform == Common::kPlatformAmiga && _game.version <= 4) {
 		_musicEngine = new Player_V4A(this, _mixer);
+	} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) {
+		_musicEngine = new Player_V5M(this, _mixer);
 	} else if (_game.id == GID_MANIAC && _game.version == 1) {
 		_musicEngine = new Player_V1(this, _mixer, MidiDriver::getMusicType(dev) != MT_PCSPK);
 	} else if (_game.version <= 2) {
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index a1cecfa..5734512 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -1086,9 +1086,6 @@ void Sound::saveLoadWithSerializer(Serializer *ser) {
 #pragma mark --- Sound resource handling ---
 #pragma mark -
 
-static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size);
-
-
 /*
  * TODO: The way we handle sound/music resources really is one huge hack.
  * We probably should reconsider how we do this, and maybe come up with a
@@ -1208,11 +1205,9 @@ int ScummEngine::readSoundResource(ResId idx) {
 	case MKTAG('M','a','c','0'):
 		_fileHandle->seek(-12, SEEK_CUR);
 		total_size = _fileHandle->readUint32BE() - 8;
-		ptr = (byte *)calloc(total_size, 1);
+		ptr = _res->createResource(rtSound, idx, total_size);
 		_fileHandle->read(ptr, total_size);
 		//dumpResource("sound-", idx, ptr);
-		convertMac0Resource(_res, idx, ptr, total_size);
-		free(ptr);
 		return 1;
 
 	case MKTAG('M','a','c','1'):
@@ -1445,219 +1440,6 @@ static byte *writeVLQ(byte *ptr, int value) {
 	return ptr;
 }
 
-static byte Mac0ToGMInstrument(uint32 type, int &transpose) {
-	transpose = 0;
-	switch (type) {
-	case MKTAG('M','A','R','I'): return 12;
-	case MKTAG('P','L','U','C'): return 45;
-	case MKTAG('H','A','R','M'): return 22;
-	case MKTAG('P','I','P','E'): return 19;
-	case MKTAG('T','R','O','M'): transpose = -12; return 57;
-	case MKTAG('S','T','R','I'): return 48;
-	case MKTAG('H','O','R','N'): return 60;
-	case MKTAG('V','I','B','E'): return 11;
-	case MKTAG('S','H','A','K'): return 77;
-	case MKTAG('P','A','N','P'): return 75;
-	case MKTAG('W','H','I','S'): return 76;
-	case MKTAG('O','R','G','A'): return 17;
-	case MKTAG('B','O','N','G'): return 115;
-	case MKTAG('B','A','S','S'): transpose = -24; return 35;
-	default:
-		error("Unknown Mac0 instrument %s found", tag2str(type));
-	}
-}
-
-static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size) {
-	/*
-	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
-	---
-
-	Instruments (General Midi):
-	"MARI" - Marimba (12)
-	"PLUC" - Pizzicato Strings (45)
-	"HARM" - Harmonica (22)
-	"PIPE" - Church Organ? (19) or Flute? (73) or Bag Pipe (109)
-	"TROM" - Trombone (57)
-	"STRI" - String Ensemble (48 or 49)
-	"HORN" - French Horn? (60) or English Horn? (69)
-	"VIBE" - Vibraphone (11)
-	"SHAK" - Shakuhachi? (77)
-	"PANP" - Pan Flute (75)
-	"WHIS" - Whistle (78) / Bottle (76)
-	"ORGA" - Drawbar Organ (16; but could also be 17-20)
-	"BONG" - Woodblock? (115)
-	"BASS" - Bass (32-39)
-
-
-	Now the task could be to convert this into MIDI, to be fed into iMuse.
-	Or we do something similiar to what is done in Player_V3, assuming
-	we can identify SFX in the MI datafiles for each of the instruments
-	listed above.
-	*/
-
-#if 0
-	byte *ptr = _res->createResource(rtSound, idx, size);
-	memcpy(ptr, src_ptr, size);
-#else
-	const int ppqn = 480;
-	byte *ptr, *start_ptr;
-
-	int total_size = 0;
-	total_size += kMIDIHeaderSize; // Header
-	total_size += 7;               // Tempo META
-	total_size += 3 * 3;           // Three program change mesages
-	total_size += 22;              // Possible jump SysEx
-	total_size += 5;               // EOT META
-
-	int i, len;
-	byte track_instr[3];
-	byte *track_data[3];
-	int track_len[3];
-	int track_transpose[3];
-	bool looped = false;
-
-	src_ptr += 8;
-	// TODO: Decipher the unknown bytes in the header. For now, skip 'em
-	src_ptr += 28;
-
-	// Parse the three channels
-	for (i = 0; i < 3; i++) {
-		assert(READ_BE_UINT32(src_ptr) == MKTAG('C','h','a','n'));
-		len = READ_BE_UINT32(src_ptr + 4);
-		track_len[i] = len - 24;
-		track_instr[i] = Mac0ToGMInstrument(READ_BE_UINT32(src_ptr + 8), track_transpose[i]);
-		track_data[i] = src_ptr + 12;
-		src_ptr += len;
-		looped = (READ_BE_UINT32(src_ptr - 8) == MKTAG('L','o','o','p'));
-
-		// For each note event, we need up to 6 bytes for the
-		// Note On (3 VLQ, 3 event), and 6 bytes for the Note
-		// Off (3 VLQ, 3 event). So 12 bytes total.
-		total_size += 12 * track_len[i];
-	}
-	assert(*src_ptr == 0x09);
-
-	// Create sound resource
-	start_ptr = res->createResource(rtSound, idx, total_size);
-
-	// Insert MIDI header
-	ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size);
-
-	// Write a tempo change Meta event
-	// 473 / 4 Hz, convert to micro seconds.
-	uint32 dw = 1000000 * 437 / 4 / ppqn; // 1000000 * ppqn * 4 / 473;
-	memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4;
-	*ptr++ = (byte)((dw >> 16) & 0xFF);
-	*ptr++ = (byte)((dw >> 8) & 0xFF);
-	*ptr++ = (byte)(dw & 0xFF);
-
-	// Insert program change messages
-	*ptr++ = 0; // VLQ
-	*ptr++ = 0xC0;
-	*ptr++ = track_instr[0];
-	*ptr++ = 0; // VLQ
-	*ptr++ = 0xC1;
-	*ptr++ = track_instr[1];
-	*ptr++ = 0; // VLQ
-	*ptr++ = 0xC2;
-	*ptr++ = track_instr[2];
-
-	// And now, the actual composition. Please turn all cell phones
-	// and pagers off during the performance. Thank you.
-	uint16 nextTime[3] = { 1, 1, 1 };
-	int stage[3] = { 0, 0, 0 };
-
-	while (track_len[0] | track_len[1] | track_len[2]) {
-		int best = -1;
-		uint16 bestTime = 0xFFFF;
-		for (i = 0; i < 3; ++i) {
-			if (track_len[i] && nextTime[i] < bestTime) {
-				bestTime = nextTime[i];
-				best = i;
-			}
-		}
-		assert (best != -1);
-
-		if (!stage[best]) {
-			// We are STARTING this event.
-			if (track_data[best][2] > 1) {
-				// Note On
-				ptr = writeVLQ(ptr, nextTime[best]);
-				*ptr++ = 0x90 | best;
-				*ptr++ = track_data[best][2] + track_transpose[best];
-				*ptr++ = track_data[best][3] * 127 / 100; // Scale velocity
-				for (i = 0; i < 3; ++i)
-					nextTime[i] -= bestTime;
-			}
-			nextTime[best] += READ_BE_UINT16 (track_data[best]);
-			stage[best] = 1;
-		} else {
-			// We are ENDING this event.
-			if (track_data[best][2] > 1) {
-				// There was a Note On, so do a Note Off
-				ptr = writeVLQ(ptr, nextTime[best]);
-				*ptr++ = 0x80 | best;
-				*ptr++ = track_data[best][2] + track_transpose[best];
-				*ptr++ = track_data[best][3] * 127 / 100; // Scale velocity
-				for (i = 0; i < 3; ++i)
-					nextTime[i] -= bestTime;
-			}
-			track_data[best] += 4;
-			track_len[best] -= 4;
-			stage[best] = 0;
-		}
-	}
-
-	// Is this a looped song? If so, effect a loop by
-	// using the S&M maybe_jump SysEx command.
-	// 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.
-	if (looped) {
-		memcpy(ptr, "\x00\xf0\x13\x7d\x30\00", 6); ptr += 6; // maybe_jump
-		memcpy(ptr, "\x00\x00", 2); ptr += 2;            // cmd -> 0 means always jump
-		memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4;    // track -> 0 (only track)
-		memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4;    // beat -> 1 (first beat)
-		memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4;    // tick -> 1
-		memcpy(ptr, "\x00\xf7", 2); ptr += 2;            // SysEx end marker
-	}
-
-	// Insert end of song META
-	memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5;
-
-	assert(ptr <= start_ptr + total_size);
-
-	// Rewrite MIDI header, this time with true size
-	total_size = ptr - start_ptr;
-	ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size);
-#endif
-}
-
 static void convertADResource(ResourceManager *res, const GameSettings& game, ResId idx, byte *src_ptr, int size) {
 	// We will ignore the PPQN in the original resource, because
 	// it's invalid anyway. We use a constant PPQN of 480.


Commit: 107a1af125cc77082cc205fcfa1b86c7509c26c5
    https://github.com/scummvm/scummvm/commit/107a1af125cc77082cc205fcfa1b86c7509c26c5
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-12T13:10:10-08:00

Commit Message:
SCUMM: Initialize the Macintosh MI1 instruments, along with the channels.

Otherwise it may crash if you quit before any instruments have been
loaded. Oops.

Changed paths:
    engines/scumm/player_v5m.cpp



diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp
index 2ad0d10..640361c 100644
--- a/engines/scumm/player_v5m.cpp
+++ b/engines/scumm/player_v5m.cpp
@@ -104,6 +104,14 @@ Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
 		_channel[i]._velocity = 0;
 		_channel[i]._remaining = 0;
 		_channel[i]._notesLeft = false;
+		_channel[i]._instrument._data = NULL;
+		_channel[i]._instrument._size = 0;
+		_channel[i]._instrument._rate = 0;
+		_channel[i]._instrument._loopStart = 0;
+		_channel[i]._instrument._loopEnd = 0;
+		_channel[i]._instrument._baseFreq = 0;
+		_channel[i]._instrument._pos = 0;
+		_channel[i]._instrument._subPos = 0;
 	}
 
 	_pitchTable[116] = 1664510;


Commit: b1d10e6a62ef49994e78b3e2c86b27050f2f0938
    https://github.com/scummvm/scummvm/commit/b1d10e6a62ef49994e78b3e2c86b27050f2f0938
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-13T13:49:12-08:00

Commit Message:
SCUMM: Add support for Mac Loom music and sound

It turns out that playing the Mac Loom music isn't particularly
different from playing the Monkey Island 1 music, except the data
layout is a bit different and there's no per-note volume.

Changed paths:
  A engines/scumm/player_v3m.cpp
  A engines/scumm/player_v3m.h
    engines/scumm/module.mk
    engines/scumm/scumm.cpp
    engines/scumm/sound.cpp



diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 474d517..57a495a 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -47,6 +47,7 @@ MODULE_OBJS := \
 	player_v2base.o \
 	player_v2cms.o \
 	player_v3a.o \
+	player_v3m.o \
 	player_v4a.o \
 	player_v5m.o \
 	resource_v2.o \
diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp
new file mode 100644
index 0000000..081f48f
--- /dev/null
+++ b/engines/scumm/player_v3m.cpp
@@ -0,0 +1,433 @@
+/* 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.
+ *
+ */
+
+/*
+	We have the following information from Lars Christensen (lechimp) and
+	Jamieson Christian (jamieson630):
+
+	RESOURCE DATA
+	LE 2 bytes Resource size
+	2 bytes Unknown
+	2 bytes 'so'
+	14 bytes Unknown
+	BE 2 bytes Instrument for Stream 1
+	BE 2 bytes Instrument for Stream 2
+	BE 2 bytes Instrument for Stream 3
+	BE 2 bytes Instrument for Stream 4
+	BE 2 bytes Instrument for Stream 5
+	BE 2 bytes Offset to Stream 1
+	BE 2 bytes Offset to Stream 2
+	BE 2 bytes Offset to Stream 3
+	BE 2 bytes Offset to Stream 4
+	BE 2 bytes Offset to Stream 5
+	? bytes The streams
+
+	STREAM DATA
+	BE 2 bytes Unknown (always 1?)
+	2 bytes Unknown (always 0?)
+	BE 2 bytes Number of events in stream
+	? bytes Stream data
+
+	Each stream event is exactly 3 bytes, therefore one can
+	assert that numEvents == (streamSize - 6) / 3. The
+	polyphony of a stream appears to be 1; in other words, only
+	one note at a time can be playing in each stream. The next
+	event is not executed until the current note (or rest) is
+	finished playing; therefore, note duration also serves as the
+	time delta between events.
+
+	FOR EACH EVENTS
+	BE 2 bytes Note duration
+	1 byte Note number to play (0 = rest/silent)
+
+	Oh, and quick speculation -- Stream 1 may be used for a
+	single-voice interleaved version of the music, where Stream 2-
+	5 represent a version of the music in up to 4-voice
+	polyphony, one voice per stream. I postulate thus because
+	the first stream of the Mac Loom theme music contains
+	interleaved voices, whereas the second stream seemed to
+	contain only the pizzicato bottom-end harp. Stream 5, in this
+	example, is empty, so if my speculation is correct, this
+	particular musical number supports 3-voice polyphony at
+	most. I must check out Streams 3 and 4 to see what they
+	contain.
+
+	==========
+
+	The instruments appear to be identified by their resource IDs:
+
+	1000	Dual Harp
+	10895	harp1
+	11445	strings1
+	11548	silent
+	13811	staff1
+	15703	brass1
+	16324	flute1
+	25614	accordian 1
+	28110	f horn1
+	29042	bassoon1
+*/
+
+#include "common/macresman.h"
+#include "common/translation.h"
+#include "engines/engine.h"
+#include "gui/message.h"
+#include "scumm/player_v3m.h"
+#include "scumm/scumm.h"
+
+#define RES_SND MKTAG('s', 'n', 'd', ' ')
+
+namespace Scumm {
+
+Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
+	: _vm(scumm),
+	  _mixer(mixer),
+	  _sampleRate(_mixer->getOutputRate()),
+	  _soundPlaying(-1) {
+
+	assert(scumm);
+	assert(mixer);
+	assert(_vm->_game.id == GID_LOOM);
+
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		_channel[i]._looped = false;
+		_channel[i]._length = 0;
+		_channel[i]._data = NULL;
+		_channel[i]._pos = 0;
+		_channel[i]._pitchModifier = 0;
+		_channel[i]._velocity = 0;
+		_channel[i]._remaining = 0;
+		_channel[i]._notesLeft = false;
+		_channel[i]._instrument._data = NULL;
+		_channel[i]._instrument._size = 0;
+		_channel[i]._instrument._rate = 0;
+		_channel[i]._instrument._loopStart = 0;
+		_channel[i]._instrument._loopEnd = 0;
+		_channel[i]._instrument._baseFreq = 0;
+		_channel[i]._instrument._pos = 0;
+		_channel[i]._instrument._subPos = 0;
+	}
+
+	_pitchTable[116] = 1664510;
+	_pitchTable[117] = 1763487;
+	_pitchTable[118] = 1868350;
+	_pitchTable[119] = 1979447;
+	_pitchTable[120] = 2097152;
+	_pitchTable[121] = 2221855;
+	_pitchTable[122] = 2353973;
+	_pitchTable[123] = 2493948;
+	_pitchTable[124] = 2642246;
+	_pitchTable[125] = 2799362;
+	_pitchTable[126] = 2965820;
+	_pitchTable[127] = 3142177;
+	for (i = 115; i >= 0; --i) {
+		_pitchTable[i] = _pitchTable[i + 12] / 2;
+	}
+
+	setMusicVolume(255);
+
+	Common::MacResManager resource;
+	// \252 is a trademark glyph. I don't think we should assume that it
+	// will be preserved when copying from the Mac disk.
+	if (!resource.exists("Loom\252") && !resource.exists("Loom")) {
+		GUI::MessageDialog dialog(_(
+			"Could not find the 'Loom' Macintosh executable to read the\n"
+			"instruments from. Music will be disabled."), _("OK"));
+		dialog.runModal();
+		return;
+	}
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_V3M::~Player_V3M() {
+	Common::StackLock lock(_mutex);
+	_mixer->stopHandle(_soundHandle);
+	stopAllSounds_Internal();
+}
+
+void Player_V3M::setMusicVolume(int vol) {
+	debug(5, "Player_V3M::setMusicVolume(%d)", vol);
+}
+
+void Player_V3M::stopAllSounds_Internal() {
+	_soundPlaying = -1;
+	for (int i = 0; i < 3; i++) {
+		// The channel data is managed by the resource manager, so
+		// don't delete that.
+		delete[] _channel[i]._instrument._data;
+		_channel[i]._instrument._data = NULL;
+
+		_channel[i]._remaining = 0;
+		_channel[i]._notesLeft = false;
+	}
+}
+
+void Player_V3M::stopAllSounds() {
+	Common::StackLock lock(_mutex);
+	debug(5, "Player_V3M::stopAllSounds()");
+	stopAllSounds_Internal();
+}
+
+void Player_V3M::stopSound(int nr) {
+	Common::StackLock lock(_mutex);
+	debug(5, "Player_V3M::stopSound(%d)", nr);
+
+	if (nr == _soundPlaying) {
+		stopAllSounds();
+	}
+}
+
+void Player_V3M::startSound(int nr) {
+	Common::StackLock lock(_mutex);
+	debug(5, "Player_V3M::startSound(%d)", nr);
+
+	Common::MacResManager resource;
+	if (!resource.open("Loom\252") && !resource.open("Loom")) {
+		return;
+	}
+
+	const byte *ptr = _vm->getResourceAddress(rtSound, nr);
+	assert(ptr);
+
+	const byte *src = ptr;
+	uint i;
+
+	assert(src[4] == 's' && src[5] == 'o');
+	for (i = 0; i < 5; i++) {
+		int instrument = READ_BE_UINT16(src + 20 + 2 * i);
+		int offset = READ_BE_UINT16(src + 30 + 2 * i);
+		_channel[i]._looped = false;
+		_channel[i]._length = READ_BE_UINT16(src + offset + 4) * 3;
+		_channel[i]._data = src + offset + 6;
+		_channel[i]._pos = 0;
+		_channel[i]._pitchModifier = 0;
+		_channel[i]._velocity = 0;
+		_channel[i]._remaining = 0;
+		_channel[i]._notesLeft = true;
+
+		Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument);
+		if (_channel[i].loadInstrument(stream)) {
+			debug(6, "Player_V3M::startSound: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str());
+		} else {
+			resource.close();
+			return;
+		}
+	}
+
+	resource.close();
+	_soundPlaying = nr;
+}
+
+bool Player_V3M::Channel::loadInstrument(Common::SeekableReadStream *stream) {
+	// Load the sound
+	uint16 soundType = stream->readUint16BE();
+	if (soundType != 1) {
+		warning("Player_V3M::loadInstrument: Unsupported sound type %d", soundType);
+		return false;
+	}
+	uint16 typeCount = stream->readUint16BE();
+	if (typeCount != 1) {
+		warning("Player_V3M::loadInstrument: Unsupported data type count %d", typeCount);
+		return false;
+	}
+	uint16 dataType = stream->readUint16BE();
+	if (dataType != 5) {
+		warning("Player_V3M::loadInstrument: Unsupported data type %d", dataType);
+		return false;
+	}
+
+	stream->readUint32BE();	// initialization option
+
+	uint16 cmdCount = stream->readUint16BE();
+	if (cmdCount != 1) {
+		warning("Player_V3M::loadInstrument: Unsupported command count %d", cmdCount);
+		return false;
+	}
+	uint16 command = stream->readUint16BE();
+	if (command != 0x8050 && command != 0x8051) {
+		warning("Player_V3M::loadInstrument: Unsupported command 0x%04X", command);
+		return false;
+	}
+
+	stream->readUint16BE(); // 0
+	uint32 soundHeaderOffset = stream->readUint32BE();
+
+	stream->seek(soundHeaderOffset);
+
+	uint32 soundDataOffset = stream->readUint32BE();
+	uint32 size = stream->readUint32BE();
+	uint32 rate = stream->readUint32BE() >> 16;
+	uint32 loopStart = stream->readUint32BE();
+	uint32 loopEnd = stream->readUint32BE();
+	byte encoding = stream->readByte();
+	byte baseFreq = stream->readByte();
+
+	if (encoding != 0) {
+		warning("Player_V3M::loadInstrument: Unsupported encoding %d", encoding);
+		return false;
+	}
+
+	stream->skip(soundDataOffset);
+
+	byte *data = new byte[size];
+	stream->read(data, size);
+
+	_instrument._data = data;
+	_instrument._size = size;
+	_instrument._rate = rate;
+	_instrument._loopStart = loopStart;
+	_instrument._loopEnd = loopEnd;
+	_instrument._baseFreq = baseFreq;
+
+	return true;
+}
+
+int Player_V3M::getMusicTimer() {
+	return 0;
+}
+
+int Player_V3M::getSoundStatus(int nr) const {
+	return _soundPlaying == nr;
+}
+
+int Player_V3M::readBuffer(int16 *data, const int numSamples) {
+	Common::StackLock lock(_mutex);
+
+	memset(data, 0, numSamples * 2);
+	if (_soundPlaying == -1) {
+		return numSamples;
+	}
+
+	bool notesLeft = false;
+
+	// Channel 0 seems to be what was played on low-end macs, that couldn't
+	// handle multi-channel music and play the game at the same time. I'm
+	// not sure if stream 4 is ever used, but let's use it just in case.
+
+	for (int i = 1; i < 5; i++) {
+		uint samplesLeft = numSamples;
+		int16 *ptr = data;
+
+		while (samplesLeft > 0) {
+			int generated;
+			if (_channel[i]._remaining == 0) {
+				uint16 duration;
+				byte note, velocity;
+				if (_channel[i].getNextNote(duration, note, velocity)) {
+					if (note > 0) {
+						const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq;
+						assert(pitchIdx >= 0);
+						// I don't want to use floating-point arithmetics here,
+						// but I ran into overflow problems with the church
+						// music. It's only once per note, so it should be ok.
+						double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
+						_channel[i]._pitchModifier = mult * _pitchTable[pitchIdx];
+						_channel[i]._velocity = velocity;
+					} else {
+						_channel[i]._pitchModifier = 0;
+						_channel[i]._velocity = 0;
+					}
+
+					// The correct formula should be:
+					//
+					// (duration * 473 * _sampleRate) / (4 * 480 * 480)
+					//
+					// But that's likely to cause integer overflow, so
+					// we do it in two steps and hope that the rounding
+					// error won't be noticeable.
+					//
+					// The original code is a bit unclear on if it should
+					// be 473 or 437, but since the comments indicated
+					// 473 I'm assuming 437 was a typo.
+					_channel[i]._remaining = (duration * _sampleRate) / (4 * 480);
+					_channel[i]._remaining = (_channel[i]._remaining * 473) / 480;
+				} else {
+					_channel[i]._pitchModifier = 0;
+					_channel[i]._velocity = 0;
+					_channel[i]._remaining = samplesLeft;
+				}
+			}
+			generated = MIN(_channel[i]._remaining, samplesLeft);
+			if (_channel[i]._velocity != 0) {
+				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated);
+			}
+			ptr += generated;
+			samplesLeft -= generated;
+			_channel[i]._remaining -= generated;
+		}
+
+		if (_channel[i]._notesLeft) {
+			notesLeft = true;
+		}
+	}
+
+	if (!notesLeft) {
+		stopAllSounds_Internal();
+	}
+
+	return numSamples;
+}
+
+bool Player_V3M::Channel::getNextNote(uint16 &duration, byte &note, byte &velocity) {
+	_instrument.newNote();
+	if (_pos >= _length) {
+		if (!_looped) {
+			_notesLeft = false;
+			return false;
+		}
+		_pos = 0;
+	}
+	duration = READ_BE_UINT16(&_data[_pos]);
+	note = _data[_pos + 2];
+	velocity = 127;
+	_pos += 3;
+	return true;
+}
+
+void Player_V3M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) {
+	int samplesLeft = numSamples;
+	while (samplesLeft) {
+		_subPos += pitchModifier;
+		while (_subPos >= 0x10000) {
+			_subPos -= 0x10000;
+			_pos++;
+			if (_pos >= _loopEnd) {
+				_pos = _loopStart;
+			}
+		}
+
+		int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255;
+		if (sample > 32767) {
+			sample = 32767;
+		} else if (sample < -32768) {
+			sample = -32768;
+		}
+
+		*data++ = sample; // (_data[_pos] * 127) / 100;
+		samplesLeft--;
+	}
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h
new file mode 100644
index 0000000..3465621
--- /dev/null
+++ b/engines/scumm/player_v3m.h
@@ -0,0 +1,113 @@
+/* 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.
+ *
+ */
+
+#ifndef SCUMM_PLAYER_V3M_H
+#define SCUMM_PLAYER_V3M_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm V3 Macintosh music driver.
+ */
+ class Player_V3M : public Audio::AudioStream, public MusicEngine {
+public:
+	Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer);
+	virtual ~Player_V3M();
+
+	// MusicEngine API
+	virtual void setMusicVolume(int vol);
+	virtual void startSound(int sound);
+	virtual void stopSound(int sound);
+	virtual void stopAllSounds();
+	virtual int  getMusicTimer();
+	virtual int  getSoundStatus(int sound) const;
+
+	// AudioStream API
+	virtual int readBuffer(int16 *buffer, const int numSamples);
+	virtual bool isStereo() const { return false; }
+	virtual bool endOfData() const { return false; }
+	virtual int getRate() const { return _sampleRate; }
+
+private:
+	ScummEngine *const _vm;
+	Common::Mutex _mutex;
+	Audio::Mixer *const _mixer;
+	Audio::SoundHandle _soundHandle;
+	const uint32 _sampleRate;
+	int _soundPlaying;
+
+	void stopAllSounds_Internal();
+
+	struct Instrument {
+		byte *_data;
+		uint32 _size;
+		uint32 _rate;
+		uint32 _loopStart;
+		uint32 _loopEnd;
+		byte _baseFreq;
+
+		uint _pos;
+		uint _subPos;
+
+		void newNote() {
+			_pos = 0;
+			_subPos = 0;
+		}
+
+		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples);
+	};
+
+	struct Channel {
+		Instrument _instrument;
+		bool _looped;
+		uint32 _length;
+		const byte *_data;
+
+		uint _pos;
+		int _pitchModifier;
+		byte _velocity;
+		uint32 _remaining;
+
+		bool _notesLeft;
+
+		bool loadInstrument(Common::SeekableReadStream *stream);
+		bool getNextNote(uint16 &duration, byte &value, byte &velocity);
+ 	};
+
+	Channel _channel[5];
+	int _pitchTable[128];
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 67ca071..4ca4df4 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -61,6 +61,7 @@
 #include "scumm/player_v2cms.h"
 #include "scumm/player_v2a.h"
 #include "scumm/player_v3a.h"
+#include "scumm/player_v3m.h"
 #include "scumm/player_v4a.h"
 #include "scumm/player_v5m.h"
 #include "scumm/resource.h"
@@ -1820,6 +1821,8 @@ void ScummEngine::setupMusic(int midi) {
 #endif
 	} else if (_game.platform == Common::kPlatformAmiga && _game.version <= 4) {
 		_musicEngine = new Player_V4A(this, _mixer);
+	} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_LOOM) {
+		_musicEngine = new Player_V3M(this, _mixer);
 	} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) {
 		_musicEngine = new Player_V5M(this, _mixer);
 	} else if (_game.id == GID_MANIAC && _game.version == 1) {
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index 5734512..d6de1cd 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -348,26 +348,9 @@ void Sound::playSound(int soundID) {
 	}
 	else if ((_vm->_game.id == GID_LOOM) && (_vm->_game.platform == Common::kPlatformMacintosh))  {
 		// Mac version of Loom uses yet another sound format
-		/*
-		playSound #9 (room 70)
-		000000: 55 00 00 45  73 6f 00 64  01 00 00 00  00 00 00 00   |U..Eso.d........|
-		000010: 00 05 00 8e  2a 8f 2d 1c  2a 8f 2a 8f  2d 1c 00 28   |....*.-.*.*.-..(|
-		000020: 00 31 00 3a  00 43 00 4c  00 01 00 00  00 01 00 64   |.1.:.C.L.......d|
-		000030: 5a 00 01 00  00 00 01 00  64 00 00 01  00 00 00 01   |Z.......d.......|
-		000040: 00 64 5a 00  01 00 00 00  01 00 64 5a  00 01 00 00   |.dZ.......dZ....|
-		000050: 00 01 00 64  00 00 00 00  00 00 00 07  00 00 00 64   |...d...........d|
-		000060: 64 00 00 4e  73 6f 00 64  01 00 00 00  00 00 00 00   |d..Nso.d........|
-		000070: 00 05 00 89  3d 57 2d 1c  3d 57 3d 57  2d 1c 00 28   |....=W-.=W=W-..(|
-		playSound #16 (room 69)
-		000000: dc 00 00 a5  73 6f 00 64  01 00 00 00  00 00 00 00   |....so.d........|
-		000010: 00 05 00 00  2a 8f 03 e8  03 e8 03 e8  03 e8 00 28   |....*..........(|
-		000020: 00 79 00 7f  00 85 00 d6  00 01 00 00  00 19 01 18   |.y..............|
-		000030: 2f 00 18 00  01 18 32 00  18 00 01 18  36 00 18 00   |/.....2.....6...|
-		000040: 01 18 3b 00  18 00 01 18  3e 00 18 00  01 18 42 00   |..;.....>.....B.|
-		000050: 18 00 01 18  47 00 18 00  01 18 4a 00  18 00 01 18   |....G.....J.....|
-		000060: 4e 00 10 00  01 18 53 00  10 00 01 18  56 00 10 00   |N.....S.....V...|
-		000070: 01 18 5a 00  10 00 02 28  5f 00 01 00  00 00 00 00   |..Z....(_.......|
-		*/
+		if (_vm->_musicEngine) {
+			_vm->_musicEngine->startSound(soundID);
+		}
 	}
 	else if ((_vm->_game.platform == Common::kPlatformMacintosh) && (_vm->_game.id == GID_INDY3) && READ_BE_UINT16(ptr + 8) == 0x1C) {
 		// Sound format as used in Indy3 EGA Mac.


Commit: 148a6d334770d992d306e7b399d42259c8efc649
    https://github.com/scummvm/scummvm/commit/148a6d334770d992d306e7b399d42259c8efc649
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-13T16:27:53-08:00

Commit Message:
SCUMM: Move most of the Macintosh player code into its own class

The Monkey Island and Loom mac music is really quite similar. The
data layout is a bit different, but most of the code was easy to
separate into its own class. The Loom player doesn't do looped music
but I don't remember off-hand if it ever should.

Changed paths:
  A engines/scumm/player_mac.cpp
  A engines/scumm/player_mac.h
    engines/scumm/module.mk
    engines/scumm/player_v3m.cpp
    engines/scumm/player_v3m.h
    engines/scumm/player_v5m.cpp
    engines/scumm/player_v5m.h



diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 57a495a..28884d7 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -36,6 +36,7 @@ MODULE_OBJS := \
 	object.o \
 	palette.o \
 	player_apple2.o \
+	player_mac.o \
 	player_mod.o \
 	player_nes.o \
 	player_pce.o \
diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
new file mode 100644
index 0000000..565b13d
--- /dev/null
+++ b/engines/scumm/player_mac.cpp
@@ -0,0 +1,319 @@
+/* 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/macresman.h"
+#include "common/translation.h"
+#include "engines/engine.h"
+#include "gui/message.h"
+#include "scumm/player_mac.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask)
+	: _vm(scumm),
+	  _mixer(mixer),
+	  _sampleRate(_mixer->getOutputRate()),
+	  _soundPlaying(-1),
+	  _numberOfChannels(numberOfChannels),
+	  _channelMask(channelMask) {
+
+	assert(scumm);
+	assert(mixer);
+
+	if (checkMusicAvailable()) {
+		_channel = NULL;
+		return;
+	}
+
+	_channel = new Player_Mac::Channel[_numberOfChannels];
+
+	int i;
+
+	for (i = 0; i < _numberOfChannels; i++) {
+		_channel[i]._looped = false;
+		_channel[i]._length = 0;
+		_channel[i]._data = NULL;
+		_channel[i]._pos = 0;
+		_channel[i]._pitchModifier = 0;
+		_channel[i]._velocity = 0;
+		_channel[i]._remaining = 0;
+		_channel[i]._notesLeft = false;
+		_channel[i]._instrument._data = NULL;
+		_channel[i]._instrument._size = 0;
+		_channel[i]._instrument._rate = 0;
+		_channel[i]._instrument._loopStart = 0;
+		_channel[i]._instrument._loopEnd = 0;
+		_channel[i]._instrument._baseFreq = 0;
+		_channel[i]._instrument._pos = 0;
+		_channel[i]._instrument._subPos = 0;
+	}
+
+	_pitchTable[116] = 1664510;
+	_pitchTable[117] = 1763487;
+	_pitchTable[118] = 1868350;
+	_pitchTable[119] = 1979447;
+	_pitchTable[120] = 2097152;
+	_pitchTable[121] = 2221855;
+	_pitchTable[122] = 2353973;
+	_pitchTable[123] = 2493948;
+	_pitchTable[124] = 2642246;
+	_pitchTable[125] = 2799362;
+	_pitchTable[126] = 2965820;
+	_pitchTable[127] = 3142177;
+	for (i = 115; i >= 0; --i) {
+		_pitchTable[i] = _pitchTable[i + 12] / 2;
+	}
+
+	setMusicVolume(255);
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_Mac::~Player_Mac() {
+	Common::StackLock lock(_mutex);
+	_mixer->stopHandle(_soundHandle);
+	stopAllSounds_Internal();
+	delete[] _channel;
+}
+
+void Player_Mac::setMusicVolume(int vol) {
+	debug(5, "Player_Mac::setMusicVolume(%d)", vol);
+}
+
+void Player_Mac::stopAllSounds_Internal() {
+	_soundPlaying = -1;
+	for (int i = 0; i < _numberOfChannels; i++) {
+		// The channel data is managed by the resource manager, so
+		// don't delete that.
+		delete[] _channel[i]._instrument._data;
+		_channel[i]._instrument._data = NULL;
+
+		_channel[i]._remaining = 0;
+		_channel[i]._notesLeft = false;
+	}
+}
+
+void Player_Mac::stopAllSounds() {
+	Common::StackLock lock(_mutex);
+	debug(5, "Player_Mac::stopAllSounds()");
+	stopAllSounds_Internal();
+}
+
+void Player_Mac::stopSound(int nr) {
+	Common::StackLock lock(_mutex);
+	debug(5, "Player_Mac::stopSound(%d)", nr);
+
+	if (nr == _soundPlaying) {
+		stopAllSounds();
+	}
+}
+
+void Player_Mac::startSound(int nr) {
+	Common::StackLock lock(_mutex);
+	debug(5, "Player_Mac::startSound(%d)", nr);
+
+	const byte *ptr = _vm->getResourceAddress(rtSound, nr);
+	assert(ptr);
+
+	if (!loadMusic(ptr)) {
+		return;
+	}
+
+	_soundPlaying = nr;
+}
+
+bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) {
+	// Load the sound
+	uint16 soundType = stream->readUint16BE();
+	if (soundType != 1) {
+		warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType);
+		return false;
+	}
+	uint16 typeCount = stream->readUint16BE();
+	if (typeCount != 1) {
+		warning("Player_Mac::loadInstrument: Unsupported data type count %d", typeCount);
+		return false;
+	}
+	uint16 dataType = stream->readUint16BE();
+	if (dataType != 5) {
+		warning("Player_Mac::loadInstrument: Unsupported data type %d", dataType);
+		return false;
+	}
+
+	stream->readUint32BE();	// initialization option
+
+	uint16 cmdCount = stream->readUint16BE();
+	if (cmdCount != 1) {
+		warning("Player_Mac::loadInstrument: Unsupported command count %d", cmdCount);
+		return false;
+	}
+	uint16 command = stream->readUint16BE();
+	if (command != 0x8050 && command != 0x8051) {
+		warning("Player_Mac::loadInstrument: Unsupported command 0x%04X", command);
+		return false;
+	}
+
+	stream->readUint16BE(); // 0
+	uint32 soundHeaderOffset = stream->readUint32BE();
+
+	stream->seek(soundHeaderOffset);
+
+	uint32 soundDataOffset = stream->readUint32BE();
+	uint32 size = stream->readUint32BE();
+	uint32 rate = stream->readUint32BE() >> 16;
+	uint32 loopStart = stream->readUint32BE();
+	uint32 loopEnd = stream->readUint32BE();
+	byte encoding = stream->readByte();
+	byte baseFreq = stream->readByte();
+
+	if (encoding != 0) {
+		warning("Player_Mac::loadInstrument: Unsupported encoding %d", encoding);
+		return false;
+	}
+
+	stream->skip(soundDataOffset);
+
+	byte *data = new byte[size];
+	stream->read(data, size);
+
+	_instrument._data = data;
+	_instrument._size = size;
+	_instrument._rate = rate;
+	_instrument._loopStart = loopStart;
+	_instrument._loopEnd = loopEnd;
+	_instrument._baseFreq = baseFreq;
+
+	return true;
+}
+
+int Player_Mac::getMusicTimer() {
+	return 0;
+}
+
+int Player_Mac::getSoundStatus(int nr) const {
+	return _soundPlaying == nr;
+}
+
+int Player_Mac::readBuffer(int16 *data, const int numSamples) {
+	Common::StackLock lock(_mutex);
+
+	memset(data, 0, numSamples * 2);
+	if (_soundPlaying == -1) {
+		return numSamples;
+	}
+
+	bool notesLeft = false;
+
+	for (int i = 0; i < _numberOfChannels; i++) {
+		if (!(_channelMask & (1 << i))) {
+			continue;
+		}
+
+		uint samplesLeft = numSamples;
+		int16 *ptr = data;
+
+		while (samplesLeft > 0) {
+			int generated;
+			if (_channel[i]._remaining == 0) {
+				uint16 duration;
+				byte note, velocity;
+				if (getNextNote(i, duration, note, velocity)) {
+					if (note > 1) {
+						const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq;
+						assert(pitchIdx >= 0);
+						// I don't want to use floating-point arithmetics here,
+						// but I ran into overflow problems with the church
+						// music. It's only once per note, so it should be ok.
+						double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
+						_channel[i]._pitchModifier = mult * _pitchTable[pitchIdx];
+						_channel[i]._velocity = velocity;
+					} else {
+						_channel[i]._pitchModifier = 0;
+						_channel[i]._velocity = 0;
+					}
+
+					// The correct formula should be:
+					//
+					// (duration * 473 * _sampleRate) / (4 * 480 * 480)
+					//
+					// But that's likely to cause integer overflow, so
+					// we do it in two steps and hope that the rounding
+					// error won't be noticeable.
+					//
+					// The original code is a bit unclear on if it should
+					// be 473 or 437, but since the comments indicated
+					// 473 I'm assuming 437 was a typo.
+					_channel[i]._remaining = (duration * _sampleRate) / (4 * 480);
+					_channel[i]._remaining = (_channel[i]._remaining * 473) / 480;
+				} else {
+					_channel[i]._pitchModifier = 0;
+					_channel[i]._velocity = 0;
+					_channel[i]._remaining = samplesLeft;
+				}
+			}
+			generated = MIN(_channel[i]._remaining, samplesLeft);
+			if (_channel[i]._velocity != 0) {
+				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated);
+			}
+			ptr += generated;
+			samplesLeft -= generated;
+			_channel[i]._remaining -= generated;
+		}
+
+		if (_channel[i]._notesLeft) {
+			notesLeft = true;
+		}
+	}
+
+	if (!notesLeft) {
+		stopAllSounds_Internal();
+	}
+
+	return numSamples;
+}
+
+void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) {
+	int samplesLeft = numSamples;
+	while (samplesLeft) {
+		_subPos += pitchModifier;
+		while (_subPos >= 0x10000) {
+			_subPos -= 0x10000;
+			_pos++;
+			if (_pos >= _loopEnd) {
+				_pos = _loopStart;
+			}
+		}
+
+		int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255;
+		if (sample > 32767) {
+			sample = 32767;
+		} else if (sample < -32768) {
+			sample = -32768;
+		}
+
+		*data++ = sample; // (_data[_pos] * 127) / 100;
+		samplesLeft--;
+	}
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h
new file mode 100644
index 0000000..505a94e
--- /dev/null
+++ b/engines/scumm/player_mac.h
@@ -0,0 +1,124 @@
+/* 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.
+ *
+ */
+
+#ifndef SCUMM_PLAYER_MAC_H
+#define SCUMM_PLAYER_MAC_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+#define RES_SND MKTAG('s', 'n', 'd', ' ')
+
+class Mixer;
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Scumm Macintosh music driver, base class.
+ */
+class Player_Mac : public Audio::AudioStream, public MusicEngine {
+public:
+	Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask);
+	virtual ~Player_Mac();
+
+	// MusicEngine API
+	virtual void setMusicVolume(int vol);
+	virtual void startSound(int sound);
+	virtual void stopSound(int sound);
+	virtual void stopAllSounds();
+	virtual int  getMusicTimer();
+	virtual int  getSoundStatus(int sound) const;
+
+	// AudioStream API
+	virtual int readBuffer(int16 *buffer, const int numSamples);
+	virtual bool isStereo() const { return false; }
+	virtual bool endOfData() const { return false; }
+	virtual int getRate() const { return _sampleRate; }
+
+private:
+	Common::Mutex _mutex;
+	Audio::Mixer *const _mixer;
+	Audio::SoundHandle _soundHandle;
+	const uint32 _sampleRate;
+	int _soundPlaying;
+
+	void stopAllSounds_Internal();
+
+	struct Instrument {
+		byte *_data;
+		uint32 _size;
+		uint32 _rate;
+		uint32 _loopStart;
+		uint32 _loopEnd;
+		byte _baseFreq;
+
+		uint _pos;
+		uint _subPos;
+
+		void newNote() {
+			_pos = 0;
+			_subPos = 0;
+		}
+
+		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples);
+	};
+
+	int _pitchTable[128];
+	int _numberOfChannels;
+	int _channelMask;
+
+	virtual bool checkMusicAvailable() { return false; }
+	virtual bool loadMusic(const byte *ptr) { return false; }
+	virtual bool getNextNote(int ch, uint16 &duration, byte &value, byte &velocity) { return false; }
+
+protected:
+	struct Channel {
+		virtual ~Channel() {}
+
+		Instrument _instrument;
+		bool _looped;
+		uint32 _length;
+		const byte *_data;
+
+		uint _pos;
+		int _pitchModifier;
+		byte _velocity;
+		uint32 _remaining;
+
+		bool _notesLeft;
+
+		bool loadInstrument(Common::SeekableReadStream *stream);
+ 	};
+
+	ScummEngine *const _vm;
+	Channel *_channel;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp
index 081f48f..2d7c2ea 100644
--- a/engines/scumm/player_v3m.cpp
+++ b/engines/scumm/player_v3m.cpp
@@ -94,59 +94,18 @@
 #include "scumm/player_v3m.h"
 #include "scumm/scumm.h"
 
-#define RES_SND MKTAG('s', 'n', 'd', ' ')
-
 namespace Scumm {
 
 Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
-	: _vm(scumm),
-	  _mixer(mixer),
-	  _sampleRate(_mixer->getOutputRate()),
-	  _soundPlaying(-1) {
-
-	assert(scumm);
-	assert(mixer);
+	: Player_Mac(scumm, mixer, 5, 0x1E) {
 	assert(_vm->_game.id == GID_LOOM);
 
-	int i;
-
-	for (i = 0; i < 5; i++) {
-		_channel[i]._looped = false;
-		_channel[i]._length = 0;
-		_channel[i]._data = NULL;
-		_channel[i]._pos = 0;
-		_channel[i]._pitchModifier = 0;
-		_channel[i]._velocity = 0;
-		_channel[i]._remaining = 0;
-		_channel[i]._notesLeft = false;
-		_channel[i]._instrument._data = NULL;
-		_channel[i]._instrument._size = 0;
-		_channel[i]._instrument._rate = 0;
-		_channel[i]._instrument._loopStart = 0;
-		_channel[i]._instrument._loopEnd = 0;
-		_channel[i]._instrument._baseFreq = 0;
-		_channel[i]._instrument._pos = 0;
-		_channel[i]._instrument._subPos = 0;
-	}
-
-	_pitchTable[116] = 1664510;
-	_pitchTable[117] = 1763487;
-	_pitchTable[118] = 1868350;
-	_pitchTable[119] = 1979447;
-	_pitchTable[120] = 2097152;
-	_pitchTable[121] = 2221855;
-	_pitchTable[122] = 2353973;
-	_pitchTable[123] = 2493948;
-	_pitchTable[124] = 2642246;
-	_pitchTable[125] = 2799362;
-	_pitchTable[126] = 2965820;
-	_pitchTable[127] = 3142177;
-	for (i = 115; i >= 0; --i) {
-		_pitchTable[i] = _pitchTable[i + 12] / 2;
-	}
-
-	setMusicVolume(255);
+	// Channel 0 seems to be what was played on low-end macs, that couldn't
+	// handle multi-channel music and play the game at the same time. I'm
+	// not sure if stream 4 is ever used, but let's use it just in case.
+}
 
+bool Player_V3M::checkMusicAvailable() {
 	Common::MacResManager resource;
 	// \252 is a trademark glyph. I don't think we should assume that it
 	// will be preserved when copying from the Mac disk.
@@ -155,72 +114,28 @@ Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
 			"Could not find the 'Loom' Macintosh executable to read the\n"
 			"instruments from. Music will be disabled."), _("OK"));
 		dialog.runModal();
-		return;
-	}
-
-	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
-}
-
-Player_V3M::~Player_V3M() {
-	Common::StackLock lock(_mutex);
-	_mixer->stopHandle(_soundHandle);
-	stopAllSounds_Internal();
-}
-
-void Player_V3M::setMusicVolume(int vol) {
-	debug(5, "Player_V3M::setMusicVolume(%d)", vol);
-}
-
-void Player_V3M::stopAllSounds_Internal() {
-	_soundPlaying = -1;
-	for (int i = 0; i < 3; i++) {
-		// The channel data is managed by the resource manager, so
-		// don't delete that.
-		delete[] _channel[i]._instrument._data;
-		_channel[i]._instrument._data = NULL;
-
-		_channel[i]._remaining = 0;
-		_channel[i]._notesLeft = false;
+		return false;
 	}
-}
-
-void Player_V3M::stopAllSounds() {
-	Common::StackLock lock(_mutex);
-	debug(5, "Player_V3M::stopAllSounds()");
-	stopAllSounds_Internal();
-}
-
-void Player_V3M::stopSound(int nr) {
-	Common::StackLock lock(_mutex);
-	debug(5, "Player_V3M::stopSound(%d)", nr);
 
-	if (nr == _soundPlaying) {
-		stopAllSounds();
-	}
+	return true;
 }
 
-void Player_V3M::startSound(int nr) {
-	Common::StackLock lock(_mutex);
-	debug(5, "Player_V3M::startSound(%d)", nr);
-
+bool Player_V3M::loadMusic(const byte *ptr) {
 	Common::MacResManager resource;
 	if (!resource.open("Loom\252") && !resource.open("Loom")) {
-		return;
+		return false;
 	}
 
-	const byte *ptr = _vm->getResourceAddress(rtSound, nr);
-	assert(ptr);
+	assert(ptr[4] == 's' && ptr[5] == 'o');
 
-	const byte *src = ptr;
 	uint i;
-
-	assert(src[4] == 's' && src[5] == 'o');
 	for (i = 0; i < 5; i++) {
-		int instrument = READ_BE_UINT16(src + 20 + 2 * i);
-		int offset = READ_BE_UINT16(src + 30 + 2 * i);
+		int instrument = READ_BE_UINT16(ptr + 20 + 2 * i);
+		int offset = READ_BE_UINT16(ptr + 30 + 2 * i);
+
 		_channel[i]._looped = false;
-		_channel[i]._length = READ_BE_UINT16(src + offset + 4) * 3;
-		_channel[i]._data = src + offset + 6;
+		_channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3;
+		_channel[i]._data = ptr + offset + 6;
 		_channel[i]._pos = 0;
 		_channel[i]._pitchModifier = 0;
 		_channel[i]._velocity = 0;
@@ -229,205 +144,31 @@ void Player_V3M::startSound(int nr) {
 
 		Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument);
 		if (_channel[i].loadInstrument(stream)) {
-			debug(6, "Player_V3M::startSound: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str());
+			debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str());
 		} else {
 			resource.close();
-			return;
+			return false;
 		}
 	}
 
 	resource.close();
-	_soundPlaying = nr;
-}
-
-bool Player_V3M::Channel::loadInstrument(Common::SeekableReadStream *stream) {
-	// Load the sound
-	uint16 soundType = stream->readUint16BE();
-	if (soundType != 1) {
-		warning("Player_V3M::loadInstrument: Unsupported sound type %d", soundType);
-		return false;
-	}
-	uint16 typeCount = stream->readUint16BE();
-	if (typeCount != 1) {
-		warning("Player_V3M::loadInstrument: Unsupported data type count %d", typeCount);
-		return false;
-	}
-	uint16 dataType = stream->readUint16BE();
-	if (dataType != 5) {
-		warning("Player_V3M::loadInstrument: Unsupported data type %d", dataType);
-		return false;
-	}
-
-	stream->readUint32BE();	// initialization option
-
-	uint16 cmdCount = stream->readUint16BE();
-	if (cmdCount != 1) {
-		warning("Player_V3M::loadInstrument: Unsupported command count %d", cmdCount);
-		return false;
-	}
-	uint16 command = stream->readUint16BE();
-	if (command != 0x8050 && command != 0x8051) {
-		warning("Player_V3M::loadInstrument: Unsupported command 0x%04X", command);
-		return false;
-	}
-
-	stream->readUint16BE(); // 0
-	uint32 soundHeaderOffset = stream->readUint32BE();
-
-	stream->seek(soundHeaderOffset);
-
-	uint32 soundDataOffset = stream->readUint32BE();
-	uint32 size = stream->readUint32BE();
-	uint32 rate = stream->readUint32BE() >> 16;
-	uint32 loopStart = stream->readUint32BE();
-	uint32 loopEnd = stream->readUint32BE();
-	byte encoding = stream->readByte();
-	byte baseFreq = stream->readByte();
-
-	if (encoding != 0) {
-		warning("Player_V3M::loadInstrument: Unsupported encoding %d", encoding);
-		return false;
-	}
-
-	stream->skip(soundDataOffset);
-
-	byte *data = new byte[size];
-	stream->read(data, size);
-
-	_instrument._data = data;
-	_instrument._size = size;
-	_instrument._rate = rate;
-	_instrument._loopStart = loopStart;
-	_instrument._loopEnd = loopEnd;
-	_instrument._baseFreq = baseFreq;
-
 	return true;
 }
 
-int Player_V3M::getMusicTimer() {
-	return 0;
-}
-
-int Player_V3M::getSoundStatus(int nr) const {
-	return _soundPlaying == nr;
-}
-
-int Player_V3M::readBuffer(int16 *data, const int numSamples) {
-	Common::StackLock lock(_mutex);
-
-	memset(data, 0, numSamples * 2);
-	if (_soundPlaying == -1) {
-		return numSamples;
-	}
-
-	bool notesLeft = false;
-
-	// Channel 0 seems to be what was played on low-end macs, that couldn't
-	// handle multi-channel music and play the game at the same time. I'm
-	// not sure if stream 4 is ever used, but let's use it just in case.
-
-	for (int i = 1; i < 5; i++) {
-		uint samplesLeft = numSamples;
-		int16 *ptr = data;
-
-		while (samplesLeft > 0) {
-			int generated;
-			if (_channel[i]._remaining == 0) {
-				uint16 duration;
-				byte note, velocity;
-				if (_channel[i].getNextNote(duration, note, velocity)) {
-					if (note > 0) {
-						const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq;
-						assert(pitchIdx >= 0);
-						// I don't want to use floating-point arithmetics here,
-						// but I ran into overflow problems with the church
-						// music. It's only once per note, so it should be ok.
-						double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
-						_channel[i]._pitchModifier = mult * _pitchTable[pitchIdx];
-						_channel[i]._velocity = velocity;
-					} else {
-						_channel[i]._pitchModifier = 0;
-						_channel[i]._velocity = 0;
-					}
-
-					// The correct formula should be:
-					//
-					// (duration * 473 * _sampleRate) / (4 * 480 * 480)
-					//
-					// But that's likely to cause integer overflow, so
-					// we do it in two steps and hope that the rounding
-					// error won't be noticeable.
-					//
-					// The original code is a bit unclear on if it should
-					// be 473 or 437, but since the comments indicated
-					// 473 I'm assuming 437 was a typo.
-					_channel[i]._remaining = (duration * _sampleRate) / (4 * 480);
-					_channel[i]._remaining = (_channel[i]._remaining * 473) / 480;
-				} else {
-					_channel[i]._pitchModifier = 0;
-					_channel[i]._velocity = 0;
-					_channel[i]._remaining = samplesLeft;
-				}
-			}
-			generated = MIN(_channel[i]._remaining, samplesLeft);
-			if (_channel[i]._velocity != 0) {
-				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated);
-			}
-			ptr += generated;
-			samplesLeft -= generated;
-			_channel[i]._remaining -= generated;
-		}
-
-		if (_channel[i]._notesLeft) {
-			notesLeft = true;
-		}
-	}
-
-	if (!notesLeft) {
-		stopAllSounds_Internal();
-	}
-
-	return numSamples;
-}
-
-bool Player_V3M::Channel::getNextNote(uint16 &duration, byte &note, byte &velocity) {
-	_instrument.newNote();
-	if (_pos >= _length) {
-		if (!_looped) {
-			_notesLeft = false;
+bool Player_V3M::getNextNote(int ch, uint16 &duration, byte &note, byte &velocity) {
+	_channel[ch]._instrument.newNote();
+	if (_channel[ch]._pos >= _channel[ch]._length) {
+		if (!_channel[ch]._looped) {
+			_channel[ch]._notesLeft = false;
 			return false;
 		}
-		_pos = 0;
+		_channel[ch]._pos = 0;
 	}
-	duration = READ_BE_UINT16(&_data[_pos]);
-	note = _data[_pos + 2];
+	duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
+	note = _channel[ch]._data[_channel[ch]._pos + 2];
 	velocity = 127;
-	_pos += 3;
+	_channel[ch]._pos += 3;
 	return true;
 }
 
-void Player_V3M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) {
-	int samplesLeft = numSamples;
-	while (samplesLeft) {
-		_subPos += pitchModifier;
-		while (_subPos >= 0x10000) {
-			_subPos -= 0x10000;
-			_pos++;
-			if (_pos >= _loopEnd) {
-				_pos = _loopStart;
-			}
-		}
-
-		int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255;
-		if (sample > 32767) {
-			sample = 32767;
-		} else if (sample < -32768) {
-			sample = -32768;
-		}
-
-		*data++ = sample; // (_data[_pos] * 127) / 100;
-		samplesLeft--;
-	}
-}
-
 } // End of namespace Scumm
diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h
index 3465621..0bbb522 100644
--- a/engines/scumm/player_v3m.h
+++ b/engines/scumm/player_v3m.h
@@ -27,6 +27,7 @@
 #include "common/util.h"
 #include "common/mutex.h"
 #include "scumm/music.h"
+#include "scumm/player_mac.h"
 #include "audio/audiostream.h"
 #include "audio/mixer.h"
 
@@ -39,73 +40,13 @@ class ScummEngine;
 /**
  * Scumm V3 Macintosh music driver.
  */
- class Player_V3M : public Audio::AudioStream, public MusicEngine {
+ class Player_V3M : public Player_Mac {
 public:
 	Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer);
-	virtual ~Player_V3M();
 
-	// MusicEngine API
-	virtual void setMusicVolume(int vol);
-	virtual void startSound(int sound);
-	virtual void stopSound(int sound);
-	virtual void stopAllSounds();
-	virtual int  getMusicTimer();
-	virtual int  getSoundStatus(int sound) const;
-
-	// AudioStream API
-	virtual int readBuffer(int16 *buffer, const int numSamples);
-	virtual bool isStereo() const { return false; }
-	virtual bool endOfData() const { return false; }
-	virtual int getRate() const { return _sampleRate; }
-
-private:
-	ScummEngine *const _vm;
-	Common::Mutex _mutex;
-	Audio::Mixer *const _mixer;
-	Audio::SoundHandle _soundHandle;
-	const uint32 _sampleRate;
-	int _soundPlaying;
-
-	void stopAllSounds_Internal();
-
-	struct Instrument {
-		byte *_data;
-		uint32 _size;
-		uint32 _rate;
-		uint32 _loopStart;
-		uint32 _loopEnd;
-		byte _baseFreq;
-
-		uint _pos;
-		uint _subPos;
-
-		void newNote() {
-			_pos = 0;
-			_subPos = 0;
-		}
-
-		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples);
-	};
-
-	struct Channel {
-		Instrument _instrument;
-		bool _looped;
-		uint32 _length;
-		const byte *_data;
-
-		uint _pos;
-		int _pitchModifier;
-		byte _velocity;
-		uint32 _remaining;
-
-		bool _notesLeft;
-
-		bool loadInstrument(Common::SeekableReadStream *stream);
-		bool getNextNote(uint16 &duration, byte &value, byte &velocity);
- 	};
-
-	Channel _channel[5];
-	int _pitchTable[128];
+	virtual bool checkMusicAvailable();
+	virtual bool loadMusic(const byte *ptr);
+	virtual bool getNextNote(int ch, uint16 &duration, byte &note, byte &velocity);
 };
 
 } // End of namespace Scumm
diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp
index 640361c..2051909 100644
--- a/engines/scumm/player_v5m.cpp
+++ b/engines/scumm/player_v5m.cpp
@@ -79,139 +79,49 @@
 #include "scumm/player_v5m.h"
 #include "scumm/scumm.h"
 
-#define RES_SND MKTAG('s', 'n', 'd', ' ')
-
 namespace Scumm {
 
 Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
-	: _vm(scumm),
-	  _mixer(mixer),
-	  _sampleRate(_mixer->getOutputRate()),
-	  _soundPlaying(-1) {
-
-	assert(scumm);
-	assert(mixer);
+	: Player_Mac(scumm, mixer, 3, 0x07) {
 	assert(_vm->_game.id == GID_MONKEY);
+}
 
-	int i;
-
-	for (i = 0; i < 3; i++) {
-		_channel[i]._looped = false;
-		_channel[i]._length = 0;
-		_channel[i]._data = NULL;
-		_channel[i]._pos = 0;
-		_channel[i]._pitchModifier = 0;
-		_channel[i]._velocity = 0;
-		_channel[i]._remaining = 0;
-		_channel[i]._notesLeft = false;
-		_channel[i]._instrument._data = NULL;
-		_channel[i]._instrument._size = 0;
-		_channel[i]._instrument._rate = 0;
-		_channel[i]._instrument._loopStart = 0;
-		_channel[i]._instrument._loopEnd = 0;
-		_channel[i]._instrument._baseFreq = 0;
-		_channel[i]._instrument._pos = 0;
-		_channel[i]._instrument._subPos = 0;
-	}
-
-	_pitchTable[116] = 1664510;
-	_pitchTable[117] = 1763487;
-	_pitchTable[118] = 1868350;
-	_pitchTable[119] = 1979447;
-	_pitchTable[120] = 2097152;
-	_pitchTable[121] = 2221855;
-	_pitchTable[122] = 2353973;
-	_pitchTable[123] = 2493948;
-	_pitchTable[124] = 2642246;
-	_pitchTable[125] = 2799362;
-	_pitchTable[126] = 2965820;
-	_pitchTable[127] = 3142177;
-	for (i = 115; i >= 0; --i) {
-		_pitchTable[i] = _pitchTable[i + 12] / 2;
-	}
-
-	setMusicVolume(255);
-
+bool Player_V5M::checkMusicAvailable() {
 	Common::MacResManager resource;
 	if (!resource.exists("Monkey Island")) {
 		GUI::MessageDialog dialog(_(
 			"Could not find the 'Monkey Island' Macintosh executable to read the\n"
 			"instruments from. Music will be disabled."), _("OK"));
 		dialog.runModal();
-		return;
-	}
-
-	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
-}
-
-Player_V5M::~Player_V5M() {
-	Common::StackLock lock(_mutex);
-	_mixer->stopHandle(_soundHandle);
-	stopAllSounds_Internal();
-}
-
-void Player_V5M::setMusicVolume(int vol) {
-	debug(5, "Player_V5M::setMusicVolume(%d)", vol);
-}
-
-void Player_V5M::stopAllSounds_Internal() {
-	_soundPlaying = -1;
-	for (int i = 0; i < 3; i++) {
-		// The channel data is managed by the resource manager, so
-		// don't delete that.
-		delete[] _channel[i]._instrument._data;
-		_channel[i]._instrument._data = NULL;
-
-		_channel[i]._remaining = 0;
-		_channel[i]._notesLeft = false;
+		return false;
 	}
-}
-
-void Player_V5M::stopAllSounds() {
-	Common::StackLock lock(_mutex);
-	debug(5, "Player_V5M::stopAllSounds()");
-	stopAllSounds_Internal();
-}
 
-void Player_V5M::stopSound(int nr) {
-	Common::StackLock lock(_mutex);
-	debug(5, "Player_V5M::stopSound(%d)", nr);
-
-	if (nr == _soundPlaying) {
-		stopAllSounds();
-	}
+	return true;
 }
 
-void Player_V5M::startSound(int nr) {
-	Common::StackLock lock(_mutex);
-	debug(5, "Player_V5M::startSound(%d)", nr);
-
+bool Player_V5M::loadMusic(const byte *ptr) {
 	Common::MacResManager resource;
 	if (!resource.open("Monkey Island")) {
-		return;
+		return false;
 	}
 
-	const byte *ptr = _vm->getResourceAddress(rtSound, nr);
-	assert(ptr);
-
-	const byte *src = ptr;
 	uint i;
 
-	src += 8;
+	ptr += 8;
 	// TODO: Decipher the unknown bytes in the header. For now, skip 'em
-	src += 28;
+	ptr += 28;
 
 	Common::MacResIDArray idArray = resource.getResIDArray(RES_SND);
 
 	// Load the three channels and their instruments
 	for (i = 0; i < 3; i++) {
-		assert(READ_BE_UINT32(src) == MKTAG('C', 'h', 'a', 'n'));
-		uint32 len = READ_BE_UINT32(src + 4);
-		uint32 instrument = READ_BE_UINT32(src + 8);
+		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 - 24;
-		_channel[i]._data = src + 12;
-		_channel[i]._looped = (READ_BE_UINT32(src + len - 8) == MKTAG('L', 'o', 'o', 'p'));
+		_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;
@@ -221,213 +131,43 @@ void Player_V5M::startSound(int nr) {
 		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, "Channel %d: Loading instrument '%s'", i, 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();
-					return;
+					return false;
 				}
 
 				break;
 			}
 		}
 
-		src += len;
+		ptr += len;
 	}
 
 	resource.close();
-	_soundPlaying = nr;
-}
-
-bool Player_V5M::Channel::loadInstrument(Common::SeekableReadStream *stream) {
-	// Load the sound
-	uint16 soundType = stream->readUint16BE();
-	if (soundType != 1) {
-		warning("Player_V5M::loadInstrument: Unsupported sound type %d", soundType);
-		return false;
-	}
-	uint16 typeCount = stream->readUint16BE();
-	if (typeCount != 1) {
-		warning("Player_V5M::loadInstrument: Unsupported data type count %d", typeCount);
-		return false;
-	}
-	uint16 dataType = stream->readUint16BE();
-	if (dataType != 5) {
-		warning("Player_V5M::loadInstrument: Unsupported data type %d", dataType);
-		return false;
-	}
-
-	stream->readUint32BE();	// initialization option
-
-	uint16 cmdCount = stream->readUint16BE();
-	if (cmdCount != 1) {
-		warning("Player_V5M::loadInstrument: Unsupported command count %d", cmdCount);
-		return false;
-	}
-	uint16 command = stream->readUint16BE();
-	if (command != 0x8050 && command != 0x8051) {
-		warning("Player_V5M::loadInstrument: Unsupported command 0x%04X", command);
-		return false;
-	}
-
-	stream->readUint16BE(); // 0
-	uint32 soundHeaderOffset = stream->readUint32BE();
-
-	stream->seek(soundHeaderOffset);
-
-	uint32 soundDataOffset = stream->readUint32BE();
-	uint32 size = stream->readUint32BE();
-	uint32 rate = stream->readUint32BE() >> 16;
-	uint32 loopStart = stream->readUint32BE();
-	uint32 loopEnd = stream->readUint32BE();
-	byte encoding = stream->readByte();
-	byte baseFreq = stream->readByte();
-
-	if (encoding != 0) {
-		warning("Player_V5M::loadInstrument: Unsupported encoding %d", encoding);
-		return false;
-	}
-
-	stream->skip(soundDataOffset);
-
-	byte *data = new byte[size];
-	stream->read(data, size);
-
-	_instrument._data = data;
-	_instrument._size = size;
-	_instrument._rate = rate;
-	_instrument._loopStart = loopStart;
-	_instrument._loopEnd = loopEnd;
-	_instrument._baseFreq = baseFreq;
-
 	return true;
 }
 
-int Player_V5M::getMusicTimer() {
-	return 0;
-}
-
-int Player_V5M::getSoundStatus(int nr) const {
-	return _soundPlaying == nr;
-}
-
-int Player_V5M::readBuffer(int16 *data, const int numSamples) {
-	Common::StackLock lock(_mutex);
-
-	memset(data, 0, numSamples * 2);
-	if (_soundPlaying == -1) {
-		return numSamples;
-	}
-
-	bool notesLeft = false;
-
-	for (int i = 0; i < 3; i++) {
-		uint samplesLeft = numSamples;
-		int16 *ptr = data;
-
-		while (samplesLeft > 0) {
-			int generated;
-			if (_channel[i]._remaining == 0) {
-				uint16 duration;
-				byte note, velocity;
-				if (_channel[i].getNextNote(duration, note, velocity)) {
-					if (note > 1) {
-						const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq;
-						assert(pitchIdx >= 0);
-						// I don't want to use floating-point arithmetics here,
-						// but I ran into overflow problems with the church
-						// music. It's only once per note, so it should be ok.
-						double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
-						_channel[i]._pitchModifier = mult * _pitchTable[pitchIdx];
-						_channel[i]._velocity = velocity;
-					} else {
-						_channel[i]._pitchModifier = 0;
-						_channel[i]._velocity = 0;
-					}
-
-					// The correct formula should be:
-					//
-					// (duration * 473 * _sampleRate) / (4 * 480 * 480)
-					//
-					// But that's likely to cause integer overflow, so
-					// we do it in two steps and hope that the rounding
-					// error won't be noticeable.
-					//
-					// The original code is a bit unclear on if it should
-					// be 473 or 437, but since the comments indicated
-					// 473 I'm assuming 437 was a typo.
-					_channel[i]._remaining = (duration * _sampleRate) / (4 * 480);
-					_channel[i]._remaining = (_channel[i]._remaining * 473) / 480;
-				} else {
-					_channel[i]._pitchModifier = 0;
-					_channel[i]._velocity = 0;
-					_channel[i]._remaining = samplesLeft;
-				}
-			}
-			generated = MIN(_channel[i]._remaining, samplesLeft);
-			if (_channel[i]._velocity != 0) {
-				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated);
-			}
-			ptr += generated;
-			samplesLeft -= generated;
-			_channel[i]._remaining -= generated;
-		}
-
-		if (_channel[i]._notesLeft) {
-			notesLeft = true;
-		}
-	}
-
-	if (!notesLeft) {
-		stopAllSounds_Internal();
-	}
-
-	return numSamples;
-}
-
-bool Player_V5M::Channel::getNextNote(uint16 &duration, byte &note, byte &velocity) {
-	_instrument.newNote();
-	if (_pos >= _length) {
-		if (!_looped) {
-			_notesLeft = false;
+bool Player_V5M::getNextNote(int ch, uint16 &duration, byte &note, byte &velocity) {
+	_channel[ch]._instrument.newNote();
+	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.
-		_pos = 0;
+		_channel[ch]._pos = 0;
 	}
-	duration = READ_BE_UINT16(&_data[_pos]);
-	note = _data[_pos + 2];
-	velocity = _data[_pos + 3];
-	_pos += 4;
+	duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
+	note = _channel[ch]._data[_channel[ch]._pos + 2];
+	velocity = _channel[ch]._data[_channel[ch]._pos + 3];
+	_channel[ch]._pos += 4;
 	return true;
 }
 
-void Player_V5M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) {
-	int samplesLeft = numSamples;
-	while (samplesLeft) {
-		_subPos += pitchModifier;
-		while (_subPos >= 0x10000) {
-			_subPos -= 0x10000;
-			_pos++;
-			if (_pos >= _loopEnd) {
-				_pos = _loopStart;
-			}
-		}
-
-		int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255;
-		if (sample > 32767) {
-			sample = 32767;
-		} else if (sample < -32768) {
-			sample = -32768;
-		}
-
-		*data++ = sample; // (_data[_pos] * 127) / 100;
-		samplesLeft--;
-	}
-}
-
 } // End of namespace Scumm
diff --git a/engines/scumm/player_v5m.h b/engines/scumm/player_v5m.h
index c245554..169fa89 100644
--- a/engines/scumm/player_v5m.h
+++ b/engines/scumm/player_v5m.h
@@ -27,6 +27,7 @@
 #include "common/util.h"
 #include "common/mutex.h"
 #include "scumm/music.h"
+#include "scumm/player_mac.h"
 #include "audio/audiostream.h"
 #include "audio/mixer.h"
 
@@ -39,73 +40,13 @@ class ScummEngine;
 /**
  * Scumm V5 Macintosh music driver.
  */
- class Player_V5M : public Audio::AudioStream, public MusicEngine {
+class Player_V5M : public Player_Mac {
 public:
 	Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer);
-	virtual ~Player_V5M();
 
-	// MusicEngine API
-	virtual void setMusicVolume(int vol);
-	virtual void startSound(int sound);
-	virtual void stopSound(int sound);
-	virtual void stopAllSounds();
-	virtual int  getMusicTimer();
-	virtual int  getSoundStatus(int sound) const;
-
-	// AudioStream API
-	virtual int readBuffer(int16 *buffer, const int numSamples);
-	virtual bool isStereo() const { return false; }
-	virtual bool endOfData() const { return false; }
-	virtual int getRate() const { return _sampleRate; }
-
-private:
-	ScummEngine *const _vm;
-	Common::Mutex _mutex;
-	Audio::Mixer *const _mixer;
-	Audio::SoundHandle _soundHandle;
-	const uint32 _sampleRate;
-	int _soundPlaying;
-
-	void stopAllSounds_Internal();
-
-	struct Instrument {
-		byte *_data;
-		uint32 _size;
-		uint32 _rate;
-		uint32 _loopStart;
-		uint32 _loopEnd;
-		byte _baseFreq;
-
-		uint _pos;
-		uint _subPos;
-
-		void newNote() {
-			_pos = 0;
-			_subPos = 0;
-		}
-
-		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples);
-	};
-
-	struct Channel {
-		Instrument _instrument;
-		bool _looped;
-		uint32 _length;
-		const byte *_data;
-
-		uint _pos;
-		int _pitchModifier;
-		byte _velocity;
-		uint32 _remaining;
-
-		bool _notesLeft;
-
-		bool loadInstrument(Common::SeekableReadStream *stream);
-		bool getNextNote(uint16 &duration, byte &value, byte &velocity);
- 	};
-
-	Channel _channel[3];
-	int _pitchTable[128];
+	virtual bool checkMusicAvailable();
+	virtual bool loadMusic(const byte *ptr);
+	virtual bool getNextNote(int ch, uint16 &duration, byte &note, byte &velocity);
 };
 
 } // End of namespace Scumm


Commit: 493644295da511e43289db49d8cafdf1150a73dc
    https://github.com/scummvm/scummvm/commit/493644295da511e43289db49d8cafdf1150a73dc
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-13T21:07:55-08:00

Commit Message:
SCUMM: Remove unnecessary check for Mac Loom

As clone2727 pointed out, the default case handles Loom. I guess it
was a special case before to *prevent* it from trying to play the
sound, and to keep some comments about the format.

Changed paths:
    engines/scumm/sound.cpp



diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index d6de1cd..cfce0cb 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -346,12 +346,6 @@ void Sound::playSound(int soundID) {
 			warning("Scumm::Sound::playSound: encountered audio resoure with chunk type 'SOUN' and sound type %d", type);
 		}
 	}
-	else if ((_vm->_game.id == GID_LOOM) && (_vm->_game.platform == Common::kPlatformMacintosh))  {
-		// Mac version of Loom uses yet another sound format
-		if (_vm->_musicEngine) {
-			_vm->_musicEngine->startSound(soundID);
-		}
-	}
 	else if ((_vm->_game.platform == Common::kPlatformMacintosh) && (_vm->_game.id == GID_INDY3) && READ_BE_UINT16(ptr + 8) == 0x1C) {
 		// Sound format as used in Indy3 EGA Mac.
 		// It seems to be closely related to the Amiga format, see player_v3a.cpp


Commit: 0b63ea1d8d0ddfbc2bbef629594eedecbc8b1273
    https://github.com/scummvm/scummvm/commit/0b63ea1d8d0ddfbc2bbef629594eedecbc8b1273
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-14T11:54:25-08:00

Commit Message:
SCUMM: Hopefully fix warning

Excplicitly cast to int to avoid a warning that I don't get, but
which clone2727 does. At least, I hope it avoids the warning.

Changed paths:
    engines/scumm/player_mac.cpp



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index 565b13d..6bc09d3 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -245,7 +245,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) {
 						// but I ran into overflow problems with the church
 						// music. It's only once per note, so it should be ok.
 						double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
-						_channel[i]._pitchModifier = mult * _pitchTable[pitchIdx];
+						_channel[i]._pitchModifier = (int)(mult * _pitchTable[pitchIdx]);
 						_channel[i]._velocity = velocity;
 					} else {
 						_channel[i]._pitchModifier = 0;


Commit: b02ecf7a8acc5218b187bf8141ab1aa5b588edfe
    https://github.com/scummvm/scummvm/commit/b02ecf7a8acc5218b187bf8141ab1aa5b588edfe
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-14T12:02:58-08:00

Commit Message:
SCUMM: Try harder to open the Loom Macintosh executable.

Try the Mac OS Roman form, the UTF-8 form and the filename without
any trademark glyph.

Changed paths:
    engines/scumm/player_v3m.cpp



diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp
index 2d7c2ea..a1e69e2 100644
--- a/engines/scumm/player_v3m.cpp
+++ b/engines/scumm/player_v3m.cpp
@@ -107,9 +107,10 @@ Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
 
 bool Player_V3M::checkMusicAvailable() {
 	Common::MacResManager resource;
-	// \252 is a trademark glyph. I don't think we should assume that it
-	// will be preserved when copying from the Mac disk.
-	if (!resource.exists("Loom\252") && !resource.exists("Loom")) {
+	// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the
+	// UTF-8 version, and just plain without in case the file system can't
+	// handle exotic characters like that.
+	if (!resource.exists("Loom\xAA") && !resource.exists("Loom\xE2\x84\xA2") && !resource.exists("Loom")) {
 		GUI::MessageDialog dialog(_(
 			"Could not find the 'Loom' Macintosh executable to read the\n"
 			"instruments from. Music will be disabled."), _("OK"));
@@ -122,7 +123,7 @@ bool Player_V3M::checkMusicAvailable() {
 
 bool Player_V3M::loadMusic(const byte *ptr) {
 	Common::MacResManager resource;
-	if (!resource.open("Loom\252") && !resource.open("Loom")) {
+	if (!resource.open("Loom\xAA") && !resource.open("Loom\xE2\x84\xA2") && !resource.open("Loom")) {
 		return false;
 	}
 


Commit: 0dcd4ba5a7bf40e03b11a475c1a6082002f486aa
    https://github.com/scummvm/scummvm/commit/0dcd4ba5a7bf40e03b11a475c1a6082002f486aa
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-14T12:20:40-08:00

Commit Message:
SCUMM: Move Mac player initialization to its own function

Apparently we cannot (portably) call virtual functions from the
constructor, so initialization has been moved to a separate function.

Changed paths:
    engines/scumm/player_mac.cpp
    engines/scumm/player_mac.h
    engines/scumm/scumm.cpp



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index 6bc09d3..58dab19 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -36,11 +36,12 @@ Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChan
 	  _soundPlaying(-1),
 	  _numberOfChannels(numberOfChannels),
 	  _channelMask(channelMask) {
-
 	assert(scumm);
 	assert(mixer);
+}
 
-	if (checkMusicAvailable()) {
+void Player_Mac::init() {
+	if (!checkMusicAvailable()) {
 		_channel = NULL;
 		return;
 	}
diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h
index 505a94e..b850915 100644
--- a/engines/scumm/player_mac.h
+++ b/engines/scumm/player_mac.h
@@ -46,6 +46,8 @@ public:
 	Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask);
 	virtual ~Player_Mac();
 
+	void init();
+
 	// MusicEngine API
 	virtual void setMusicVolume(int vol);
 	virtual void startSound(int sound);
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 4ca4df4..888a7ff 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -1823,8 +1823,10 @@ void ScummEngine::setupMusic(int midi) {
 		_musicEngine = new Player_V4A(this, _mixer);
 	} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_LOOM) {
 		_musicEngine = new Player_V3M(this, _mixer);
+		((Player_V3M *)_musicEngine)->init();
 	} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) {
 		_musicEngine = new Player_V5M(this, _mixer);
+		((Player_V5M *)_musicEngine)->init();
 	} else if (_game.id == GID_MANIAC && _game.version == 1) {
 		_musicEngine = new Player_V1(this, _mixer, MidiDriver::getMusicType(dev) != MT_PCSPK);
 	} else if (_game.version <= 2) {


Commit: b75349383e834b47ad57f86a2c0f5ab48ca4aa2c
    https://github.com/scummvm/scummvm/commit/b75349383e834b47ad57f86a2c0f5ab48ca4aa2c
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-14T13:12:46-08:00

Commit Message:
SCUMM: Remove Mac version of MI1 from special case.

We no longer use iMuse for MI1 Mac so this never happens. The Mac
player can only play one song at a time, so it should be all right.

Changed paths:
    engines/scumm/sound.cpp



diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index cfce0cb..2fe16c5 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -391,8 +391,7 @@ void Sound::playSound(int soundID) {
 	}
 	else {
 
-		if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA
-			|| (_vm->_game.id == GID_MONKEY && _vm->_game.platform == Common::kPlatformMacintosh)) {
+		if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA) {
 			// Works around the fact that in some places in MonkeyEGA/VGA,
 			// the music is never explicitly stopped.
 			// Rather it seems that starting a new music is supposed to


Commit: f784d683e0f42c059331b49dbf90f91ac442b820
    https://github.com/scummvm/scummvm/commit/f784d683e0f42c059331b49dbf90f91ac442b820
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-15T13:23:44-08:00

Commit Message:
SCUMM: Save/load Mac music engine state for Loom and MI1

Note that while this removes _townsPlayer->saveLoadWithSerializer(s)
it really shouldn't break anything because _musicEngine also points
to the FM Towns player. Famous last words...

Changed paths:
    engines/scumm/music.h
    engines/scumm/player_mac.cpp
    engines/scumm/player_mac.h
    engines/scumm/saveload.cpp
    engines/scumm/saveload.h



diff --git a/engines/scumm/music.h b/engines/scumm/music.h
index a527c77..9fd14d8 100644
--- a/engines/scumm/music.h
+++ b/engines/scumm/music.h
@@ -24,6 +24,7 @@
 #define SCUMM_MUSIC_H
 
 #include "common/scummsys.h"
+#include "engines/scumm/saveload.h"
 
 namespace Scumm {
 
@@ -78,6 +79,11 @@ public:
 	 * @return the music timer
 	 */
 	virtual int  getMusicTimer() { return 0; }
+
+	/**
+	 * Save or load the music state.
+	 */
+	virtual void saveLoadWithSerializer(Serializer *ser) {}
 };
 
 } // End of namespace Scumm
diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index 58dab19..7b0f671 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -97,6 +97,48 @@ Player_Mac::~Player_Mac() {
 	delete[] _channel;
 }
 
+void Player_Mac::saveLoadWithSerializer(Serializer *ser) {
+	Common::StackLock lock(_mutex);
+	if (ser->getVersion() < VER(94)) {
+		if (_vm->_game.id == GID_MONKEY && ser->isLoading()) {
+			// TODO: Skip old iMUSE save/load information
+		}
+	} else {
+		static const SaveLoadEntry musicEntries[] = {
+			MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)),
+			MKEND()
+		};
+
+		static const SaveLoadEntry channelEntries[] = {
+			MKLINE(Channel, _pos, sleUint16, VER(94)),
+			MKLINE(Channel, _pitchModifier, sleInt32, VER(94)),
+			MKLINE(Channel, _velocity, sleUint8, VER(94)),
+			MKLINE(Channel, _remaining, sleUint32, VER(94)),
+			MKLINE(Channel, _notesLeft, sleUint8, VER(94)),
+			MKEND()
+		};
+
+		static const SaveLoadEntry instrumentEntries[] = {
+			MKLINE(Instrument, _pos, sleUint32, VER(94)),
+			MKLINE(Instrument, _subPos, sleUint32, VER(94)),
+			MKEND()
+		};
+
+		ser->saveLoadEntries(this, musicEntries);
+
+		if (ser->isLoading() && _soundPlaying != -1) {
+			const byte *ptr = _vm->getResourceAddress(rtSound, _soundPlaying);
+			assert(ptr);
+			loadMusic(ptr);
+		}
+
+		ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries);
+		for (int i = 0; i < _numberOfChannels; i++) {
+			ser->saveLoadEntries(&_channel[i], instrumentEntries);
+		}
+	}
+}
+
 void Player_Mac::setMusicVolume(int vol) {
 	debug(5, "Player_Mac::setMusicVolume(%d)", vol);
 }
diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h
index b850915..a30111b 100644
--- a/engines/scumm/player_mac.h
+++ b/engines/scumm/player_mac.h
@@ -27,6 +27,7 @@
 #include "common/util.h"
 #include "common/mutex.h"
 #include "scumm/music.h"
+#include "scumm/saveload.h"
 #include "audio/audiostream.h"
 #include "audio/mixer.h"
 
@@ -62,6 +63,8 @@ public:
 	virtual bool endOfData() const { return false; }
 	virtual int getRate() const { return _sampleRate; }
 
+	virtual void saveLoadWithSerializer(Serializer *ser);
+
 private:
 	Common::Mutex _mutex;
 	Audio::Mixer *const _mixer;
diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp
index 72896e0..3453e53 100644
--- a/engines/scumm/saveload.cpp
+++ b/engines/scumm/saveload.cpp
@@ -1477,9 +1477,13 @@ void ScummEngine::saveOrLoad(Serializer *s) {
 	}
 
 
-	// Save/load FM-Towns audio status
-	if (_townsPlayer)
-		_townsPlayer->saveLoadWithSerializer(s);
+	//
+	// Save/load music engine status
+	//
+	if (_musicEngine) {
+		_musicEngine->saveLoadWithSerializer(s);
+	}
+
 
 	//
 	// Save/load the charset renderer state
diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h
index a640bc1..4bfa7d0 100644
--- a/engines/scumm/saveload.h
+++ b/engines/scumm/saveload.h
@@ -47,7 +47,7 @@ namespace Scumm {
  * only saves/loads those which are valid for the version of the savegame
  * which is being loaded/saved currently.
  */
-#define CURRENT_VER 93
+#define CURRENT_VER 94
 
 /**
  * An auxillary macro, used to specify savegame versions. We use this instead


Commit: f0c1d8dcc46b77895ad85b3cdaaf3b81e497b0d8
    https://github.com/scummvm/scummvm/commit/f0c1d8dcc46b77895ad85b3cdaaf3b81e497b0d8
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-15T22:49:17-08:00

Commit Message:
SCUMM: Add hack to preserve savegame compatibility with Mac MI1

For old savegames, we now use a "dummy" iMUSE objet to skip the old
iMUSE save state. I had hoped to be able to do this without making
any changes to the iMUSE code itself, but I was unable to.

Also added note about how the save state for the new music will not
quite work if the mixer output rate changes. Personally, I'm not
too worried about that. It breaks, but it shouldn't break badly.

Changed paths:
    engines/scumm/imuse/imuse.cpp
    engines/scumm/imuse/imuse.h
    engines/scumm/imuse/imuse_internal.h
    engines/scumm/player_mac.cpp



diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index 016ba89..b69ce55 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -363,7 +363,7 @@ void IMuseInternal::pause(bool paused) {
 	_paused = paused;
 }
 
-int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) {
+int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad) {
 	Common::StackLock lock(_mutex, "IMuseInternal::save_or_load()");
 	const SaveLoadEntry mainEntries[] = {
 		MKLINE(IMuseInternal, _queue_end, sleUint8, VER(8)),
@@ -440,7 +440,16 @@ int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) {
 	for (i = 0; i < 8; ++i)
 		ser->saveLoadEntries(0, volumeFaderEntries);
 
-	if (ser->isLoading()) {
+	// Normally, we have to fix up the data structures after loading a
+	// saved game. But there are cases where we don't. For instance, The
+	// Macintosh version of Monkey Island 1 used to convert the Mac0 music
+	// resources to General MIDI and play it through iMUSE as a rough
+	// approximation. Now it has its own player, but old savegame still
+	// have the iMUSE data in them. We have to skip that data, using a
+	// dummy iMUSE object, but since the resource is no longer recognizable
+	// to iMUSE, the fixup fails hard. So yes, this is a bit of a hack.
+
+	if (ser->isLoading() && fixAfterLoad) {
 		// Load all sounds that we need
 		fix_players_after_load(scumm);
 		fix_parts_after_load();
diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h
index 23449e4..cce5309 100644
--- a/engines/scumm/imuse/imuse.h
+++ b/engines/scumm/imuse/imuse.h
@@ -62,7 +62,7 @@ public:
 public:
 	virtual void on_timer(MidiDriver *midi) = 0;
 	virtual void pause(bool paused) = 0;
-	virtual int save_or_load(Serializer *ser, ScummEngine *scumm) = 0;
+	virtual int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true) = 0;
 	virtual bool get_sound_active(int sound) const = 0;
 	virtual int32 doCommand(int numargs, int args[]) = 0;
 	virtual int clear_queue() = 0;
diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h
index 846e2d7..6be564a 100644
--- a/engines/scumm/imuse/imuse_internal.h
+++ b/engines/scumm/imuse/imuse_internal.h
@@ -518,7 +518,7 @@ protected:
 public:
 	// IMuse interface
 	void pause(bool paused);
-	int save_or_load(Serializer *ser, ScummEngine *scumm);
+	int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true);
 	bool get_sound_active(int sound) const;
 	int32 doCommand(int numargs, int args[]);
 	uint32 property(int prop, uint32 value);
diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index 7b0f671..cd1df51 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -26,6 +26,7 @@
 #include "gui/message.h"
 #include "scumm/player_mac.h"
 #include "scumm/scumm.h"
+#include "scumm/imuse/imuse.h"
 
 namespace Scumm {
 
@@ -101,7 +102,8 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) {
 	Common::StackLock lock(_mutex);
 	if (ser->getVersion() < VER(94)) {
 		if (_vm->_game.id == GID_MONKEY && ser->isLoading()) {
-			// TODO: Skip old iMUSE save/load information
+			IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL);
+			dummyImuse->save_or_load(ser, _vm, false);
 		}
 	} else {
 		static const SaveLoadEntry musicEntries[] = {
@@ -109,6 +111,12 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) {
 			MKEND()
 		};
 
+		// Note: This will fail slightly when loading a savegame if
+		// the mixer output rate has changed, because the pitch
+		// modifier and remaining samples were calculated from it. As
+		// a result, the first note to be played will be out of tune,
+		// and the channels will probably be slightly out of sync.
+
 		static const SaveLoadEntry channelEntries[] = {
 			MKLINE(Channel, _pos, sleUint16, VER(94)),
 			MKLINE(Channel, _pitchModifier, sleInt32, VER(94)),


Commit: b6a42e9faa4ae9b9508bba33c1adbcfd8228c56d
    https://github.com/scummvm/scummvm/commit/b6a42e9faa4ae9b9508bba33c1adbcfd8228c56d
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-16T07:43:13-08:00

Commit Message:
SCUMM: Store sample rate in Mac MI1 / Loom savegames

This keeps the music from breaking when loading a savegame that was
made with a different sample rate than the current one. It also
breaks all savegames made in the past eight hours, but I don't think
it's necessary to maintain savegame compatibility within a pull
request, as long as it still works with savegames made before it.

Changed paths:
    engines/scumm/player_mac.cpp
    engines/scumm/player_mac.h



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index cd1df51..04c66c4 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -107,16 +107,11 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) {
 		}
 	} else {
 		static const SaveLoadEntry musicEntries[] = {
+			MKLINE(Player_Mac, _sampleRate, sleUint32, VER(94)),
 			MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)),
 			MKEND()
 		};
 
-		// Note: This will fail slightly when loading a savegame if
-		// the mixer output rate has changed, because the pitch
-		// modifier and remaining samples were calculated from it. As
-		// a result, the first note to be played will be out of tune,
-		// and the channels will probably be slightly out of sync.
-
 		static const SaveLoadEntry channelEntries[] = {
 			MKLINE(Channel, _pos, sleUint16, VER(94)),
 			MKLINE(Channel, _pitchModifier, sleInt32, VER(94)),
@@ -132,6 +127,9 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) {
 			MKEND()
 		};
 
+		uint32 mixerSampleRate = _sampleRate;
+		int i;
+
 		ser->saveLoadEntries(this, musicEntries);
 
 		if (ser->isLoading() && _soundPlaying != -1) {
@@ -141,9 +139,22 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) {
 		}
 
 		ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries);
-		for (int i = 0; i < _numberOfChannels; i++) {
+		for (i = 0; i < _numberOfChannels; i++) {
 			ser->saveLoadEntries(&_channel[i], instrumentEntries);
 		}
+
+		if (ser->isLoading()) {
+			// If necessary, adjust the channel data to fit the
+			// current sample rate.
+			if (_soundPlaying != -1 && _sampleRate != mixerSampleRate) {
+				double mult = (double)_sampleRate / (double)mixerSampleRate;
+				for (i = 0; i < _numberOfChannels; i++) {
+					_channel[i]._pitchModifier = (int)((double)_channel[i]._pitchModifier * mult);
+					_channel[i]._remaining = (int)((double)_channel[i]._remaining / mult);
+				}
+			}
+			_sampleRate = mixerSampleRate;
+		}
 	}
 }
 
diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h
index a30111b..0585eb1 100644
--- a/engines/scumm/player_mac.h
+++ b/engines/scumm/player_mac.h
@@ -69,7 +69,7 @@ private:
 	Common::Mutex _mutex;
 	Audio::Mixer *const _mixer;
 	Audio::SoundHandle _soundHandle;
-	const uint32 _sampleRate;
+	uint32 _sampleRate;
 	int _soundPlaying;
 
 	void stopAllSounds_Internal();


Commit: 4626e211496aad5ffe27bea3121483faa98770d7
    https://github.com/scummvm/scummvm/commit/4626e211496aad5ffe27bea3121483faa98770d7
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-17T02:42:41-08:00

Commit Message:
SCUMM: Remove commented out code.

It was the remains of an experiment and no longer serves a purpose.

Changed paths:
    engines/scumm/player_mac.cpp



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index 04c66c4..ac4615c 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -373,7 +373,7 @@ void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int
 			sample = -32768;
 		}
 
-		*data++ = sample; // (_data[_pos] * 127) / 100;
+		*data++ = sample;
 		samplesLeft--;
 	}
 }


Commit: ee65532a5e3af0a92384d6d777110b6e602cf1c3
    https://github.com/scummvm/scummvm/commit/ee65532a5e3af0a92384d6d777110b6e602cf1c3
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-17T02:53:54-08:00

Commit Message:
SCUMM: Avoid "pops" at the end of the note in Mac MI1/Loom music

At least on my computer, when the note ended abruptly there would
be an annoying "pop" at the end. This was particularly noticeable
at the end of the distaff notes in Loom. To get around this, fade
out the last 100 samples. There's nothing magical about 100 in
particular, but it's a nice even number and it should be short
enough that it's never a noticeable part of the note, even at low
sample rates.

Changed paths:
    engines/scumm/player_mac.cpp
    engines/scumm/player_mac.h



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index ac4615c..7c3e2c2 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -335,7 +335,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) {
 			}
 			generated = MIN(_channel[i]._remaining, samplesLeft);
 			if (_channel[i]._velocity != 0) {
-				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated);
+				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining);
 			}
 			ptr += generated;
 			samplesLeft -= generated;
@@ -354,7 +354,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) {
 	return numSamples;
 }
 
-void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) {
+void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote) {
 	int samplesLeft = numSamples;
 	while (samplesLeft) {
 		_subPos += pitchModifier;
@@ -366,7 +366,21 @@ void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int
 			}
 		}
 
-		int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255;
+		int newSample = ((_data[_pos] - 129) * 128 * volume) / 255;
+
+		// Fade out the last 100 samples on each note. Even at low
+		// output sample rates this is just a fraction of a second,
+		// but it gets rid of distracting "pops" at the end when the
+		// sample would otherwise go abruptly from something to
+		// nothing. This was particularly noticeable on the distaff
+		// notes in Loom.
+
+		remainingSamplesOnNote--;
+		if (remainingSamplesOnNote < 100) {
+			newSample = (newSample * remainingSamplesOnNote) / 100;
+		}
+
+		int sample = *data + newSample;
 		if (sample > 32767) {
 			sample = 32767;
 		} else if (sample < -32768) {
diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h
index 0585eb1..3f5184d 100644
--- a/engines/scumm/player_mac.h
+++ b/engines/scumm/player_mac.h
@@ -90,7 +90,7 @@ private:
 			_subPos = 0;
 		}
 
-		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples);
+		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote);
 	};
 
 	int _pitchTable[128];


Commit: f3c9b218065357ef0178e4d68143deada86b6ed0
    https://github.com/scummvm/scummvm/commit/f3c9b218065357ef0178e4d68143deada86b6ed0
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-18T04:43:33-08:00

Commit Message:
SCUMM: Fix whitespace

Changed paths:
    engines/scumm/player_v3m.h



diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h
index 0bbb522..b39783f 100644
--- a/engines/scumm/player_v3m.h
+++ b/engines/scumm/player_v3m.h
@@ -40,7 +40,7 @@ class ScummEngine;
 /**
  * Scumm V3 Macintosh music driver.
  */
- class Player_V3M : public Player_Mac {
+class Player_V3M : public Player_Mac {
 public:
 	Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer);
 


Commit: 4f18a92f5a91eb502518846771bc8e82ff7da20a
    https://github.com/scummvm/scummvm/commit/4f18a92f5a91eb502518846771bc8e82ff7da20a
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-18T05:30:17-08:00

Commit Message:
SCUMM: Prevent music channels from drifting out of sync in Mac MI1

In looped music, prevent the music channels from drifting out of
sync over time. This was noticeable after a few minutes in the
SCUMM Bar. We do this by extending the last note (which is just
zeroes, so we didn't even use to play it) so that it has the
exact number of samples needed to make all channels the exact
same length. (This is calculated when the music is loaded, so it
does not need any extra data in the save games, thankfully.)

As a result, the getNextNote() is now responsible for converting
the duration to number of samples (out of necessity) and for
converting the note to a pitch modifier (out of symmetry). I made
several false starts before I realized how much easier it would
be this way.

Changed paths:
    engines/scumm/player_mac.cpp
    engines/scumm/player_mac.h
    engines/scumm/player_v3m.cpp
    engines/scumm/player_v3m.h
    engines/scumm/player_v5m.cpp
    engines/scumm/player_v5m.h



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index 7c3e2c2..470bdaf 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -276,6 +276,34 @@ int Player_Mac::getSoundStatus(int nr) const {
 	return _soundPlaying == nr;
 }
 
+uint32 Player_Mac::durationToSamples(uint16 duration) {
+	// The correct formula should be:
+	//
+	// (duration * 473 * _sampleRate) / (4 * 480 * 480)
+	//
+	// But that's likely to cause integer overflow, so we do it in two
+	// steps and hope that the rounding error won't be noticeable.
+	//
+	// The original code is a bit unclear on if it should be 473 or 437,
+	// but since the comments indicated 473 I'm assuming 437 was a typo.
+	uint32 samples = (duration * _sampleRate) / (4 * 480);
+	samples = (samples * 473) / 480;
+	return samples;
+}
+
+int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) {
+	if (note > 1) {
+		const int pitchIdx = note + 60 - instrument->_baseFreq;
+		// I don't want to use floating-point arithmetics here, but I
+		// ran into overflow problems with the church music in Monkey
+		// Island. It's only once per note, so it should be ok.
+		double mult = (double)instrument->_rate / (double)_sampleRate;
+		return (int)(mult * _pitchTable[pitchIdx]);
+	} else {
+		return 0;
+	}
+}
+
 int Player_Mac::readBuffer(int16 *data, const int numSamples) {
 	Common::StackLock lock(_mutex);
 
@@ -297,36 +325,14 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) {
 		while (samplesLeft > 0) {
 			int generated;
 			if (_channel[i]._remaining == 0) {
-				uint16 duration;
-				byte note, velocity;
-				if (getNextNote(i, duration, note, velocity)) {
-					if (note > 1) {
-						const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq;
-						assert(pitchIdx >= 0);
-						// I don't want to use floating-point arithmetics here,
-						// but I ran into overflow problems with the church
-						// music. It's only once per note, so it should be ok.
-						double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
-						_channel[i]._pitchModifier = (int)(mult * _pitchTable[pitchIdx]);
-						_channel[i]._velocity = velocity;
-					} else {
-						_channel[i]._pitchModifier = 0;
-						_channel[i]._velocity = 0;
-					}
-
-					// The correct formula should be:
-					//
-					// (duration * 473 * _sampleRate) / (4 * 480 * 480)
-					//
-					// But that's likely to cause integer overflow, so
-					// we do it in two steps and hope that the rounding
-					// error won't be noticeable.
-					//
-					// The original code is a bit unclear on if it should
-					// be 473 or 437, but since the comments indicated
-					// 473 I'm assuming 437 was a typo.
-					_channel[i]._remaining = (duration * _sampleRate) / (4 * 480);
-					_channel[i]._remaining = (_channel[i]._remaining * 473) / 480;
+				uint32 samples;
+				int pitchModifier;
+				byte velocity;
+				if (getNextNote(i, samples, pitchModifier, velocity)) {
+					_channel[i]._remaining = samples;
+					_channel[i]._pitchModifier = pitchModifier;
+					_channel[i]._velocity = velocity;
+
 				} else {
 					_channel[i]._pitchModifier = 0;
 					_channel[i]._velocity = 0;
diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h
index 3f5184d..c46495c 100644
--- a/engines/scumm/player_mac.h
+++ b/engines/scumm/player_mac.h
@@ -99,7 +99,7 @@ private:
 
 	virtual bool checkMusicAvailable() { return false; }
 	virtual bool loadMusic(const byte *ptr) { return false; }
-	virtual bool getNextNote(int ch, uint16 &duration, byte &value, byte &velocity) { return false; }
+	virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { return false; }
 
 protected:
 	struct Channel {
@@ -122,6 +122,9 @@ protected:
 
 	ScummEngine *const _vm;
 	Channel *_channel;
+
+	uint32 durationToSamples(uint16 duration);
+	int noteToPitchModifier(byte note, Instrument *instrument);
 };
 
 } // End of namespace Scumm
diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp
index a1e69e2..db532a9 100644
--- a/engines/scumm/player_v3m.cpp
+++ b/engines/scumm/player_v3m.cpp
@@ -156,7 +156,7 @@ bool Player_V3M::loadMusic(const byte *ptr) {
 	return true;
 }
 
-bool Player_V3M::getNextNote(int ch, uint16 &duration, byte &note, byte &velocity) {
+bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
 	_channel[ch]._instrument.newNote();
 	if (_channel[ch]._pos >= _channel[ch]._length) {
 		if (!_channel[ch]._looped) {
@@ -165,8 +165,10 @@ bool Player_V3M::getNextNote(int ch, uint16 &duration, byte &note, byte &velocit
 		}
 		_channel[ch]._pos = 0;
 	}
-	duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
-	note = _channel[ch]._data[_channel[ch]._pos + 2];
+	uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
+	byte note = _channel[ch]._data[_channel[ch]._pos + 2];
+	samples = durationToSamples(duration);
+	pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
 	velocity = 127;
 	_channel[ch]._pos += 3;
 	return true;
diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h
index b39783f..359bab3 100644
--- a/engines/scumm/player_v3m.h
+++ b/engines/scumm/player_v3m.h
@@ -46,7 +46,7 @@ public:
 
 	virtual bool checkMusicAvailable();
 	virtual bool loadMusic(const byte *ptr);
-	virtual bool getNextNote(int ch, uint16 &duration, byte &note, byte &velocity);
+	virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity);
 };
 
 } // End of namespace Scumm
diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp
index 2051909..d59cf9a 100644
--- a/engines/scumm/player_v5m.cpp
+++ b/engines/scumm/player_v5m.cpp
@@ -119,7 +119,7 @@ bool Player_V5M::loadMusic(const byte *ptr) {
 		uint32 len = READ_BE_UINT32(ptr + 4);
 		uint32 instrument = READ_BE_UINT32(ptr + 8);
 
-		_channel[i]._length = len - 24;
+		_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;
@@ -147,10 +147,30 @@ bool Player_V5M::loadMusic(const byte *ptr) {
 	}
 
 	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 (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 (i = 0; i < 3; i++) {
+		_lastNoteSamples[i] = maxSamples - samples[i];
+	}
+
 	return true;
 }
 
-bool Player_V5M::getNextNote(int ch, uint16 &duration, byte &note, byte &velocity) {
+bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
 	_channel[ch]._instrument.newNote();
 	if (_channel[ch]._pos >= _channel[ch]._length) {
 		if (!_channel[ch]._looped) {
@@ -163,10 +183,16 @@ bool Player_V5M::getNextNote(int ch, uint16 &duration, byte &note, byte &velocit
 		// MI1 Lookout music, where I was hearing problems.
 		_channel[ch]._pos = 0;
 	}
-	duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
-	note = _channel[ch]._data[_channel[ch]._pos + 2];
+	uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
+	byte note = _channel[ch]._data[_channel[ch]._pos + 2];
+	samples = durationToSamples(duration);
+	pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
 	velocity = _channel[ch]._data[_channel[ch]._pos + 3];
 	_channel[ch]._pos += 4;
+
+	if (_channel[ch]._pos >= _channel[ch]._length) {
+		samples = _lastNoteSamples[ch];
+	}
 	return true;
 }
 
diff --git a/engines/scumm/player_v5m.h b/engines/scumm/player_v5m.h
index 169fa89..b2079ee 100644
--- a/engines/scumm/player_v5m.h
+++ b/engines/scumm/player_v5m.h
@@ -46,7 +46,10 @@ public:
 
 	virtual bool checkMusicAvailable();
 	virtual bool loadMusic(const byte *ptr);
-	virtual bool getNextNote(int ch, uint16 &duration, byte &note, byte &velocity);
+	virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity);
+
+private:
+	uint32 _lastNoteSamples[3];
 };
 
 } // End of namespace Scumm


Commit: 34a8b5049e903f33a26b56da5fa32986a023feec
    https://github.com/scummvm/scummvm/commit/34a8b5049e903f33a26b56da5fa32986a023feec
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-18T08:56:13-08:00

Commit Message:
SCUMM: Use more correct (I think) way of converting samples

It shouldn't make any real difference, but it's probably more
formally correct.

Changed paths:
    engines/scumm/player_mac.cpp



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index 470bdaf..2286a15 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -372,7 +372,7 @@ void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int
 			}
 		}
 
-		int newSample = ((_data[_pos] - 129) * 128 * volume) / 255;
+		int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255;
 
 		// Fade out the last 100 samples on each note. Even at low
 		// output sample rates this is just a fraction of a second,


Commit: ae823b5c6a31a568072545503dbc5c125d19c391
    https://github.com/scummvm/scummvm/commit/ae823b5c6a31a568072545503dbc5c125d19c391
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-18T08:57:01-08:00

Commit Message:
SCUMM: Fix regression that caused "pops" in MI1 jungle music

Properly treat rests as rests, not notes. Otherwise, it would try
to play a really low note which just came out as a "pop".

Changed paths:
    engines/scumm/player_v3m.cpp
    engines/scumm/player_v5m.cpp



diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp
index db532a9..ad812a5 100644
--- a/engines/scumm/player_v3m.cpp
+++ b/engines/scumm/player_v3m.cpp
@@ -168,8 +168,13 @@ bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &
 	uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
 	byte note = _channel[ch]._data[_channel[ch]._pos + 2];
 	samples = durationToSamples(duration);
-	pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
-	velocity = 127;
+	if (note > 0) {
+		pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
+		velocity = 127;
+	} else {
+		pitchModifier = 0;
+		velocity = 0;
+	}
 	_channel[ch]._pos += 3;
 	return true;
 }
diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp
index d59cf9a..26cfb0e 100644
--- a/engines/scumm/player_v5m.cpp
+++ b/engines/scumm/player_v5m.cpp
@@ -186,8 +186,15 @@ bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &
 	uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
 	byte note = _channel[ch]._data[_channel[ch]._pos + 2];
 	samples = durationToSamples(duration);
-	pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
-	velocity = _channel[ch]._data[_channel[ch]._pos + 3];
+
+	if (note > 1) {
+		pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument);
+		velocity = _channel[ch]._data[_channel[ch]._pos + 3];
+	} else {
+		pitchModifier = 0;
+		velocity = 0;
+	}
+
 	_channel[ch]._pos += 4;
 
 	if (_channel[ch]._pos >= _channel[ch]._length) {


Commit: 94b0881427c9120e5e638872840e24297bad2a68
    https://github.com/scummvm/scummvm/commit/94b0881427c9120e5e638872840e24297bad2a68
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-18T10:18:21-08:00

Commit Message:
SCUMM: Lock the sound resource while the music is playing

After some discussion on #scummvm, the player now locks the sound
resource while the music is playing. This prevents the resource
manager from expiring the resource, which at best could cause
music to restart where it shouldn't.. At worst, I guess it could
have crashed, but I never saw that happen.

Changed paths:
    engines/scumm/player_mac.cpp



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index 2286a15..ef97c2d 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -25,6 +25,7 @@
 #include "engines/engine.h"
 #include "gui/message.h"
 #include "scumm/player_mac.h"
+#include "scumm/resource.h"
 #include "scumm/scumm.h"
 #include "scumm/imuse/imuse.h"
 
@@ -163,6 +164,9 @@ void Player_Mac::setMusicVolume(int vol) {
 }
 
 void Player_Mac::stopAllSounds_Internal() {
+	if (_soundPlaying != -1) {
+		_vm->_res->unlock(rtSound, _soundPlaying);
+	}
 	_soundPlaying = -1;
 	for (int i = 0; i < _numberOfChannels; i++) {
 		// The channel data is managed by the resource manager, so
@@ -194,6 +198,8 @@ void Player_Mac::startSound(int nr) {
 	Common::StackLock lock(_mutex);
 	debug(5, "Player_Mac::startSound(%d)", nr);
 
+	stopAllSounds_Internal();
+
 	const byte *ptr = _vm->getResourceAddress(rtSound, nr);
 	assert(ptr);
 
@@ -201,11 +207,11 @@ void Player_Mac::startSound(int nr) {
 		return;
 	}
 
+	_vm->_res->lock(rtSound, nr);
 	_soundPlaying = nr;
 }
 
 bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) {
-	// Load the sound
 	uint16 soundType = stream->readUint16BE();
 	if (soundType != 1) {
 		warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType);


Commit: cb21d7309e1f559ce5e99ad7fe826f9ecd00ac37
    https://github.com/scummvm/scummvm/commit/cb21d7309e1f559ce5e99ad7fe826f9ecd00ac37
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-18T22:16:42-08:00

Commit Message:
SCUMM: Add Windows encoding of the Mac Loom filename

Changed paths:
    engines/scumm/player_v3m.cpp



diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp
index ad812a5..6a675f2 100644
--- a/engines/scumm/player_v3m.cpp
+++ b/engines/scumm/player_v3m.cpp
@@ -105,12 +105,29 @@ Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
 	// not sure if stream 4 is ever used, but let's use it just in case.
 }
 
+// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the Windows
+// version, the UTF-8 version, and just plain without in case the file system
+// can't handle exotic characters like that.
+
+static const char *loomFileNames[] = {
+	"Loom\xAA",
+	"Loom\x99",
+	"Loom\xE2\x84\xA2",
+	"Loom"
+};
+
 bool Player_V3M::checkMusicAvailable() {
 	Common::MacResManager resource;
-	// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the
-	// UTF-8 version, and just plain without in case the file system can't
-	// handle exotic characters like that.
-	if (!resource.exists("Loom\xAA") && !resource.exists("Loom\xE2\x84\xA2") && !resource.exists("Loom")) {
+	bool found = false;
+
+	for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) {
+		if (resource.exists(loomFileNames[i])) {
+			found = true;
+			break;
+		}
+	}
+		
+	if (!found) {
 		GUI::MessageDialog dialog(_(
 			"Could not find the 'Loom' Macintosh executable to read the\n"
 			"instruments from. Music will be disabled."), _("OK"));
@@ -123,7 +140,16 @@ bool Player_V3M::checkMusicAvailable() {
 
 bool Player_V3M::loadMusic(const byte *ptr) {
 	Common::MacResManager resource;
-	if (!resource.open("Loom\xAA") && !resource.open("Loom\xE2\x84\xA2") && !resource.open("Loom")) {
+	bool found = false;
+
+	for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) {
+		if (resource.open(loomFileNames[i])) {
+			found = true;
+			break;
+		}
+	}
+
+	if (!found) {
 		return false;
 	}
 


Commit: 9e995991e7598f235392700f808ed8aebdc64b75
    https://github.com/scummvm/scummvm/commit/9e995991e7598f235392700f808ed8aebdc64b75
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-18T22:18:05-08:00

Commit Message:
SCUMM: Fix crash when Macintosh instruments aren't available

Initialise _channel[] even when the instruments aren't available.
Otherwise, ScummVM will crash in a number of places including,
but not limited to, when loading savegames.

Changed paths:
    engines/scumm/player_mac.cpp



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index ef97c2d..6cfbec3 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -43,11 +43,6 @@ Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChan
 }
 
 void Player_Mac::init() {
-	if (!checkMusicAvailable()) {
-		_channel = NULL;
-		return;
-	}
-
 	_channel = new Player_Mac::Channel[_numberOfChannels];
 
 	int i;
@@ -89,6 +84,10 @@ void Player_Mac::init() {
 
 	setMusicVolume(255);
 
+	if (!checkMusicAvailable()) {
+		return;
+	}
+
 	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
 }
 


Commit: fc0288e9d5928597b2c9f3d21ebc467b9255239d
    https://github.com/scummvm/scummvm/commit/fc0288e9d5928597b2c9f3d21ebc467b9255239d
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-18T22:25:42-08:00

Commit Message:
SCUMM: Simplify checkMusicAvailable() a bit

Changed paths:
    engines/scumm/player_v3m.cpp
    engines/scumm/player_v5m.cpp



diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp
index 6a675f2..35d2aaa 100644
--- a/engines/scumm/player_v3m.cpp
+++ b/engines/scumm/player_v3m.cpp
@@ -118,24 +118,18 @@ static const char *loomFileNames[] = {
 
 bool Player_V3M::checkMusicAvailable() {
 	Common::MacResManager resource;
-	bool found = false;
 
 	for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) {
 		if (resource.exists(loomFileNames[i])) {
-			found = true;
-			break;
+			return true;
 		}
 	}
 		
-	if (!found) {
-		GUI::MessageDialog dialog(_(
-			"Could not find the 'Loom' Macintosh executable to read the\n"
-			"instruments from. Music will be disabled."), _("OK"));
-		dialog.runModal();
-		return false;
-	}
-
-	return true;
+	GUI::MessageDialog dialog(_(
+		"Could not find the 'Loom' Macintosh executable to read the\n"
+		"instruments from. Music will be disabled."), _("OK"));
+	dialog.runModal();
+	return false;
 }
 
 bool Player_V3M::loadMusic(const byte *ptr) {
diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp
index 26cfb0e..254b2c7 100644
--- a/engines/scumm/player_v5m.cpp
+++ b/engines/scumm/player_v5m.cpp
@@ -88,15 +88,16 @@ Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
 
 bool Player_V5M::checkMusicAvailable() {
 	Common::MacResManager resource;
-	if (!resource.exists("Monkey Island")) {
-		GUI::MessageDialog dialog(_(
-			"Could not find the 'Monkey Island' Macintosh executable to read the\n"
-			"instruments from. Music will be disabled."), _("OK"));
-		dialog.runModal();
-		return false;
+
+	if (resource.exists("Monkey Island")) {
+		return true;
 	}
 
-	return true;
+	GUI::MessageDialog dialog(_(
+		"Could not find the 'Monkey Island' Macintosh executable to read the\n"
+		"instruments from. Music will be disabled."), _("OK"));
+	dialog.runModal();
+	return false;
 }
 
 bool Player_V5M::loadMusic(const byte *ptr) {


Commit: 2aa8d0d65d784f0f4d9f5a8471626b5ffa046b90
    https://github.com/scummvm/scummvm/commit/2aa8d0d65d784f0f4d9f5a8471626b5ffa046b90
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-18T23:40:51-08:00

Commit Message:
SCUMM: Try harder to find Mac Monkey Island instruments

At least for me, hfsutils turns spaces into underscores so try both
"Monkey Island" and "Monkey_Island".

Changed paths:
    engines/scumm/player_v5m.cpp



diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp
index 254b2c7..1cb9831 100644
--- a/engines/scumm/player_v5m.cpp
+++ b/engines/scumm/player_v5m.cpp
@@ -86,11 +86,21 @@ Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
 	assert(_vm->_game.id == GID_MONKEY);
 }
 
+// Try both with and without underscore in the filename, because hfsutils may
+// turn the space into an underscore. At least, it did for me.
+
+static const char *monkeyIslandFileNames[] = {
+	"Monkey Island",
+	"Monkey_Island"
+};
+
 bool Player_V5M::checkMusicAvailable() {
 	Common::MacResManager resource;
 
-	if (resource.exists("Monkey Island")) {
-		return true;
+	for (int i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) {
+		if (resource.exists(monkeyIslandFileNames[i])) {
+			return true;
+		}
 	}
 
 	GUI::MessageDialog dialog(_(
@@ -102,11 +112,19 @@ bool Player_V5M::checkMusicAvailable() {
 
 bool Player_V5M::loadMusic(const byte *ptr) {
 	Common::MacResManager resource;
-	if (!resource.open("Monkey Island")) {
-		return false;
+	bool found = false;
+	uint i;
+
+	for (i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) {
+		if (resource.open(monkeyIslandFileNames[i])) {
+			found = true;
+			break;
+		}
 	}
 
-	uint i;
+	if (!found) {
+		return false;
+	}
 
 	ptr += 8;
 	// TODO: Decipher the unknown bytes in the header. For now, skip 'em


Commit: d06f69f94b6cb2fcf51c3d0018a960d5d0c50051
    https://github.com/scummvm/scummvm/commit/d06f69f94b6cb2fcf51c3d0018a960d5d0c50051
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-22T21:01:30-08:00

Commit Message:
SCUMM: Fix memory leak when loading old MI1 Mac savegames

I completely forgot to delete the dummy iMUSE object after using it
to skip over the old music save information. Thanks to Lordhoto for
pointing this out.

Changed paths:
    engines/scumm/player_mac.cpp



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index 6cfbec3..fa0dd9d 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -104,6 +104,7 @@ void Player_Mac::saveLoadWithSerializer(Serializer *ser) {
 		if (_vm->_game.id == GID_MONKEY && ser->isLoading()) {
 			IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL);
 			dummyImuse->save_or_load(ser, _vm, false);
+			delete dummyImuse;
 		}
 	} else {
 		static const SaveLoadEntry musicEntries[] = {


Commit: 076bcbc5c6b4c0f34e919dccfb0d407e88138560
    https://github.com/scummvm/scummvm/commit/076bcbc5c6b4c0f34e919dccfb0d407e88138560
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-22T21:54:25-08:00

Commit Message:
SCUMM: Added TODO comment about Mac MI1 music

Some notes in the main theme are very staccato, and this could
possibly explain why.

Changed paths:
    engines/scumm/player_mac.cpp



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index fa0dd9d..a91050e 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -298,6 +298,15 @@ uint32 Player_Mac::durationToSamples(uint16 duration) {
 }
 
 int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) {
+	// TODO: Monkey Island 1 uses both note values 0 and 1 as rests.
+	// Perhaps 0 means an abrupt end of the current note, while 1 means it
+	// should drop off gradually? One of the voices in the main theme
+	// sounds a lot more staccato than what I hear in a Mac emulator. (But
+	// it's hard to tell since that emulator has problems with the music.)
+	// Also, some instruments (though not this particular one) have data
+	// after the loop end point, which could possible be used to fade out
+	// the instrument.
+
 	if (note > 1) {
 		const int pitchIdx = note + 60 - instrument->_baseFreq;
 		// I don't want to use floating-point arithmetics here, but I


Commit: d3cf4d10f27a6349f08bee66713e9032d81cb8f8
    https://github.com/scummvm/scummvm/commit/d3cf4d10f27a6349f08bee66713e9032d81cb8f8
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2012-11-23T16:39:16-08:00

Commit Message:
SCUMM: Handle note value 1 as "hold current note" in MI1 Mac

After listening to the original music in a Mac emulator (which
unfortunately doesn't handle the music very well), I can only
conclude that note value 1 means the note should continue playing.
At first I thought maybe it was supposed to fade the current note,
or perhaps change its volume, but I can't hear any traces of
either. So I'm going to assume it just means "hold the current
note", though for the life of me I cannot think of any valid
reason for such a command. So it may be wrong, but it sounds
closer to the emulator than it did before.

Changed paths:
    engines/scumm/player_mac.cpp
    engines/scumm/player_mac.h
    engines/scumm/player_v3m.cpp
    engines/scumm/player_v5m.cpp



diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp
index a91050e..bbb97d3 100644
--- a/engines/scumm/player_mac.cpp
+++ b/engines/scumm/player_mac.cpp
@@ -31,13 +31,14 @@
 
 namespace Scumm {
 
-Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask)
+Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds)
 	: _vm(scumm),
 	  _mixer(mixer),
 	  _sampleRate(_mixer->getOutputRate()),
 	  _soundPlaying(-1),
 	  _numberOfChannels(numberOfChannels),
-	  _channelMask(channelMask) {
+	  _channelMask(channelMask),
+	  _fadeNoteEnds(fadeNoteEnds) {
 	assert(scumm);
 	assert(mixer);
 }
@@ -298,16 +299,7 @@ uint32 Player_Mac::durationToSamples(uint16 duration) {
 }
 
 int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) {
-	// TODO: Monkey Island 1 uses both note values 0 and 1 as rests.
-	// Perhaps 0 means an abrupt end of the current note, while 1 means it
-	// should drop off gradually? One of the voices in the main theme
-	// sounds a lot more staccato than what I hear in a Mac emulator. (But
-	// it's hard to tell since that emulator has problems with the music.)
-	// Also, some instruments (though not this particular one) have data
-	// after the loop end point, which could possible be used to fade out
-	// the instrument.
-
-	if (note > 1) {
+	if (note > 0) {
 		const int pitchIdx = note + 60 - instrument->_baseFreq;
 		// I don't want to use floating-point arithmetics here, but I
 		// ran into overflow problems with the church music in Monkey
@@ -356,7 +348,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) {
 			}
 			generated = MIN(_channel[i]._remaining, samplesLeft);
 			if (_channel[i]._velocity != 0) {
-				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining);
+				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining, _fadeNoteEnds);
 			}
 			ptr += generated;
 			samplesLeft -= generated;
@@ -375,7 +367,7 @@ int Player_Mac::readBuffer(int16 *data, const int numSamples) {
 	return numSamples;
 }
 
-void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote) {
+void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds) {
 	int samplesLeft = numSamples;
 	while (samplesLeft) {
 		_subPos += pitchModifier;
@@ -389,16 +381,23 @@ void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int
 
 		int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255;
 
-		// Fade out the last 100 samples on each note. Even at low
-		// output sample rates this is just a fraction of a second,
-		// but it gets rid of distracting "pops" at the end when the
-		// sample would otherwise go abruptly from something to
-		// nothing. This was particularly noticeable on the distaff
-		// notes in Loom.
-
-		remainingSamplesOnNote--;
-		if (remainingSamplesOnNote < 100) {
-			newSample = (newSample * remainingSamplesOnNote) / 100;
+		if (fadeNoteEnds) {
+			// Fade out the last 100 samples on each note. Even at
+			// low output sample rates this is just a fraction of a
+			// second, but it gets rid of distracting "pops" at the
+			// end when the sample would otherwise go abruptly from
+			// something to nothing. This was particularly
+			// noticeable on the distaff notes in Loom.
+			//
+			// The reason it's conditional is that Monkey Island
+			// appears to have a "hold current note" command, and
+			// if we fade out the current note in that case we
+			// will actually introduce new "pops".
+
+			remainingSamplesOnNote--;
+			if (remainingSamplesOnNote < 100) {
+				newSample = (newSample * remainingSamplesOnNote) / 100;
+			}
 		}
 
 		int sample = *data + newSample;
diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h
index c46495c..09307b4 100644
--- a/engines/scumm/player_mac.h
+++ b/engines/scumm/player_mac.h
@@ -44,7 +44,7 @@ class ScummEngine;
  */
 class Player_Mac : public Audio::AudioStream, public MusicEngine {
 public:
-	Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask);
+	Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds);
 	virtual ~Player_Mac();
 
 	void init();
@@ -90,12 +90,13 @@ private:
 			_subPos = 0;
 		}
 
-		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote);
+		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds);
 	};
 
 	int _pitchTable[128];
 	int _numberOfChannels;
 	int _channelMask;
+	bool _fadeNoteEnds;
 
 	virtual bool checkMusicAvailable() { return false; }
 	virtual bool loadMusic(const byte *ptr) { return false; }
diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp
index 35d2aaa..e614631 100644
--- a/engines/scumm/player_v3m.cpp
+++ b/engines/scumm/player_v3m.cpp
@@ -97,7 +97,7 @@
 namespace Scumm {
 
 Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
-	: Player_Mac(scumm, mixer, 5, 0x1E) {
+	: Player_Mac(scumm, mixer, 5, 0x1E, true) {
 	assert(_vm->_game.id == GID_LOOM);
 
 	// Channel 0 seems to be what was played on low-end macs, that couldn't
diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp
index 1cb9831..500f3bb 100644
--- a/engines/scumm/player_v5m.cpp
+++ b/engines/scumm/player_v5m.cpp
@@ -82,7 +82,7 @@
 namespace Scumm {
 
 Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
-	: Player_Mac(scumm, mixer, 3, 0x07) {
+	: Player_Mac(scumm, mixer, 3, 0x07, false) {
 	assert(_vm->_game.id == GID_MONKEY);
 }
 
@@ -190,7 +190,6 @@ bool Player_V5M::loadMusic(const byte *ptr) {
 }
 
 bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) {
-	_channel[ch]._instrument.newNote();
 	if (_channel[ch]._pos >= _channel[ch]._length) {
 		if (!_channel[ch]._looped) {
 			_channel[ch]._notesLeft = false;
@@ -206,9 +205,31 @@ bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &
 	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;


Commit: 97d7bf9b9aaa1aac4939d2a690f65fde7e844b63
    https://github.com/scummvm/scummvm/commit/97d7bf9b9aaa1aac4939d2a690f65fde7e844b63
Author: Johannes Schickel (lordhoto at gmail.com)
Date: 2012-12-14T11:57:49-08:00

Commit Message:
Merge pull request #291 from eriktorbjorn/mac-mi-music-final

SCUMM: Add support for Macintosh music in Monkey Island 1 and Loom

Changed paths:
  A engines/scumm/player_mac.cpp
  A engines/scumm/player_mac.h
  A engines/scumm/player_v3m.cpp
  A engines/scumm/player_v3m.h
  A engines/scumm/player_v5m.cpp
  A engines/scumm/player_v5m.h
    engines/scumm/imuse/imuse.cpp
    engines/scumm/imuse/imuse.h
    engines/scumm/imuse/imuse_internal.h
    engines/scumm/module.mk
    engines/scumm/music.h
    engines/scumm/saveload.cpp
    engines/scumm/saveload.h
    engines/scumm/scumm.cpp
    engines/scumm/sound.cpp









More information about the Scummvm-git-logs mailing list