[Scummvm-cvs-logs] scummvm master -> 651bf899399de2b5c2d8e62a5bb4964b037950d2

bluegr bluegr at gmail.com
Fri Nov 1 05:58:48 CET 2013


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

Summary:
651bf89939 SCUMM: Move all players to a separate "player" directory


Commit: 651bf899399de2b5c2d8e62a5bb4964b037950d2
    https://github.com/scummvm/scummvm/commit/651bf899399de2b5c2d8e62a5bb4964b037950d2
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2013-10-31T21:57:53-07:00

Commit Message:
SCUMM: Move all players to a separate "player" directory

There are 34 player .cpp/.h player files, so they have been placed in
their own directory, to logically separate them from the rest of the
engine

Changed paths:
  A engines/scumm/player/ad.cpp
  A engines/scumm/player/ad.h
  A engines/scumm/player/apple2.cpp
  A engines/scumm/player/apple2.h
  A engines/scumm/player/mac.cpp
  A engines/scumm/player/mac.h
  A engines/scumm/player/mod.cpp
  A engines/scumm/player/mod.h
  A engines/scumm/player/nes.cpp
  A engines/scumm/player/nes.h
  A engines/scumm/player/pce.cpp
  A engines/scumm/player/pce.h
  A engines/scumm/player/sid.cpp
  A engines/scumm/player/sid.h
  A engines/scumm/player/towns.cpp
  A engines/scumm/player/towns.h
  A engines/scumm/player/v1.cpp
  A engines/scumm/player/v1.h
  A engines/scumm/player/v2.cpp
  A engines/scumm/player/v2.h
  A engines/scumm/player/v2a.cpp
  A engines/scumm/player/v2a.h
  A engines/scumm/player/v2base.cpp
  A engines/scumm/player/v2base.h
  A engines/scumm/player/v2cms.cpp
  A engines/scumm/player/v2cms.h
  A engines/scumm/player/v3a.cpp
  A engines/scumm/player/v3a.h
  A engines/scumm/player/v3m.cpp
  A engines/scumm/player/v3m.h
  A engines/scumm/player/v4a.cpp
  A engines/scumm/player/v4a.h
  A engines/scumm/player/v5m.cpp
  A engines/scumm/player/v5m.h
  R engines/scumm/player_ad.cpp
  R engines/scumm/player_ad.h
  R engines/scumm/player_apple2.cpp
  R engines/scumm/player_apple2.h
  R engines/scumm/player_mac.cpp
  R engines/scumm/player_mac.h
  R engines/scumm/player_mod.cpp
  R engines/scumm/player_mod.h
  R engines/scumm/player_nes.cpp
  R engines/scumm/player_nes.h
  R engines/scumm/player_pce.cpp
  R engines/scumm/player_pce.h
  R engines/scumm/player_sid.cpp
  R engines/scumm/player_sid.h
  R engines/scumm/player_towns.cpp
  R engines/scumm/player_towns.h
  R engines/scumm/player_v1.cpp
  R engines/scumm/player_v1.h
  R engines/scumm/player_v2.cpp
  R engines/scumm/player_v2.h
  R engines/scumm/player_v2a.cpp
  R engines/scumm/player_v2a.h
  R engines/scumm/player_v2base.cpp
  R engines/scumm/player_v2base.h
  R engines/scumm/player_v2cms.cpp
  R engines/scumm/player_v2cms.h
  R engines/scumm/player_v3a.cpp
  R engines/scumm/player_v3a.h
  R engines/scumm/player_v3m.cpp
  R engines/scumm/player_v3m.h
  R engines/scumm/player_v4a.cpp
  R engines/scumm/player_v4a.h
  R engines/scumm/player_v5m.cpp
  R engines/scumm/player_v5m.h
    engines/scumm/module.mk
    engines/scumm/saveload.cpp
    engines/scumm/script_v5.cpp
    engines/scumm/scumm.cpp
    engines/scumm/sound.cpp



diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index a377ad3..3c06710 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -35,23 +35,23 @@ MODULE_OBJS := \
 	midiparser_ro.o \
 	object.o \
 	palette.o \
-	player_ad.o \
-	player_apple2.o \
-	player_mac.o \
-	player_mod.o \
-	player_nes.o \
-	player_pce.o \
-	player_sid.o \
-	player_towns.o \
-	player_v1.o \
-	player_v2.o \
-	player_v2a.o \
-	player_v2base.o \
-	player_v2cms.o \
-	player_v3a.o \
-	player_v3m.o \
-	player_v4a.o \
-	player_v5m.o \
+	player/ad.o \
+	player/apple2.o \
+	player/mac.o \
+	player/mod.o \
+	player/nes.o \
+	player/pce.o \
+	player/sid.o \
+	player/towns.o \
+	player/v1.o \
+	player/v2.o \
+	player/v2a.o \
+	player/v2base.o \
+	player/v2cms.o \
+	player/v3a.o \
+	player/v3m.o \
+	player/v4a.o \
+	player/v5m.o \
 	resource_v2.o \
 	resource_v3.o \
 	resource_v4.o \
diff --git a/engines/scumm/player/ad.cpp b/engines/scumm/player/ad.cpp
new file mode 100644
index 0000000..d3cd0483
--- /dev/null
+++ b/engines/scumm/player/ad.cpp
@@ -0,0 +1,959 @@
+/* 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 "scumm/player/ad.h"
+#include "scumm/imuse/imuse.h"
+#include "scumm/scumm.h"
+#include "scumm/resource.h"
+
+#include "audio/fmopl.h"
+
+#include "common/textconsole.h"
+#include "common/config-manager.h"
+
+namespace Scumm {
+
+#define AD_CALLBACK_FREQUENCY 472
+
+Player_AD::Player_AD(ScummEngine *scumm, Audio::Mixer *mixer)
+	: _vm(scumm), _mixer(mixer), _rate(mixer->getOutputRate()) {
+	_opl2 = OPL::Config::create();
+	if (!_opl2->init(_rate)) {
+		error("Could not initialize OPL2 emulator");
+	}
+
+	_samplesPerCallback = _rate / AD_CALLBACK_FREQUENCY;
+	_samplesPerCallbackRemainder = _rate % AD_CALLBACK_FREQUENCY;
+	_samplesTillCallback = 0;
+	_samplesTillCallbackRemainder = 0;
+
+	memset(_registerBackUpTable, 0, sizeof(_registerBackUpTable));
+	writeReg(0x01, 0x00);
+	writeReg(0xBD, 0x00);
+	writeReg(0x08, 0x00);
+	writeReg(0x01, 0x20);
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+	_engineMusicTimer = 0;
+	_soundPlaying = -1;
+
+	_curOffset = 0;
+
+	_sfxTimer = 4;
+	_rndSeed = 1;
+
+	memset(_channels, 0, sizeof(_channels));
+	memset(_sfxResource, 0, sizeof(_sfxResource));
+	memset(_sfxPriority, 0, sizeof(_sfxPriority));
+}
+
+Player_AD::~Player_AD() {
+	_mixer->stopHandle(_soundHandle);
+
+	stopAllSounds();
+	Common::StackLock lock(_mutex);
+	delete _opl2;
+	_opl2 = 0;
+}
+
+void Player_AD::setMusicVolume(int vol) {
+	// HACK: We ignore the parameter and set up the volume specified in the
+	// config manager. This allows us to differentiate between music and sfx
+	// volume changes.
+	setupVolume();
+}
+
+void Player_AD::startSound(int sound) {
+	Common::StackLock lock(_mutex);
+
+	// Query the sound resource
+	const byte *res = _vm->getResourceAddress(rtSound, sound);
+
+	if (res[2] == 0x80) {
+		// Stop the current sounds
+		stopAllSounds();
+
+		// Lock the new music resource
+		_soundPlaying = sound;
+		_vm->_res->lock(rtSound, _soundPlaying);
+
+		// Start the new music resource
+		_resource = res;
+		startMusic();
+	} else {
+		// Only try to start a sfx when no music is playing.
+		if (_soundPlaying == -1) {
+			const byte priority = res[0];
+			const byte channel  = res[1];
+
+			// Check for out of bounds access
+			if (channel >= 3) {
+				warning("AdLib sfx resource %d uses channel %d", sound, channel);
+				return;
+			}
+
+			// Check whether the channel is free or the priority of the new
+			// sfx resource is above the old one.
+			if (_channels[channel * 3 + 0].state
+			    || _channels[channel * 3 + 1].state
+			    || _channels[channel * 3 + 2].state) {
+				if (_sfxPriority[channel] > priority) {
+					return;
+				}
+			}
+
+			// Lock the new resource
+			_sfxResource[channel] = sound;
+			_sfxPriority[channel] = priority;
+			_vm->_res->lock(rtSound, sound);
+
+			// Start the actual sfx resource
+			_resource = res;
+			startSfx();
+		}
+	}
+
+	// Setup the sound volume
+	setupVolume();
+}
+
+void Player_AD::stopSound(int sound) {
+	Common::StackLock lock(_mutex);
+
+	if (sound == _soundPlaying) {
+		stopAllSounds();
+	} else {
+		for (int i = 0; i < 3; ++i) {
+			if (_sfxResource[i] == sound) {
+				if (_channels[i * 3 + 0].state
+				    || _channels[i * 3 + 1].state
+				    || _channels[i * 3 + 2].state) {
+					// Unlock the sound resource
+					_vm->_res->unlock(rtSound, sound);
+
+					// Stop the actual sfx playback
+					_channels[i * 3 + 0].state = 0;
+					_channels[i * 3 + 1].state = 0;
+					_channels[i * 3 + 2].state = 0;
+					clearChannel(i * 3 + 0);
+					clearChannel(i * 3 + 1);
+					clearChannel(i * 3 + 2);
+				}
+			}
+		}
+	}
+}
+
+void Player_AD::stopAllSounds() {
+	Common::StackLock lock(_mutex);
+
+	// Unlock the music resource if present
+	if (_soundPlaying != -1) {
+		_vm->_res->unlock(rtSound, _soundPlaying);
+		_soundPlaying = -1;
+	}
+
+	// Stop the music playback
+	_curOffset = 0;
+
+	// Unloack all used sfx resources
+	for (int i = 0; i < 3; ++i) {
+		if (_channels[i * 3 + 0].state || _channels[i * 3 + 1].state || _channels[i * 3 + 2].state) {
+			_vm->_res->unlock(rtSound, _sfxResource[i]);
+		}
+	}
+
+	// Reset all the sfx channels
+	for (int i = 0; i < 9; ++i) {
+		_channels[i].state = 0;
+		clearChannel(i);
+	}
+
+	writeReg(0xBD, 0x00);
+}
+
+int Player_AD::getMusicTimer() {
+	return _engineMusicTimer;
+}
+
+int Player_AD::getSoundStatus(int sound) const {
+	return (sound == _soundPlaying);
+}
+
+void Player_AD::saveLoadWithSerializer(Serializer *ser) {
+	Common::StackLock lock(_mutex);
+
+	if (ser->getVersion() < VER(95)) {
+		IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL);
+		dummyImuse->save_or_load(ser, _vm, false);
+		delete dummyImuse;
+		return;
+	}
+
+	// TODO: Be nicer than the original and save the data to continue the
+	// currently played sound resources on load?
+}
+
+int Player_AD::readBuffer(int16 *buffer, const int numSamples) {
+	Common::StackLock lock(_mutex);
+
+	int len = numSamples;
+
+	while (len > 0) {
+		if (!_samplesTillCallback) {
+			// Run the update callback for music or sfx depending on which is
+			// active.
+			if (_curOffset) {
+				updateMusic();
+			} else {
+				updateSfx();
+			}
+
+			_samplesTillCallback = _samplesPerCallback;
+			_samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
+			if (_samplesTillCallbackRemainder >= AD_CALLBACK_FREQUENCY) {
+				++_samplesTillCallback;
+				_samplesTillCallbackRemainder -= AD_CALLBACK_FREQUENCY;
+			}
+		}
+
+		const int samplesToRead = MIN(len, _samplesTillCallback);
+		_opl2->readBuffer(buffer, samplesToRead);
+
+		buffer += samplesToRead;
+		len -= samplesToRead;
+		_samplesTillCallback -= samplesToRead;
+	}
+
+	return numSamples;
+}
+
+void Player_AD::setupVolume() {
+	// Setup the correct volume
+	int soundVolumeMusic = CLIP<int>(ConfMan.getInt("music_volume"), 0, Audio::Mixer::kMaxChannelVolume);
+	int soundVolumeSfx = CLIP<int>(ConfMan.getInt("sfx_volume"), 0, Audio::Mixer::kMaxChannelVolume);
+	if (ConfMan.hasKey("mute")) {
+		if (ConfMan.getBool("mute")) {
+			soundVolumeMusic = 0;
+			soundVolumeSfx = 0;
+		}
+	}
+
+	// In case a music is being played set the music volume. Set the sfx
+	// volume otherwise. This is safe because in the latter case either
+	// sfx are playing or there is no sound being played at all.
+	if (_soundPlaying != -1) {
+		_mixer->setChannelVolume(_soundHandle, soundVolumeMusic);
+	} else {
+		_mixer->setChannelVolume(_soundHandle, soundVolumeSfx);
+	}
+}
+
+void Player_AD::writeReg(int r, int v) {
+	if (r >= 0 && r < ARRAYSIZE(_registerBackUpTable)) {
+		_registerBackUpTable[r] = v;
+	}
+	_opl2->writeReg(r, v);
+}
+
+uint8 Player_AD::readReg(int r) const {
+	if (r >= 0 && r < ARRAYSIZE(_registerBackUpTable)) {
+		return _registerBackUpTable[r];
+	} else {
+		return 0;
+	}
+}
+
+void Player_AD::setupChannel(const uint channel, const byte *instrOffset) {
+	instrOffset += 2;
+	writeReg(0xC0 + channel, *instrOffset++);
+	setupOperator(_operatorOffsetTable[channel * 2 + 0], instrOffset);
+	setupOperator(_operatorOffsetTable[channel * 2 + 1], instrOffset);
+}
+
+void Player_AD::setupOperator(const uint opr, const byte *&instrOffset) {
+	writeReg(0x20 + opr, *instrOffset++);
+	writeReg(0x40 + opr, *instrOffset++);
+	writeReg(0x60 + opr, *instrOffset++);
+	writeReg(0x80 + opr, *instrOffset++);
+	writeReg(0xE0 + opr, *instrOffset++);
+}
+
+const int Player_AD::_operatorOffsetTable[18] = {
+	 0,  3,  1,  4,
+	 2,  5,  8, 11,
+	 9, 12, 10, 13,
+	16, 19, 17, 20,
+	18, 21
+};
+
+// Music
+
+void Player_AD::startMusic() {
+	memset(_instrumentOffset, 0, sizeof(_instrumentOffset));
+	memset(_channelLastEvent, 0, sizeof(_channelLastEvent));
+	memset(_channelFrequency, 0, sizeof(_channelFrequency));
+	memset(_channelB0Reg, 0, sizeof(_channelB0Reg));
+
+	_voiceChannels = 0;
+	uint instruments = _resource[10];
+	for (uint i = 0; i < instruments; ++i) {
+		const int instrIndex = _resource[11 + i] - 1;
+		if (0 <= instrIndex && instrIndex < 16) {
+			_instrumentOffset[instrIndex] = i * 16 + 16 + 3;
+			_voiceChannels |= _resource[_instrumentOffset[instrIndex] + 13];
+		}
+	}
+
+	if (_voiceChannels) {
+		_mdvdrState = 0x20;
+		_voiceChannels = 6;
+	} else {
+		_mdvdrState = 0;
+		_voiceChannels = 9;
+	}
+
+	_curOffset = 0x93;
+	// TODO: is this the same for Loom?
+	_nextEventTimer = 40;
+	_engineMusicTimer = 0;
+	_internalMusicTimer = 0;
+	_musicTimer = 0;
+
+	writeReg(0xBD, _mdvdrState);
+
+	const bool isLoom = (_vm->_game.id == GID_LOOM);
+	_timerLimit = isLoom ? 473 : 256;
+	_musicTicks = _resource[3] * (isLoom ? 2 : 1);
+	_loopFlag = (_resource[4] == 0);
+	_musicLoopStart = READ_LE_UINT16(_resource + 5);
+}
+
+void Player_AD::updateMusic() {
+	_musicTimer += _musicTicks;
+	if (_musicTimer < _timerLimit) {
+		return;
+	}
+	_musicTimer -= _timerLimit;
+
+	++_internalMusicTimer;
+	if (_internalMusicTimer > 120) {
+		_internalMusicTimer = 0;
+		++_engineMusicTimer;
+	}
+
+	--_nextEventTimer;
+	if (_nextEventTimer) {
+		return;
+	}
+
+	while (true) {
+		uint command = _resource[_curOffset++];
+		if (command == 0xFF) {
+			// META EVENT
+			// Get the command number.
+			command = _resource[_curOffset++];
+			if (command == 47) {
+				// End of track
+				if (_loopFlag) {
+					// In case the track is looping jump to the start.
+					_curOffset = _musicLoopStart;
+					_nextEventTimer = 0;
+				} else {
+					// Otherwise completely stop playback.
+					stopAllSounds();
+				}
+				return;
+			} else if (command == 88) {
+				// This is proposedly a debug information insertion. The CMS
+				// player code handles this differently, but is still using
+				// the same resources...
+				_curOffset += 5;
+			} else if (command == 81) {
+				// Change tempo. This is used exclusively in Loom.
+				const uint timing = _resource[_curOffset + 2] | (_resource[_curOffset + 1] << 8);
+				_musicTicks = 0x73000 / timing;
+				command = _resource[_curOffset++];
+				_curOffset += command;
+			} else {
+				// In case an unknown meta event occurs just skip over the
+				// data by using the length supplied.
+				command = _resource[_curOffset++];
+				_curOffset += command;
+			}
+		} else {
+			if (command >= 0x90) {
+				// NOTE ON
+				// Extract the channel number and save it in command.
+				command -= 0x90;
+
+				const uint instrOffset = _instrumentOffset[command];
+				if (instrOffset) {
+					if (_resource[instrOffset + 13] != 0) {
+						setupRhythm(_resource[instrOffset + 13], instrOffset);
+					} else {
+						int channel = findFreeChannel();
+						if (channel != -1) {
+							noteOff(channel);
+							setupChannel(channel, instrOffset);
+							_channelLastEvent[channel] = command + 0x90;
+							_channelFrequency[channel] = _resource[_curOffset];
+							setupFrequency(channel, _resource[_curOffset]);
+						}
+					}
+				}
+			} else {
+				// NOTE OFF
+				const uint note = _resource[_curOffset];
+				command += 0x10;
+
+				// Find the output channel which plays the note.
+				uint channel = 0xFF;
+				for (uint i = 0; i < _voiceChannels; ++i) {
+					if (_channelFrequency[i] == note && _channelLastEvent[i] == command) {
+						channel = i;
+						break;
+					}
+				}
+
+				if (channel != 0xFF) {
+					// In case a output channel playing the note was found,
+					// stop it.
+					noteOff(channel);
+				} else {
+					// In case there is no such note this will disable the
+					// rhythm instrument played on the channel.
+					command -= 0x90;
+					const uint instrOffset = _instrumentOffset[command];
+					if (instrOffset && _resource[instrOffset + 13] != 0) {
+						const uint rhythmInstr = _resource[instrOffset + 13];
+						if (rhythmInstr < 6) {
+							_mdvdrState &= _mdvdrTable[rhythmInstr] ^ 0xFF;
+							writeReg(0xBD, _mdvdrState);
+						}
+					}
+				}
+			}
+
+			_curOffset += 2;
+		}
+
+		// In case there is a delay till the next event stop handling.
+		if (_resource[_curOffset] != 0) {
+			break;
+		}
+		++_curOffset;
+	}
+
+	_nextEventTimer = _resource[_curOffset++];
+	if (_nextEventTimer & 0x80) {
+		_nextEventTimer -= 0x80;
+		_nextEventTimer <<= 7;
+		_nextEventTimer |= _resource[_curOffset++];
+	}
+
+	_nextEventTimer >>= (_vm->_game.id == GID_LOOM) ? 2 : 1;
+	if (!_nextEventTimer) {
+		_nextEventTimer = 1;
+	}
+}
+
+void Player_AD::noteOff(uint channel) {
+	_channelLastEvent[channel] = 0;
+	writeReg(0xB0 + channel, _channelB0Reg[channel] & 0xDF);
+}
+
+int Player_AD::findFreeChannel() {
+	for (uint i = 0; i < _voiceChannels; ++i) {
+		if (!_channelLastEvent[i]) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+void Player_AD::setupFrequency(uint channel, int8 frequency) {
+	frequency -= 31;
+	if (frequency < 0) {
+		frequency = 0;
+	}
+
+	uint octave = 0;
+	while (frequency >= 12) {
+		frequency -= 12;
+		++octave;
+	}
+
+	const uint noteFrequency = _noteFrequencies[frequency];
+	octave <<= 2;
+	octave |= noteFrequency >> 8;
+	octave |= 0x20;
+	writeReg(0xA0 + channel, noteFrequency & 0xFF);
+	_channelB0Reg[channel] = octave;
+	writeReg(0xB0 + channel, octave);
+}
+
+void Player_AD::setupRhythm(uint rhythmInstr, uint instrOffset) {
+	if (rhythmInstr == 1) {
+		setupChannel(6, instrOffset);
+		writeReg(0xA6, _resource[instrOffset++]);
+		writeReg(0xB6, _resource[instrOffset] & 0xDF);
+		_mdvdrState |= 0x10;
+		writeReg(0xBD, _mdvdrState);
+	} else if (rhythmInstr < 6) {
+		const byte *secondOperatorOffset = _resource + instrOffset + 8;
+		setupOperator(_rhythmOperatorTable[rhythmInstr], secondOperatorOffset);
+		writeReg(0xA0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset++]);
+		writeReg(0xB0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset++] & 0xDF);
+		writeReg(0xC0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset]);
+		_mdvdrState |= _mdvdrTable[rhythmInstr];
+		writeReg(0xBD, _mdvdrState);
+	}
+}
+
+const uint Player_AD::_noteFrequencies[12] = {
+	0x200, 0x21E, 0x23F, 0x261,
+	0x285, 0x2AB, 0x2D4, 0x300,
+	0x32E, 0x35E, 0x390, 0x3C7
+};
+
+const uint Player_AD::_mdvdrTable[6] = {
+	0x00, 0x10, 0x08, 0x04, 0x02, 0x01
+};
+
+const uint Player_AD::_rhythmOperatorTable[6] = {
+	0x00, 0x00, 0x14, 0x12, 0x15, 0x11
+};
+
+const uint Player_AD::_rhythmChannelTable[6] = {
+	0x00, 0x00, 0x07, 0x08, 0x08, 0x07
+};
+
+// SFX
+
+void Player_AD::startSfx() {
+	writeReg(0xBD, 0x00);
+
+	// The second byte of the resource defines the logical channel where
+	// the sound effect should be played.
+	const int startChannel = _resource[1] * 3;
+
+	// Clear the channel.
+	_channels[startChannel + 0].state = 0;
+	_channels[startChannel + 1].state = 0;
+	_channels[startChannel + 2].state = 0;
+
+	clearChannel(startChannel + 0);
+	clearChannel(startChannel + 1);
+	clearChannel(startChannel + 2);
+
+	// Set up the first channel to pick up playback.
+	_channels[startChannel].currentOffset = _channels[startChannel].startOffset = _resource + 2;
+	_channels[startChannel].state = 1;
+
+	// Scan for the start of the other channels and set them up if required.
+	int curChannel = startChannel + 1;
+	const byte *bufferPosition = _resource + 2;
+	uint8 command = 0;
+	while ((command = *bufferPosition) != 0xFF) {
+		switch (command) {
+		case 1:
+			// INSTRUMENT DEFINITION
+			bufferPosition += 15;
+			break;
+
+		case 2:
+			// NOTE DEFINITION
+			bufferPosition += 11;
+			break;
+
+		case 0x80:
+			// LOOP
+			bufferPosition += 1;
+			break;
+
+		default:
+			// START OF CHANNEL
+			bufferPosition += 1;
+			_channels[curChannel].currentOffset = bufferPosition;
+			_channels[curChannel].startOffset = bufferPosition;
+			_channels[curChannel].state = 1;
+			++curChannel;
+			break;
+		}
+	}
+}
+
+void Player_AD::updateSfx() {
+	if (--_sfxTimer) {
+		return;
+	}
+	_sfxTimer = 4;
+
+	for (int i = 0; i <= 9; ++i) {
+		if (!_channels[i].state) {
+			continue;
+		}
+
+		updateChannel(i);
+	}
+}
+
+void Player_AD::clearChannel(int channel) {
+	writeReg(0xA0 + channel, 0x00);
+	writeReg(0xB0 + channel, 0x00);
+}
+
+void Player_AD::updateChannel(int channel) {
+	if (_channels[channel].state == 1) {
+		parseSlot(channel);
+	} else {
+		updateSlot(channel);
+	}
+}
+
+void Player_AD::parseSlot(int channel) {
+	while (true) {
+		const byte *curOffset = _channels[channel].currentOffset;
+
+		switch (*curOffset) {
+		case 1:
+			// INSTRUMENT DEFINITION
+			++curOffset;
+			_channels[channel].instrumentData[0] = *(curOffset + 0);
+			_channels[channel].instrumentData[1] = *(curOffset + 2);
+			_channels[channel].instrumentData[2] = *(curOffset + 9);
+			_channels[channel].instrumentData[3] = *(curOffset + 8);
+			_channels[channel].instrumentData[4] = *(curOffset + 4);
+			_channels[channel].instrumentData[5] = *(curOffset + 3);
+			_channels[channel].instrumentData[6] = 0;
+
+			setupChannel(channel, curOffset);
+
+			writeReg(0xA0 + channel, *(curOffset + 0));
+			writeReg(0xB0 + channel, *(curOffset + 1) & 0xDF);
+
+			_channels[channel].currentOffset += 15;
+			break;
+
+		case 2:
+			// NOTE DEFINITION
+			++curOffset;
+			_channels[channel].state = 2;
+			noteOffOn(channel);
+			parseNote(channel, 0, curOffset);
+			parseNote(channel, 1, curOffset);
+			return;
+
+		case 0x80:
+			// LOOP
+			_channels[channel].currentOffset = _channels[channel].startOffset;
+			break;
+
+		default:
+			// START OF CHANNEL
+			// When we encounter a start of another channel while playback
+			// it means that the current channel is finished. Thus, we will
+			// stop it.
+			clearChannel(channel);
+			_channels[channel].state = 0;
+
+			// If no channel of the sound effect is playing anymore, unlock
+			// the resource.
+			channel /= 3;
+			if (!_channels[channel + 0].state
+			    && !_channels[channel + 1].state
+			    && !_channels[channel + 2].state) {
+				_vm->_res->unlock(rtSound, _sfxResource[channel]);
+			}
+			return;
+		}
+	}
+}
+
+void Player_AD::updateSlot(int channel) {
+	const byte *curOffset = _channels[channel].currentOffset + 1;
+
+	for (int num = 0; num <= 1; ++num, curOffset += 5) {
+		if (!(*curOffset & 0x80)) {
+			continue;
+		}
+
+		const int note = channel * 2 + num;
+		bool updateNote = false;
+
+		if (_notes[note].state == 2) {
+			if (!--_notes[note].sustainTimer) {
+				updateNote = true;
+			}
+		} else {
+			updateNote = processNoteEnvelope(note, _notes[note].instrumentValue);
+
+			if (_notes[note].bias) {
+				writeRegisterSpecial(note, _notes[note].bias - _notes[note].instrumentValue, *curOffset & 0x07);
+			} else {
+				writeRegisterSpecial(note, _notes[note].instrumentValue, *curOffset & 0x07);
+			}
+		}
+
+		if (updateNote) {
+			if (processNote(note, curOffset)) {
+				if (!(*curOffset & 0x08)) {
+					_channels[channel].currentOffset += 11;
+					_channels[channel].state = 1;
+					continue;
+				} else if (*curOffset & 0x10) {
+					noteOffOn(channel);
+				}
+
+				_notes[note].state = -1;
+				processNote(note, curOffset);
+			}
+		}
+
+		if ((*curOffset & 0x20) && !--_notes[note].playTime) {
+			_channels[channel].currentOffset += 11;
+			_channels[channel].state = 1;
+		}
+	}
+}
+
+void Player_AD::parseNote(int channel, int num, const byte *offset) {
+	if (num) {
+		offset += 5;
+	}
+
+	if (*offset & 0x80) {
+		const int note = channel * 2 + num;
+		_notes[note].state = -1;
+		processNote(note, offset);
+		_notes[note].playTime = 0; 
+
+		if (*offset & 0x20) {
+			_notes[note].playTime = (*(offset + 4) >> 4) * 118;
+			_notes[note].playTime += (*(offset + 4) & 0x0F) * 8;
+		}
+	}
+}
+
+bool Player_AD::processNote(int note, const byte *offset) {
+	if (++_notes[note].state == 4) {
+		return true;
+	}
+
+	const int instrumentDataOffset = *offset & 0x07;
+	_notes[note].bias = _noteBiasTable[instrumentDataOffset];
+
+	uint8 instrumentDataValue = 0;
+	if (_notes[note].state == 0) {
+		instrumentDataValue = _channels[note / 2].instrumentData[instrumentDataOffset];
+	}
+
+	uint8 noteInstrumentValue = readRegisterSpecial(note, instrumentDataValue, instrumentDataOffset);
+	if (_notes[note].bias) {
+		noteInstrumentValue = _notes[note].bias - noteInstrumentValue;
+	}
+	_notes[note].instrumentValue = noteInstrumentValue;
+
+	if (_notes[note].state == 2) {
+		_notes[note].sustainTimer = _numStepsTable[*(offset + 3) >> 4];
+
+		if (*offset & 0x40) {
+			_notes[note].sustainTimer = (((getRnd() << 8) * _notes[note].sustainTimer) >> 16) + 1;
+		}
+	} else {
+		int timer1, timer2;
+		if (_notes[note].state == 3) {
+			timer1 = *(offset + 3) & 0x0F;
+			timer2 = 0;
+		} else {
+			timer1 = *(offset + _notes[note].state + 1) >> 4;
+			timer2 = *(offset + _notes[note].state + 1) & 0x0F;
+		}
+
+		int adjustValue = ((_noteAdjustTable[timer2] * _noteAdjustScaleTable[instrumentDataOffset]) >> 16) - noteInstrumentValue;
+		setupNoteEnvelopeState(note, _numStepsTable[timer1], adjustValue);
+	}
+
+	return false;
+}
+
+void Player_AD::noteOffOn(int channel) {
+	const uint8 regValue = readReg(0xB0 | channel);
+	writeReg(0xB0 | channel, regValue & 0xDF);
+	writeReg(0xB0 | channel, regValue | 0x20);
+}
+
+void Player_AD::writeRegisterSpecial(int note, uint8 value, int offset) {
+	if (offset == 6) {
+		return;
+	}
+
+	// Division by 2 extracts the channel number out of the note.
+	note /= 2;
+
+	uint8 regNum;
+	if (_useOperatorTable[offset]) {
+		regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + note * 2];
+	} else {
+		regNum = _channelOffsetTable[note];
+	}
+
+	regNum += _baseRegisterTable[offset];
+
+	uint8 regValue = readReg(regNum) & (~_registerMaskTable[offset]);
+	regValue |= value << _registerShiftTable[offset];
+
+	writeReg(regNum, regValue);
+}
+
+uint8 Player_AD::readRegisterSpecial(int note, uint8 defaultValue, int offset) {
+	if (offset == 6) {
+		return 0;
+	}
+
+	// Division by 2 extracts the channel number out of the note.
+	note /= 2;
+
+	uint8 regNum;
+	if (_useOperatorTable[offset]) {
+		regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + note * 2];
+	} else {
+		regNum = _channelOffsetTable[note];
+	}
+
+	regNum += _baseRegisterTable[offset];
+
+	uint8 regValue;
+	if (defaultValue) {
+		regValue = defaultValue;
+	} else {
+		regValue = readReg(regNum);
+	}
+
+	regValue &= _registerMaskTable[offset];
+	regValue >>= _registerShiftTable[offset];
+
+	return regValue;
+}
+
+void Player_AD::setupNoteEnvelopeState(int note, int steps, int adjust) {
+	_notes[note].preIncrease = 0;
+	if (ABS(adjust) > steps) {
+		_notes[note].preIncrease = 1;
+		_notes[note].adjust = adjust / steps;
+		_notes[note].envelope.stepIncrease = ABS(adjust % steps);
+	} else {
+		_notes[note].adjust = adjust;
+		_notes[note].envelope.stepIncrease = ABS(adjust);
+	}
+
+	_notes[note].envelope.step = steps;
+	_notes[note].envelope.stepCounter = 0;
+	_notes[note].envelope.timer = steps;
+}
+
+bool Player_AD::processNoteEnvelope(int note, int &instrumentValue) {
+	if (_notes[note].preIncrease) {
+		instrumentValue += _notes[note].adjust;
+	}
+
+	_notes[note].envelope.stepCounter += _notes[note].envelope.stepIncrease;
+	if (_notes[note].envelope.stepCounter >= _notes[note].envelope.step) {
+		_notes[note].envelope.stepCounter -= _notes[note].envelope.step;
+
+		if (_notes[note].adjust < 0) {
+			--instrumentValue;
+		} else {
+			++instrumentValue;
+		}
+	}
+
+	if (--_notes[note].envelope.timer) {
+		return false;
+	} else {
+		return true;
+	}
+}
+
+uint8 Player_AD::getRnd() {
+	if (_rndSeed & 1) {
+		_rndSeed >>= 1;
+		_rndSeed ^= 0xB8;
+	} else {
+		_rndSeed >>= 1;
+	}
+
+	return _rndSeed;
+}
+
+const uint Player_AD::_noteBiasTable[7] = {
+	0x00, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x00
+};
+
+const uint Player_AD::_numStepsTable[16] = {
+	    1,    4,    6,    8,
+	   10,   14,   18,   24,
+	   36,   64,  100,  160,
+	  240,  340,  600, 1200
+};
+
+const uint Player_AD::_noteAdjustScaleTable[7] = {
+	255,   7,  63,  15,  63,  15,  63
+};
+
+const uint Player_AD::_noteAdjustTable[16] = {
+	    0,  4369,  8738, 13107,
+	17476, 21845, 26214, 30583,
+	34952, 39321, 43690, 48059,
+	52428, 46797, 61166, 65535
+};
+
+const bool Player_AD::_useOperatorTable[7] = {
+	false, false, true, true, true, true, false
+};
+
+const uint Player_AD::_channelOffsetTable[11] = {
+	 0,  1,  2,  3,
+	 4,  5,  6,  7,
+	 8,  8,  7
+};
+
+const uint Player_AD::_channelOperatorOffsetTable[7] = {
+	0, 0, 1, 1, 0, 0, 0
+};
+
+const uint Player_AD::_baseRegisterTable[7] = {
+	0xA0, 0xC0, 0x40, 0x20, 0x40, 0x20, 0x00
+};
+
+const uint Player_AD::_registerMaskTable[7] = {
+	0xFF, 0x0E, 0x3F, 0x0F, 0x3F, 0x0F, 0x00
+};
+
+const uint Player_AD::_registerShiftTable[7] = {
+	0, 1, 0, 0, 0, 0, 0
+};
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player/ad.h b/engines/scumm/player/ad.h
new file mode 100644
index 0000000..da6c717
--- /dev/null
+++ b/engines/scumm/player/ad.h
@@ -0,0 +1,190 @@
+/* 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_AD_H
+#define SCUMM_PLAYER_AD_H
+
+#include "scumm/music.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+#include "common/mutex.h"
+
+namespace OPL {
+class OPL;
+}
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Sound output for v3/v4 AdLib data.
+ */
+class Player_AD : public MusicEngine, public Audio::AudioStream {
+public:
+	Player_AD(ScummEngine *scumm, Audio::Mixer *mixer);
+	virtual ~Player_AD();
+
+	// 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;
+
+	virtual void saveLoadWithSerializer(Serializer *ser);
+
+	// 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 _rate; }
+
+private:
+	ScummEngine *const _vm;
+	Common::Mutex _mutex;
+	Audio::Mixer *const _mixer;
+	const int _rate;
+	Audio::SoundHandle _soundHandle;
+	void setupVolume();
+
+	OPL::OPL *_opl2;
+
+	int _samplesPerCallback;
+	int _samplesPerCallbackRemainder;
+	int _samplesTillCallback;
+	int _samplesTillCallbackRemainder;
+
+	int _soundPlaying;
+	int _engineMusicTimer;
+
+	// AdLib register utilities
+	uint8 _registerBackUpTable[256];
+	void writeReg(int r, int v);
+	uint8 readReg(int r) const;
+
+	// Instrument setup
+	void setupChannel(const uint channel, uint instrOffset) {
+		setupChannel(channel, _resource + instrOffset);
+	}
+	void setupChannel(const uint channel, const byte *instrOffset);
+	void setupOperator(const uint opr, const byte *&instrOffset);
+	static const int _operatorOffsetTable[18];
+
+	// Sound data
+	const byte *_resource;
+
+	// Music handling
+	void startMusic();
+	void updateMusic();
+	void noteOff(uint channel);
+	int findFreeChannel();
+	void setupFrequency(uint channel, int8 frequency);
+	void setupRhythm(uint rhythmInstr, uint instrOffset);
+
+	uint _timerLimit;
+	uint _musicTicks;
+	uint _musicTimer;
+	uint _internalMusicTimer;
+	bool _loopFlag;
+	uint _musicLoopStart;
+	uint _instrumentOffset[16];
+	uint _channelLastEvent[9];
+	uint _channelFrequency[9];
+	uint _channelB0Reg[9];
+
+	uint _mdvdrState;
+	uint _voiceChannels;
+	
+	uint _curOffset;
+	uint _nextEventTimer;
+
+	static const uint _noteFrequencies[12];
+	static const uint _mdvdrTable[6];
+	static const uint _rhythmOperatorTable[6];
+	static const uint _rhythmChannelTable[6];
+
+	// SFX handling
+	void startSfx();
+	void updateSfx();
+	void clearChannel(int channel);
+	void updateChannel(int channel);
+	void parseSlot(int channel);
+	void updateSlot(int channel);
+	void parseNote(int channel, int num, const byte *offset);
+	bool processNote(int note, const byte *offset);
+	void noteOffOn(int channel);
+	void writeRegisterSpecial(int note, uint8 value, int offset);
+	uint8 readRegisterSpecial(int note, uint8 defaultValue, int offset);
+	void setupNoteEnvelopeState(int note, int steps, int adjust);
+	bool processNoteEnvelope(int note, int &instrumentValue);
+
+	int _sfxTimer;
+
+	int _sfxResource[3];
+	int _sfxPriority[3];
+
+	struct Channel {
+		int state;
+		const byte *currentOffset;
+		const byte *startOffset;
+		uint8 instrumentData[7];
+	} _channels[11];
+
+	uint8 _rndSeed;
+	uint8 getRnd();
+
+	struct Note {
+		int state;
+		int playTime;
+		int sustainTimer;
+		int instrumentValue;
+		int bias;
+		int preIncrease;
+		int adjust;
+		
+		struct Envelope {
+			int stepIncrease;
+			int step;
+			int stepCounter;
+			int timer;
+		} envelope;
+	} _notes[22];
+
+	static const uint _noteBiasTable[7];
+	static const uint _numStepsTable[16];
+	static const uint _noteAdjustScaleTable[7];
+	static const uint _noteAdjustTable[16];
+	static const bool _useOperatorTable[7];
+	static const uint _channelOffsetTable[11];
+	static const uint _channelOperatorOffsetTable[7];
+	static const uint _baseRegisterTable[7];
+	static const uint _registerMaskTable[7];
+	static const uint _registerShiftTable[7];
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/apple2.cpp b/engines/scumm/player/apple2.cpp
new file mode 100644
index 0000000..671cbf9
--- /dev/null
+++ b/engines/scumm/player/apple2.cpp
@@ -0,0 +1,500 @@
+/* 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 "engines/engine.h"
+#include "scumm/player/apple2.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+/************************************
+ * Apple-II sound-resource parsers
+ ************************************/
+
+/*
+ * SoundFunction1: frequency up/down
+ */
+class AppleII_SoundFunction1_FreqUpDown : public AppleII_SoundFunction {
+public:
+	virtual void init(Player_AppleII *player, const byte *params) {
+		_player = player;
+		_delta = params[0];
+		_count = params[1];
+		_interval = params[2];
+		_limit = params[3];
+		_decInterval = (params[4] >= 0x40);
+	}
+
+	virtual bool update() { // D085
+		if (_decInterval) {
+			do {
+				_update(_interval, _count);
+				_interval -= _delta;
+			} while (_interval >= _limit);
+		} else {
+			do {
+				_update(_interval, _count);
+				_interval += _delta;
+			} while (_interval < _limit);
+		}
+		return true;
+	}
+
+private:
+	void _update(int interval /*a*/, int count /*y*/) { // D076
+		assert(interval > 0); // 0 == 256?
+		assert(count > 0); // 0 == 256?
+
+		for (; count >= 0; --count) {
+			_player->speakerToggle();
+			_player->generateSamples(17 + 5 * interval);
+		}
+	}
+
+protected:
+	int _delta;
+	int _count;
+	byte _interval; // must be unsigned byte ("interval < delta" possible)
+	int _limit;
+	bool _decInterval;
+};
+
+/*
+ * SoundFunction2: symmetric wave (~)
+ */
+class AppleII_SoundFunction2_SymmetricWave : public AppleII_SoundFunction {
+public:
+	virtual void init(Player_AppleII *player, const byte *params) {
+		_player = player;
+		_params = params;
+		_pos = 1;
+	}
+
+	virtual bool update() { // D0D6
+		// while (pos = 1; pos < 256; ++pos)
+		if (_pos < 256) {
+			byte interval = _params[_pos];
+			if (interval == 0xFF)
+				return true;
+			_update(interval, _params[0] /*, LD12F=interval*/);
+
+			++_pos;
+			return false;
+		}
+		return true;
+	}
+
+private:
+	void _update(int interval /*a*/, int count) { // D0EF
+		if (interval == 0xFE) {
+			_player->wait(interval, 10);
+		} else {
+			assert(count > 0); // 0 == 256?
+			assert(interval > 0); // 0 == 256?
+
+			int a = (interval >> 3) + count;
+			for (int y = a; y > 0; --y) {
+				_player->generateSamples(1292 - 5*interval);
+				_player->speakerToggle();
+
+				_player->generateSamples(1287 - 5*interval);
+				_player->speakerToggle();
+			}
+		}
+	}
+
+protected:
+	const byte *_params;
+	int _pos;
+};
+
+/*
+ * SoundFunction3: asymmetric wave (__-)
+ */
+class AppleII_SoundFunction3_AsymmetricWave : public AppleII_SoundFunction {
+public:
+	virtual void init(Player_AppleII *player, const byte *params) {
+		_player = player;
+		_params = params;
+		_pos = 1;
+	}
+
+	virtual bool update() { // D132
+		// while (pos = 1; pos < 256; ++pos)
+		if (_pos < 256) {
+			byte interval = _params[_pos];
+			if (interval == 0xFF)
+				return true;
+			_update(interval, _params[0]);
+
+			++_pos;
+			return false;
+		}
+		return true;
+	}
+
+private:
+	void _update(int interval /*a*/, int count /*LD12D*/) { // D14B
+		if (interval == 0xFE) {
+			_player->wait(interval, 70);
+		} else {
+			assert(interval > 0); // 0 == 256?
+			assert(count > 0); // 0 == 256?
+
+			for (int y = count; y > 0; --y) {
+				_player->generateSamples(1289 - 5*interval);
+				_player->speakerToggle();
+			}
+		}
+	}
+
+protected:
+	const byte *_params;
+	int _pos;
+};
+
+/*
+ * SoundFunction4: polyphone (2 voices)
+ */
+class AppleII_SoundFunction4_Polyphone : public AppleII_SoundFunction {
+public:
+	virtual void init(Player_AppleII *player, const byte *params) {
+		_player = player;
+		_params = params;
+		_updateRemain1 = 80;
+		_updateRemain2 = 10;
+		_count = 0;
+	}
+
+	virtual bool update() { // D170
+		// while (_params[0] != 0x01)
+		if (_params[0] != 0x01) {
+			if (_count == 0) // prepare next loop
+				nextLoop(_params[0], _params[1], _params[2]);
+			if (loopIteration()) // loop finished -> fetch next parameter set
+				_params += 3;
+			return false;
+		}
+		return true;
+	}
+
+private:
+	/*
+	 * prepare for next parameter set loop
+	 */
+	void nextLoop(byte param0, byte param1, byte param2) { // LD182
+		_count = (-param2 << 8) | 0x3;
+
+		_bitmask1 = 0x3;
+		_bitmask2 = 0x3;
+
+		_updateInterval2 = param0;
+		if (_updateInterval2 == 0)
+			_bitmask2 = 0x0;
+
+		_updateInterval1 = param1;
+		if (_updateInterval1 == 0) {
+			_bitmask1 = 0x0;
+			if (_bitmask2 != 0) {
+				_bitmask1 = _bitmask2;
+				_bitmask2 = 0;
+				_updateInterval1 = _updateInterval2;
+			}
+		}
+
+		_speakerShiftReg = 0;
+	}
+
+	/*
+	 * perform one loop iteration
+	 * Returns true if loop finished
+	 */
+	bool loopIteration() { // D1A2
+		--_updateRemain1;
+		--_updateRemain2;
+
+		if (_updateRemain2 == 0) {
+			_updateRemain2 = _updateInterval2;
+			// use only first voice's data (bitmask1) if both voices are triggered
+			if (_updateRemain1 != 0) {
+				_speakerShiftReg ^= _bitmask2;
+			}
+		}
+
+		if (_updateRemain1 == 0) {
+			_updateRemain1 = _updateInterval1;
+			_speakerShiftReg ^= _bitmask1;
+		}
+
+		if (_speakerShiftReg & 0x1)
+			_player->speakerToggle();
+		_speakerShiftReg >>= 1;
+		_player->generateSamples(42); /* actually 42.5 */
+
+		++_count;
+		return (_count == 0);
+	}
+
+protected:
+	const byte *_params;
+
+	byte _updateRemain1;
+	byte _updateRemain2;
+
+	uint16 _count;
+	byte _bitmask1;
+	byte _bitmask2;
+	byte _updateInterval1;
+	byte _updateInterval2;
+	byte _speakerShiftReg;
+};
+
+/*
+ * SoundFunction5: periodic noise
+ */
+class AppleII_SoundFunction5_Noise : public AppleII_SoundFunction {
+public:
+	virtual void init(Player_AppleII *player, const byte *params) {
+		_player = player;
+		_index = 0;
+		_param0 = params[0];
+		assert(_param0 > 0);
+	}
+
+	virtual bool update() { // D222
+		const byte noiseMask[] = {
+			0x3F, 0x3F, 0x7F, 0x7F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F
+		};
+
+		// while (i = 0; i < 10; ++i)
+		if (_index < 10) {
+			int count = _param0;
+			do {
+				_update(noise() & noiseMask[_index], 1);
+				--count;
+			} while (count > 0);
+
+			++_index;
+			return false;
+		}
+
+		return true;
+	}
+
+private:
+	void _update(int interval /*a*/, int count) { // D270
+		assert(count > 0); // 0 == 256?
+		if (interval == 0)
+			interval = 256;
+
+		for (int i = count; i > 0; --i) {
+			_player->generateSamples(10 + 5*interval);
+			_player->speakerToggle();
+
+			_player->generateSamples(5 + 5*interval);
+			_player->speakerToggle();
+		}
+	}
+
+	byte /*a*/ noise() { // D261
+		static int pos = 0; // initial value?
+		byte result = _noiseTable[pos];
+		pos = (pos + 1) % 256;
+		return result;
+	}
+
+protected:
+	int _index;
+	int _param0;
+
+private:
+	static const byte _noiseTable[256];
+};
+
+// LD000[loc] ^ LD00A[loc]
+const byte AppleII_SoundFunction5_Noise::_noiseTable[256] = {
+	0x65, 0x1b, 0xda, 0x11, 0x61, 0xe5, 0x77, 0x57, 0x92, 0xc8, 0x51, 0x1c, 0xd4, 0x91, 0x62, 0x63,
+	0x00, 0x38, 0x57, 0xd5, 0x18, 0xd8, 0xdc, 0x40, 0x03, 0x86, 0xd3, 0x2f, 0x10, 0x11, 0xd8, 0x3c,
+	0xbe, 0x00, 0x19, 0xc5, 0xd2, 0xc3, 0xca, 0x34, 0x00, 0x28, 0xbf, 0xb9, 0x18, 0x20, 0x01, 0xcc,
+	0xda, 0x08, 0xbc, 0x75, 0x7c, 0xb0, 0x8d, 0xe0, 0x09, 0x18, 0xbf, 0x5d, 0xe9, 0x8c, 0x75, 0x64,
+	0xe5, 0xb5, 0x5d, 0xe0, 0xb7, 0x7d, 0xe9, 0x8c, 0x55, 0x65, 0xc5, 0xb5, 0x5d, 0xd8, 0x09, 0x0d,
+	0x64, 0xf0, 0xf0, 0x08, 0x63, 0x03, 0x00, 0x55, 0x35, 0xc0, 0x00, 0x20, 0x74, 0xa5, 0x1e, 0xe3,
+	0x00, 0x06, 0x3c, 0x52, 0xd1, 0x70, 0xd0, 0x57, 0x02, 0xf0, 0x00, 0xb6, 0xfc, 0x02, 0x11, 0x9a,
+	0x3b, 0xc8, 0x38, 0xdf, 0x1a, 0xb0, 0xd1, 0xb8, 0xd0, 0x18, 0x8a, 0x4a, 0xea, 0x1b, 0x12, 0x5d,
+	0x29, 0x58, 0xd8, 0x43, 0xb8, 0x2d, 0xd2, 0x61, 0x10, 0x3c, 0x0c, 0x5d, 0x1b, 0x61, 0x10, 0x3c,
+	0x0a, 0x5d, 0x1d, 0x61, 0x10, 0x3c, 0x0b, 0x19, 0x88, 0x21, 0xc0, 0x21, 0x07, 0x00, 0x65, 0x62,
+	0x08, 0xe9, 0x36, 0x40, 0x20, 0x41, 0x06, 0x00, 0x20, 0x00, 0x00, 0xed, 0xa3, 0x00, 0x88, 0x06,
+	0x98, 0x01, 0x5d, 0x7f, 0x02, 0x1d, 0x78, 0x03, 0x60, 0xcb, 0x3a, 0x01, 0xbd, 0x78, 0x02, 0x5d,
+	0x7e, 0x03, 0x1d, 0xf5, 0xa6, 0x40, 0x81, 0xb4, 0xd0, 0x8d, 0xd3, 0xd0, 0x6d, 0xd5, 0x61, 0x48,
+	0x61, 0x4d, 0xd1, 0xc8, 0xb1, 0xd8, 0x69, 0xff, 0x61, 0xd9, 0xed, 0xa0, 0xfe, 0x19, 0x91, 0x37,
+	0x19, 0x37, 0x00, 0xf1, 0x00, 0x01, 0x1f, 0x00, 0xad, 0xc1, 0x01, 0x01, 0x2e, 0x00, 0x40, 0xc6,
+	0x7a, 0x9b, 0x95, 0x43, 0xfc, 0x18, 0xd2, 0x9e, 0x2a, 0x5a, 0x4b, 0x2a, 0xb6, 0x87, 0x30, 0x6c
+};
+
+/************************************
+ * Apple-II player
+ ************************************/
+
+Player_AppleII::Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer)
+	: _mixer(mixer), _vm(scumm), _soundFunc(0) {
+	resetState();
+	setSampleRate(_mixer->getOutputRate());
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_AppleII::~Player_AppleII() {
+	_mixer->stopHandle(_soundHandle);
+	delete _soundFunc;
+}
+
+void Player_AppleII::resetState() {
+	_soundNr = 0;
+	_type = 0;
+	_loop = 0;
+	_params = NULL;
+	_speakerState = 0;
+	delete _soundFunc;
+	_soundFunc = 0;
+	_sampleConverter.reset();
+}
+
+void Player_AppleII::startSound(int nr) {
+	Common::StackLock lock(_mutex);
+
+	byte *data = _vm->getResourceAddress(rtSound, nr);
+	assert(data);
+	byte *ptr1 = data + 4;
+
+	resetState();
+	_soundNr = nr;
+	_type = ptr1[0];
+	_loop = ptr1[1];
+	_params = &ptr1[2];
+
+	switch (_type) {
+	case 0: // empty (nothing to play)
+		resetState();
+		return;
+	case 1:
+		_soundFunc = new AppleII_SoundFunction1_FreqUpDown();
+		break;
+	case 2:
+		_soundFunc = new AppleII_SoundFunction2_SymmetricWave();
+		break;
+	case 3:
+		_soundFunc = new AppleII_SoundFunction3_AsymmetricWave();
+		break;
+	case 4:
+		_soundFunc = new AppleII_SoundFunction4_Polyphone();
+		break;
+	case 5:
+		_soundFunc = new AppleII_SoundFunction5_Noise();
+		break;
+	}
+	_soundFunc->init(this, _params);
+
+	assert(_loop > 0);
+
+	debug(4, "startSound %d: type %d, loop %d",
+		  nr, _type, _loop);
+}
+
+bool Player_AppleII::updateSound() {
+	if (!_soundFunc)
+		return false;
+
+	if (_soundFunc->update()) {
+		--_loop;
+		if (_loop <= 0) {
+			delete _soundFunc;
+			_soundFunc = 0;
+		} else {
+			// reset function state on each loop
+			_soundFunc->init(this, _params);
+		}
+	}
+
+	return true;
+}
+
+void Player_AppleII::stopAllSounds() {
+	Common::StackLock lock(_mutex);
+	resetState();
+}
+
+void Player_AppleII::stopSound(int nr) {
+	Common::StackLock lock(_mutex);
+	if (_soundNr == nr) {
+		resetState();
+	}
+}
+
+int Player_AppleII::getSoundStatus(int nr) const {
+	Common::StackLock lock(_mutex);
+	return (_soundNr == nr);
+}
+
+int Player_AppleII::getMusicTimer() {
+	/* Apple-II sounds are synchronous -> no music timer */
+	return 0;
+}
+
+int Player_AppleII::readBuffer(int16 *buffer, const int numSamples) {
+	Common::StackLock lock(_mutex);
+
+	if (!_soundNr)
+		return 0;
+
+	int samplesLeft = numSamples;
+	do {
+		int nSamplesRead = _sampleConverter.readSamples(buffer, samplesLeft);
+		samplesLeft -= nSamplesRead;
+		buffer += nSamplesRead;
+	} while ((samplesLeft > 0) && updateSound());
+
+	// reset state if sound is played completely
+	if (!_soundFunc && (_sampleConverter.availableSize() == 0))
+		resetState();
+
+	return numSamples - samplesLeft;
+}
+
+/************************************
+ * Apple-II sound-resource helpers
+ ************************************/
+
+// toggle speaker on/off
+void Player_AppleII::speakerToggle() {
+	_speakerState ^= 0x1;
+}
+
+void Player_AppleII::generateSamples(int cycles) {
+	_sampleConverter.addCycles(_speakerState, cycles);
+}
+
+void Player_AppleII::wait(int interval, int count /*y*/) {
+	assert(count > 0); // 0 == 256?
+	assert(interval > 0); // 0 == 256?
+	generateSamples(11 + count*(8 + 5 * interval));
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player/apple2.h b/engines/scumm/player/apple2.h
new file mode 100644
index 0000000..e1ec9d8
--- /dev/null
+++ b/engines/scumm/player/apple2.h
@@ -0,0 +1,297 @@
+/* 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_APPLEII_H
+#define SCUMM_PLAYER_APPLEII_H
+
+#include "common/mutex.h"
+#include "common/scummsys.h"
+#include "common/memstream.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/softsynth/sid.h"
+
+namespace Scumm {
+
+class ScummEngine;
+
+/*
+ * Optimized for use with periodical read/write phases when the buffer
+ * is filled in a write phase and completely read in a read phase.
+ * The growing strategy is optimized for repeated small (e.g. 2 bytes)
+ * single writes resulting in large buffers
+ * (avg.: 4KB, max: 18KB @ 16bit/22.050kHz (MM sound21)).
+ */
+class SampleBuffer {
+public:
+	SampleBuffer() : _data(0) {
+		clear();
+	}
+
+	~SampleBuffer() {
+		free(_data);
+	}
+
+	void clear() {
+		free(_data);
+		_data = 0;
+		_capacity = 0;
+		_writePos = 0;
+		_readPos = 0;
+	}
+
+	void ensureFree(uint32 needed) {
+		// if data was read completely, reset read/write pos to front
+		if ((_writePos != 0) && (_writePos == _readPos)) {
+			_writePos = 0;
+			_readPos = 0;
+		}
+
+		// check for enough space at end of buffer
+		uint32 freeEndCnt = _capacity - _writePos;
+		if (needed <= freeEndCnt)
+			return;
+
+		uint32 avail = availableSize();
+
+		// check for enough space at beginning and end of buffer
+		if (needed <= _readPos + freeEndCnt) {
+			// move unread data to front of buffer
+			memmove(_data, _data + _readPos, avail);
+			_writePos = avail;
+			_readPos = 0;
+		} else { // needs a grow
+			byte *old_data = _data;
+			uint32 new_len = avail + needed;
+
+			_capacity = new_len + 2048;
+			_data = (byte *)malloc(_capacity);
+
+			if (old_data) {
+				// copy old unread data to front of new buffer
+				memcpy(_data, old_data + _readPos, avail);
+				free(old_data);
+				_writePos = avail;
+				_readPos = 0;
+			}
+		}
+	}
+
+	uint32 availableSize() const {
+		if (_readPos >= _writePos)
+			return 0;
+		return _writePos - _readPos;
+	}
+
+	uint32 write(const void *dataPtr, uint32 dataSize) {
+		ensureFree(dataSize);
+		memcpy(_data + _writePos, dataPtr, dataSize);
+		_writePos += dataSize;
+		return dataSize;
+	}
+
+	uint32 read(byte *dataPtr, uint32 dataSize) {
+		uint32 avail = availableSize();
+		if (avail == 0)
+			return 0;
+		if (dataSize > avail)
+			dataSize = avail;
+		memcpy(dataPtr, _data + _readPos, dataSize);
+		_readPos += dataSize;
+		return dataSize;
+	}
+
+private:
+	uint32 _writePos;
+	uint32 _readPos;
+	uint32 _capacity;
+	byte *_data;
+};
+
+// CPU_CLOCK according to AppleWin
+static const double APPLEII_CPU_CLOCK = 1020484.5; // ~ 1.02 MHz
+
+/*
+ * Converts the 1-bit speaker state values into audio samples.
+ * This is done by aggregation of the speaker states at each
+ * CPU cycle in a sampling period into an audio sample.
+ */
+class SampleConverter {
+private:
+	void addSampleToBuffer(int sample) {
+		int16 value = sample * _volume / _maxVolume;
+		_buffer.write(&value, sizeof(value));
+	}
+
+public:
+	SampleConverter() :
+		_cyclesPerSampleFP(0),
+		_missingCyclesFP(0),
+		_sampleCyclesSumFP(0),
+		_volume(_maxVolume)
+	{}
+
+	~SampleConverter() {}
+
+	void reset() {
+		_missingCyclesFP = 0;
+		_sampleCyclesSumFP = 0;
+		_buffer.clear();
+	}
+
+	uint32 availableSize() const {
+		return _buffer.availableSize();
+	}
+
+	void setMusicVolume(int vol) {
+		assert(vol >= 0 && vol <= _maxVolume);
+		_volume = vol;
+	}
+
+	void setSampleRate(int rate) {
+		/* ~46 CPU cycles per sample @ 22.05kHz */
+		_cyclesPerSampleFP = int(APPLEII_CPU_CLOCK * (1 << PREC_SHIFT) / rate);
+		reset();
+	}
+
+	void addCycles(byte level, const int cycles) {
+		/* convert to fixed precision floats */
+		int cyclesFP = cycles << PREC_SHIFT;
+
+		// step 1: if cycles are left from the last call, process them first
+		if (_missingCyclesFP > 0) {
+			int n = (_missingCyclesFP < cyclesFP) ? _missingCyclesFP : cyclesFP;
+			if (level)
+				_sampleCyclesSumFP += n;
+			cyclesFP -= n;
+			_missingCyclesFP -= n;
+			if (_missingCyclesFP == 0) {
+				addSampleToBuffer(2*32767 * _sampleCyclesSumFP / _cyclesPerSampleFP - 32767);
+			} else {
+				return;
+			}
+		}
+
+		_sampleCyclesSumFP = 0;
+
+		// step 2: process blocks of cycles fitting into a whole sample
+		while (cyclesFP >= _cyclesPerSampleFP) {
+			addSampleToBuffer(level ? 32767 : -32767);
+			cyclesFP -= _cyclesPerSampleFP;
+		}
+
+		// step 3: remember cycles left for next call
+		if (cyclesFP > 0) {
+			_missingCyclesFP = _cyclesPerSampleFP - cyclesFP;
+			if (level)
+				_sampleCyclesSumFP = cyclesFP;
+		}
+	}
+
+	uint32 readSamples(void *buffer, int numSamples) {
+		return _buffer.read((byte *)buffer, numSamples * 2) / 2;
+	}
+
+private:
+	static const int PREC_SHIFT = 7;
+
+private:
+	int _cyclesPerSampleFP;   /* (fixed precision) */
+	int _missingCyclesFP;     /* (fixed precision) */
+	int _sampleCyclesSumFP;   /* (fixed precision) */
+	int _volume; /* 0 - 256 */
+	static const int _maxVolume = 256;
+	SampleBuffer _buffer;
+};
+
+class Player_AppleII;
+
+class AppleII_SoundFunction {
+public:
+	AppleII_SoundFunction() {}
+	virtual ~AppleII_SoundFunction() {}
+	virtual void init(Player_AppleII *player, const byte *params) = 0;
+	/* returns true if finished */
+	virtual bool update() = 0;
+protected:
+	Player_AppleII *_player;
+};
+
+class Player_AppleII : public Audio::AudioStream, public MusicEngine {
+public:
+	Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer);
+	virtual ~Player_AppleII();
+
+	virtual void setMusicVolume(int vol) { _sampleConverter.setMusicVolume(vol); }
+	void setSampleRate(int rate) {
+		_sampleRate = rate;
+		_sampleConverter.setSampleRate(rate);
+	}
+	virtual void startSound(int sound);
+	virtual void stopSound(int sound);
+	virtual void stopAllSounds();
+	virtual int  getSoundStatus(int sound) const;
+	virtual int  getMusicTimer();
+
+	// AudioStream API
+	int readBuffer(int16 *buffer, const int numSamples);
+	bool isStereo() const { return false; }
+	bool endOfData() const { return false; }
+	int getRate() const { return _sampleRate; }
+
+public:
+	void speakerToggle();
+	void generateSamples(int cycles);
+	void wait(int interval, int count);
+
+private:
+	// sound number
+	int _soundNr;
+	// type of sound
+	int _type;
+	// number of loops left
+	int _loop;
+	// global sound param list
+	const byte *_params;
+	// speaker toggle state (0 / 1)
+	byte _speakerState;
+	// sound function
+	AppleII_SoundFunction *_soundFunc;
+	// cycle to sample converter
+	SampleConverter _sampleConverter;
+
+private:
+	ScummEngine *_vm;
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundHandle;
+	int _sampleRate;
+	Common::Mutex _mutex;
+
+private:
+	void resetState();
+	bool updateSound();
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/mac.cpp b/engines/scumm/player/mac.cpp
new file mode 100644
index 0000000..1fba266
--- /dev/null
+++ b/engines/scumm/player/mac.cpp
@@ -0,0 +1,419 @@
+/* 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/resource.h"
+#include "scumm/scumm.h"
+#include "scumm/imuse/imuse.h"
+
+namespace Scumm {
+
+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),
+	  _fadeNoteEnds(fadeNoteEnds) {
+	assert(scumm);
+	assert(mixer);
+}
+
+void Player_Mac::init() {
+	_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);
+
+	if (!checkMusicAvailable()) {
+		return;
+	}
+
+	_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::saveLoadWithSerializer(Serializer *ser) {
+	Common::StackLock lock(_mutex);
+	if (ser->getVersion() < VER(94)) {
+		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[] = {
+			MKLINE(Player_Mac, _sampleRate, sleUint32, VER(94)),
+			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()
+		};
+
+		uint32 mixerSampleRate = _sampleRate;
+		int i;
+
+		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 (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;
+		}
+	}
+}
+
+void Player_Mac::setMusicVolume(int vol) {
+	debug(5, "Player_Mac::setMusicVolume(%d)", 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
+		// 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);
+
+	stopAllSounds_Internal();
+
+	const byte *ptr = _vm->getResourceAddress(rtSound, nr);
+	assert(ptr);
+
+	if (!loadMusic(ptr)) {
+		return;
+	}
+
+	_vm->_res->lock(rtSound, nr);
+	_soundPlaying = nr;
+}
+
+bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) {
+	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;
+}
+
+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 using bitwise operations to perform
+	// ((duration * 473 * _sampleRate) / 4096) without overflowing,
+	// then divide this by 225
+	// (note that 4 * 480 * 480 == 225 * 4096 == 225 << 12)
+	//
+	// 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);
+	samples = (samples >> 12) * 473 + (((samples & 4095) * 473) >> 12);
+	samples = samples / 225;
+	return samples;
+}
+
+int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) {
+	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
+		// 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);
+
+	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) {
+				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;
+					_channel[i]._remaining = samplesLeft;
+				}
+			}
+			generated = MIN<uint32>(_channel[i]._remaining, samplesLeft);
+			if (_channel[i]._velocity != 0) {
+				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining, _fadeNoteEnds);
+			}
+			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 remainingSamplesOnNote, bool fadeNoteEnds) {
+	int samplesLeft = numSamples;
+	while (samplesLeft) {
+		_subPos += pitchModifier;
+		while (_subPos >= 0x10000) {
+			_subPos -= 0x10000;
+			_pos++;
+			if (_pos >= _loopEnd) {
+				_pos = _loopStart;
+			}
+		}
+
+		int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255;
+
+		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;
+		if (sample > 32767) {
+			sample = 32767;
+		} else if (sample < -32768) {
+			sample = -32768;
+		}
+
+		*data++ = sample;
+		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..09307b4
--- /dev/null
+++ b/engines/scumm/player/mac.h
@@ -0,0 +1,133 @@
+/* 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 "scumm/saveload.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, bool fadeNoteEnds);
+	virtual ~Player_Mac();
+
+	void init();
+
+	// 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; }
+
+	virtual void saveLoadWithSerializer(Serializer *ser);
+
+private:
+	Common::Mutex _mutex;
+	Audio::Mixer *const _mixer;
+	Audio::SoundHandle _soundHandle;
+	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 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; }
+	virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, 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;
+
+	uint32 durationToSamples(uint16 duration);
+	int noteToPitchModifier(byte note, Instrument *instrument);
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/mod.cpp b/engines/scumm/player/mod.cpp
new file mode 100644
index 0000000..afa8967
--- /dev/null
+++ b/engines/scumm/player/mod.cpp
@@ -0,0 +1,223 @@
+/* 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 "scumm/player/mod.h"
+#include "audio/mixer.h"
+#include "audio/rate.h"
+#include "audio/decoders/raw.h"
+
+namespace Scumm {
+
+Player_MOD::Player_MOD(Audio::Mixer *mixer)
+	: _mixer(mixer), _sampleRate(mixer->getOutputRate()) {
+	int i;
+	_mixamt = 0;
+	_mixpos = 0;
+
+	for (i = 0; i < MOD_MAXCHANS; i++) {
+		_channels[i].id = 0;
+		_channels[i].vol = 0;
+		_channels[i].freq = 0;
+		_channels[i].input = NULL;
+		_channels[i].ctr = 0;
+		_channels[i].pos = 0;
+	}
+
+	_playproc = NULL;
+	_playparam = NULL;
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_MOD::~Player_MOD() {
+	_mixer->stopHandle(_soundHandle);
+	for (int i = 0; i < MOD_MAXCHANS; i++) {
+		if (!_channels[i].id)
+			continue;
+		delete _channels[i].input;
+	}
+}
+
+void Player_MOD::setMusicVolume(int vol) {
+	_maxvol = vol;
+}
+
+void Player_MOD::setUpdateProc(ModUpdateProc *proc, void *param, int freq) {
+	_playproc = proc;
+	_playparam = param;
+	_mixamt = _sampleRate / freq;
+}
+void Player_MOD::clearUpdateProc() {
+	_playproc = NULL;
+	_playparam = NULL;
+	_mixamt = 0;
+}
+
+void Player_MOD::startChannel(int id, void *data, int size, int rate, uint8 vol, int loopStart, int loopEnd, int8 pan) {
+	int i;
+	if (id == 0)
+		error("player_mod - attempted to start channel id 0");
+
+	for (i = 0; i < MOD_MAXCHANS; i++) {
+		if (!_channels[i].id)
+			break;
+	}
+	if (i == MOD_MAXCHANS) {
+		warning("player_mod - too many music channels playing (%i max)",MOD_MAXCHANS);
+		return;
+	}
+	_channels[i].id = id;
+	_channels[i].vol = vol;
+	_channels[i].pan = pan;
+	_channels[i].freq = rate;
+	_channels[i].ctr = 0;
+
+	Audio::SeekableAudioStream *stream = Audio::makeRawStream((const byte *)data, size, rate, 0);
+	if (loopStart != loopEnd) {
+		_channels[i].input = new Audio::SubLoopingAudioStream(stream, 0, Audio::Timestamp(0, loopStart, rate), Audio::Timestamp(0, loopEnd, rate));
+	} else {
+		_channels[i].input = stream;
+	}
+
+	// read the first sample
+	_channels[i].input->readBuffer(&_channels[i].pos, 1);
+}
+
+void Player_MOD::stopChannel(int id) {
+	if (id == 0)
+		error("player_mod - attempted to stop channel id 0");
+	for (int i = 0; i < MOD_MAXCHANS; i++) {
+		if (_channels[i].id == id) {
+			delete _channels[i].input;
+			_channels[i].input = NULL;
+			_channels[i].id = 0;
+			_channels[i].vol = 0;
+			_channels[i].freq = 0;
+			_channels[i].ctr = 0;
+			_channels[i].pos = 0;
+		}
+	}
+}
+void Player_MOD::setChannelVol(int id, uint8 vol) {
+	if (id == 0)
+		error("player_mod - attempted to set volume for channel id 0");
+	for (int i = 0; i < MOD_MAXCHANS; i++) {
+		if (_channels[i].id == id) {
+			_channels[i].vol = vol;
+			break;
+		}
+	}
+}
+
+void Player_MOD::setChannelPan(int id, int8 pan) {
+	if (id == 0)
+		error("player_mod - attempted to set pan for channel id 0");
+	for (int i = 0; i < MOD_MAXCHANS; i++) {
+		if (_channels[i].id == id) {
+			_channels[i].pan = pan;
+			break;
+		}
+	}
+}
+
+void Player_MOD::setChannelFreq(int id, int freq) {
+	if (id == 0)
+		error("player_mod - attempted to set frequency for channel id 0");
+	for (int i = 0; i < MOD_MAXCHANS; i++) {
+		if (_channels[i].id == id) {
+			if (freq > 31400)	// this is about as high as WinUAE goes
+				freq = 31400;	// can't easily verify on my own Amiga
+			_channels[i].freq = freq;
+			break;
+		}
+	}
+}
+
+void Player_MOD::do_mix(int16 *data, uint len) {
+	int i;
+	int dpos = 0;
+	uint dlen = 0;
+	memset(data, 0, 2 * len * sizeof(int16));
+	while (len) {
+		if (_playproc) {
+			dlen = _mixamt - _mixpos;
+			if (!_mixpos)
+				_playproc(_playparam);
+			if (dlen <= len) {
+				_mixpos = 0;
+				len -= dlen;
+			} else {
+				_mixpos = len;
+				dlen = len;
+				len = 0;
+			}
+		} else {
+			dlen = len;
+			len = 0;
+		}
+		for (i = 0; i < MOD_MAXCHANS; i++) {
+			if (_channels[i].id) {
+				Audio::st_volume_t vol_l = (127 - _channels[i].pan) * _channels[i].vol / 127;
+				Audio::st_volume_t vol_r = (127 + _channels[i].pan) * _channels[i].vol / 127;
+				for (uint j = 0; j < dlen; j++) {
+					// simple linear resample, unbuffered
+					int delta = (uint32)(_channels[i].freq * 0x10000) / _sampleRate;
+					uint16 cfrac = ~_channels[i].ctr & 0xFFFF;
+					if (_channels[i].ctr + delta < 0x10000)
+						cfrac = delta;
+					_channels[i].ctr += delta;
+					int32 cpos = _channels[i].pos * cfrac / 0x10000;
+					while (_channels[i].ctr >= 0x10000) {
+						if (_channels[i].input->readBuffer(&_channels[i].pos, 1) != 1) {	// out of data
+							stopChannel(_channels[i].id);
+							goto skipchan;	// exit 2 loops at once
+						}
+						_channels[i].ctr -= 0x10000;
+						if (_channels[i].ctr > 0x10000)
+							cpos += _channels[i].pos;
+						else
+							cpos += (int32)(_channels[i].pos * (_channels[i].ctr & 0xFFFF)) / 0x10000;
+					}
+					int16 pos = 0;
+					// if too many samples play in a row, the calculation below will overflow and clip
+					// so try and split it up into pieces it can manage comfortably
+					while (cpos < -0x8000) {
+						pos -= 0x80000000 / delta;
+						cpos += 0x8000;
+					}
+					while (cpos > 0x7FFF) {
+						pos += 0x7FFF0000 / delta;
+						cpos -= 0x7FFF;
+					}
+					pos += cpos * 0x10000 / delta;
+					Audio::clampedAdd(data[(dpos + j) * 2 + 0], pos * vol_l / Audio::Mixer::kMaxMixerVolume);
+					Audio::clampedAdd(data[(dpos + j) * 2 + 1], pos * vol_r / Audio::Mixer::kMaxMixerVolume);
+				}
+			}
+skipchan:		;	// channel ran out of data
+		}
+		dpos += dlen;
+	}
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player/mod.h b/engines/scumm/player/mod.h
new file mode 100644
index 0000000..619d835
--- /dev/null
+++ b/engines/scumm/player/mod.h
@@ -0,0 +1,100 @@
+/* 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_MOD_H
+#define SCUMM_PLAYER_MOD_H
+
+#include "scumm/scumm.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+namespace Audio {
+class RateConverter;
+}
+
+namespace Scumm {
+
+/**
+ * Generic Amiga MOD mixer - provides a 60Hz 'update' routine.
+ */
+class Player_MOD : public Audio::AudioStream {
+public:
+	Player_MOD(Audio::Mixer *mixer);
+	virtual ~Player_MOD();
+	virtual void setMusicVolume(int vol);
+
+	virtual void startChannel(int id, void *data, int size, int rate, uint8 vol, int loopStart = 0, int loopEnd = 0, int8 pan = 0);
+	virtual void stopChannel(int id);
+	virtual void setChannelVol(int id, uint8 vol);
+	virtual void setChannelPan(int id, int8 pan);
+	virtual void setChannelFreq(int id, int freq);
+
+	typedef void ModUpdateProc(void *param);
+
+	virtual void setUpdateProc(ModUpdateProc *proc, void *param, int freq);
+	virtual void clearUpdateProc();
+
+	// AudioStream API
+	int readBuffer(int16 *buffer, const int numSamples) {
+		do_mix(buffer, numSamples / 2);
+		return numSamples;
+	}
+	bool isStereo() const { return true; }
+	bool endOfData() const { return false; }
+	int getRate() const { return _sampleRate; }
+
+private:
+	enum {
+		MOD_MAXCHANS = 24
+	};
+
+	struct soundChan {
+		int id;
+		uint8 vol;
+		int8 pan;
+		uint16 freq;
+
+		uint32 ctr;
+		int16 pos;
+		Audio::AudioStream *input;
+	};
+
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundHandle;
+
+	uint32 _mixamt;
+	uint32 _mixpos;
+	const int _sampleRate;
+
+	soundChan _channels[MOD_MAXCHANS];
+
+	uint8 _maxvol;
+
+	virtual void do_mix(int16 *buf, uint len);
+
+	ModUpdateProc *_playproc;
+	void *_playparam;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/nes.cpp b/engines/scumm/player/nes.cpp
new file mode 100644
index 0000000..072f46d
--- /dev/null
+++ b/engines/scumm/player/nes.cpp
@@ -0,0 +1,1067 @@
+/* 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
+ * aint32 with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef DISABLE_NES_APU
+
+#include "engines/engine.h"
+#include "scumm/player/nes.h"
+#include "scumm/scumm.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+static const byte channelMask[4] = {1, 2, 4, 8};
+
+static const uint16 freqTable[64] = {
+	0x07F0, 0x077E, 0x0712, 0x06AE, 0x064E, 0x05F3, 0x059E, 0x054D,
+	0x0501, 0x04B9, 0x0475, 0x0435, 0x03F8, 0x03BF, 0x0389, 0x0357,
+	0x0327, 0x02F9, 0x02CF, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A,
+	0x01FC, 0x01DF, 0x01C4, 0x01AB, 0x0193, 0x017C, 0x0167, 0x0152,
+	0x013F, 0x012D, 0x011C, 0x010C, 0x00FD, 0x00EE, 0x00E1, 0x00D4,
+	0x00C8, 0x00BD, 0x00B2, 0x00A8, 0x009F, 0x0096, 0x008D, 0x0085,
+	0x007E, 0x0076, 0x0070, 0x0069, 0x0063, 0x005E, 0x0058, 0x0053,
+	0x004F, 0x004A, 0x0046, 0x0042, 0x003E, 0x003A, 0x0037, 0x0034
+};
+
+static const byte instChannel[16] = {
+	0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 1, 3, 3, 3
+};
+static const byte startCmd[16] = {
+	0x05, 0x03, 0x06, 0x08, 0x0B, 0x01, 0x01, 0x1A,
+	0x16, 0x06, 0x04, 0x17, 0x02, 0x10, 0x0E, 0x0D
+};
+static const byte releaseCmd[16] = {
+	0x0F, 0x00, 0x00, 0x09, 0x00, 0x14, 0x15, 0x00,
+	0x00, 0x00, 0x1B, 0x1B, 0x0F, 0x0F, 0x0F, 0x0F
+};
+static const byte nextCmd[28] =	{
+	0xFF, 0xFF, 0xFF, 0xFF, 0x17, 0xFF, 0x07, 0xFF,
+	0xFF, 0x0A, 0x09, 0x0C, 0x00, 0x00, 0x00, 0x00,
+	0x11, 0x12, 0x11, 0x03, 0xFF, 0xFF, 0x18, 0x00,
+	0x19, 0x00, 0x00, 0x00
+};
+static const byte nextDelay[28] = {
+	0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
+	0x00, 0x05, 0x08, 0x03, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00,
+	0x03, 0x00, 0x00, 0x00
+};
+
+namespace APUe {
+
+static const byte LengthCounts[32] = {
+	0x0A,0xFE,
+	0x14,0x02,
+	0x28,0x04,
+	0x50,0x06,
+	0xA0,0x08,
+	0x3C,0x0A,
+	0x0E,0x0C,
+	0x1A,0x0E,
+
+	0x0C,0x10,
+	0x18,0x12,
+	0x30,0x14,
+	0x60,0x16,
+	0xC0,0x18,
+	0x48,0x1A,
+	0x10,0x1C,
+	0x20,0x1E
+};
+
+class SoundGen {
+protected:
+	byte wavehold;
+	uint32 freq;	// short
+	uint32 CurD;
+
+public:
+	byte Timer;
+	int32 Pos;
+	uint32 Cycles;	// short
+
+	inline byte GetTimer() const { return Timer; }
+};
+
+class Square : public SoundGen {
+protected:
+	byte volume, envelope, duty, swpspeed, swpdir, swpstep, swpenab;
+	byte Vol;
+	byte EnvCtr, Envelope, BendCtr;
+	bool Enabled, ValidFreq, Active;
+	bool EnvClk, SwpClk;
+
+	void CheckActive();
+
+public:
+	void Reset();
+	void Write(int Reg, byte Val);
+	void Run();
+	void QuarterFrame();
+	void HalfFrame();
+};
+
+static const int8 Duties[4][8] = {
+	{-4,+4,-4,-4,-4,-4,-4,-4},
+	{-4,+4,+4,-4,-4,-4,-4,-4},
+	{-4,+4,+4,+4,+4,-4,-4,-4},
+	{+4,-4,-4,+4,+4,+4,+4,+4}
+};
+
+void Square::Reset() {
+	memset(this, 0, sizeof(*this));
+	Cycles = 1;
+	EnvCtr = 1;
+	BendCtr = 1;
+}
+
+void Square::CheckActive() {
+	ValidFreq = (freq >= 0x8) && ((swpdir) || !((freq + (freq >> swpstep)) & 0x800));
+	Active = Timer && ValidFreq;
+	Pos = Active ? (Duties[duty][CurD] * Vol) : 0;
+}
+
+void Square::Write(int Reg, byte Val) {
+	switch (Reg) {
+	case 0:
+		volume = Val & 0xF;
+		envelope = Val & 0x10;
+		wavehold = Val & 0x20;
+		duty = (Val >> 6) & 0x3;
+		Vol = envelope ? volume : Envelope;
+		break;
+
+	case 1:
+		swpstep = Val & 0x07;
+		swpdir = Val & 0x08;
+		swpspeed = (Val >> 4) & 0x7;
+		swpenab = Val & 0x80;
+		SwpClk = true;
+		break;
+
+	case 2:
+		freq &= 0x700;
+		freq |= Val;
+		break;
+
+	case 3:
+		freq &= 0xFF;
+		freq |= (Val & 0x7) << 8;
+
+		if (Enabled)
+			Timer = LengthCounts[(Val >> 3) & 0x1F];
+
+		CurD = 0;
+		EnvClk = true;
+		break;
+
+	case 4:
+		Enabled = (Val != 0);
+		if (!Enabled)
+			Timer = 0;
+		break;
+	}
+	CheckActive();
+}
+
+void Square::Run() {
+	Cycles = (freq + 1) << 1;
+	CurD = (CurD + 1) & 0x7;
+
+	if (Active)
+		Pos = Duties[duty][CurD] * Vol;
+}
+
+void Square::QuarterFrame() {
+	if (EnvClk) {
+		EnvClk = false;
+		Envelope = 0xF;
+		EnvCtr = volume + 1;
+	} else if (!--EnvCtr) {
+		EnvCtr = volume + 1;
+
+		if (Envelope)
+			Envelope--;
+		else
+			Envelope = wavehold ? 0xF : 0x0;
+	}
+
+	Vol = envelope ? volume : Envelope;
+	CheckActive();
+}
+
+void Square::HalfFrame() {
+	if (!--BendCtr) {
+		BendCtr = swpspeed + 1;
+
+		if (swpenab && swpstep && ValidFreq) {
+			int sweep = freq >> swpstep;
+			// FIXME: Is -sweep or ~sweep correct???
+			freq += swpdir ? -sweep : sweep;
+		}
+	}
+
+	if (SwpClk) {
+		SwpClk = false;
+		BendCtr = swpspeed + 1;
+	}
+
+	if (Timer && !wavehold)
+		Timer--;
+
+	CheckActive();
+}
+
+
+class Triangle : public SoundGen {
+protected:
+	byte linear;
+	byte LinCtr;
+	bool Enabled, Active;
+	bool LinClk;
+
+	void CheckActive();
+
+public:
+	void Reset();
+	void Write(int Reg, byte Val);
+	void Run();
+	void QuarterFrame();
+	void HalfFrame();
+};
+
+static const int8 TriDuty[32] = {
+	-8,-7,-6,-5,-4,-3,-2,-1,
+	+0,+1,+2,+3,+4,+5,+6,+7,
+	+7,+6,+5,+4,+3,+2,+1,+0,
+	-1,-2,-3,-4,-5,-6,-7,-8
+};
+
+void Triangle::Reset() {
+	memset(this, 0, sizeof(*this));
+	Cycles = 1;
+}
+
+void Triangle::CheckActive() {
+	Active = Timer && LinCtr;
+
+	if (freq < 4)
+		Pos = 0;	// beyond hearing range
+	else
+		Pos = TriDuty[CurD] * 8;
+}
+
+void Triangle::Write(int Reg, byte Val) {
+	switch (Reg) {
+	case 0:
+		linear = Val & 0x7F;
+		wavehold = (Val >> 7) & 0x1;
+		break;
+
+	case 2:
+		freq &= 0x700;
+		freq |= Val;
+		break;
+
+	case 3:
+		freq &= 0xFF;
+		freq |= (Val & 0x7) << 8;
+
+		if (Enabled)
+			Timer = LengthCounts[(Val >> 3) & 0x1F];
+
+		LinClk = true;
+		break;
+
+	case 4:
+		Enabled = (Val != 0);
+		if (!Enabled)
+			Timer = 0;
+		break;
+	}
+	CheckActive();
+}
+
+void Triangle::Run() {
+	Cycles = freq + 1;
+
+	if (Active) {
+		CurD++;
+		CurD &= 0x1F;
+
+		if (freq < 4)
+			Pos = 0;	// beyond hearing range
+		else
+			Pos = TriDuty[CurD] * 8;
+	}
+}
+
+void Triangle::QuarterFrame() {
+	if (LinClk)
+		LinCtr = linear;
+	else if (LinCtr)
+		LinCtr--;
+
+	if (!wavehold)
+		LinClk = false;
+
+	CheckActive();
+}
+
+void Triangle::HalfFrame() {
+	if (Timer && !wavehold)
+		Timer--;
+
+	CheckActive();
+}
+
+class Noise : public SoundGen {
+protected:
+	byte volume, envelope, datatype;
+	byte Vol;
+	byte EnvCtr, Envelope;
+	bool Enabled;
+	bool EnvClk;
+
+	void CheckActive();
+
+public:
+	void Reset();
+	void Write(int Reg, byte Val);
+	void Run();
+	void QuarterFrame();
+	void HalfFrame();
+};
+
+static const uint32 NoiseFreq[16] = {
+	0x004,0x008,0x010,0x020,0x040,0x060,0x080,0x0A0,
+	0x0CA,0x0FE,0x17C,0x1FC,0x2FA,0x3F8,0x7F2,0xFE4
+};
+
+void Noise::Reset() {
+	memset(this, 0, sizeof(*this));
+	CurD = 1;
+	Cycles = 1;
+	EnvCtr = 1;
+
+}
+
+void Noise::Write(int Reg, byte Val) {
+	switch (Reg) {
+	case 0:
+		volume = Val & 0x0F;
+		envelope = Val & 0x10;
+		wavehold = Val & 0x20;
+		Vol = envelope ? volume : Envelope;
+
+		if (Timer)
+			Pos = ((CurD & 0x4000) ? -2 : 2) * Vol;
+		break;
+
+	case 2:
+		freq = Val & 0xF;
+		datatype = Val & 0x80;
+		break;
+
+	case 3:
+		if (Enabled)
+			Timer = LengthCounts[(Val >> 3) & 0x1F];
+
+		EnvClk = true;
+		break;
+
+	case 4:
+		Enabled = (Val != 0);
+		if (!Enabled)
+			Timer = 0;
+		break;
+	}
+}
+
+void Noise::Run() {
+	Cycles = NoiseFreq[freq];	/* no + 1 here */
+
+	if (datatype)
+		CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 8)) & 0x1);
+	else
+		CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 13)) & 0x1);
+
+	if (Timer)
+		Pos = ((CurD & 0x4000) ? -2 : 2) * Vol;
+}
+
+void Noise::QuarterFrame() {
+	if (EnvClk) {
+		EnvClk = false;
+		Envelope = 0xF;
+		EnvCtr = volume + 1;
+	} else if (!--EnvCtr) {
+		EnvCtr = volume + 1;
+
+		if (Envelope)
+			Envelope--;
+		else
+			Envelope = wavehold ? 0xF : 0x0;
+	}
+
+	Vol = envelope ? volume : Envelope;
+
+	if (Timer)
+		Pos = ((CurD & 0x4000) ? -2 : 2) * Vol;
+}
+
+void Noise::HalfFrame() {
+	if (Timer && !wavehold)
+		Timer--;
+}
+
+class APU {
+protected:
+	int	BufPos;
+	int	SampleRate;
+
+	Square _square0;
+	Square _square1;
+	Triangle _triangle;
+	Noise _noise;
+
+	struct {
+		uint32 Cycles;
+		int Num;
+	} Frame;
+
+public:
+	APU(int rate) : SampleRate(rate) {
+		Reset();
+	}
+
+	void WriteReg(int Addr, byte Val);
+	byte Read4015();
+	void Reset ();
+	int16 GetSample();
+};
+
+void APU::WriteReg(int Addr, byte Val) {
+	switch (Addr) {
+	case 0x000:	_square0.Write(0,Val);	break;
+	case 0x001:	_square0.Write(1,Val);	break;
+	case 0x002:	_square0.Write(2,Val);	break;
+	case 0x003:	_square0.Write(3,Val);	break;
+	case 0x004:	_square1.Write(0,Val);	break;
+	case 0x005:	_square1.Write(1,Val);	break;
+	case 0x006:	_square1.Write(2,Val);	break;
+	case 0x007:	_square1.Write(3,Val);	break;
+	case 0x008:	_triangle.Write(0,Val);	break;
+	case 0x009:	_triangle.Write(1,Val);	break;
+	case 0x00A:	_triangle.Write(2,Val);	break;
+	case 0x00B:	_triangle.Write(3,Val);	break;
+	case 0x00C:	_noise.Write(0,Val);	break;
+	case 0x00D:	_noise.Write(1,Val);	break;
+	case 0x00E:	_noise.Write(2,Val);	break;
+	case 0x00F:	_noise.Write(3,Val);	break;
+	case 0x015:	_square0.Write(4,Val & 0x1);
+				_square1.Write(4,Val & 0x2);
+				_triangle.Write(4,Val & 0x4);
+				_noise.Write(4,Val & 0x8);
+		break;
+	}
+}
+
+byte APU::Read4015() {
+	byte result =
+		(( _square0.GetTimer()) ? 0x01 : 0) |
+		(( _square1.GetTimer()) ? 0x02 : 0) |
+		((_triangle.GetTimer()) ? 0x04 : 0) |
+		((   _noise.GetTimer()) ? 0x08 : 0);
+	return result;
+}
+
+void APU::Reset () {
+	BufPos = 0;
+
+	_square0.Reset();
+	_square1.Reset();
+	_triangle.Reset();
+	_noise.Reset();
+
+	Frame.Num = 0;
+	Frame.Cycles = 1;
+}
+
+template<class T>
+int step(T &obj, int sampcycles, uint frame_Cycles, int frame_Num) {
+	int samppos = 0;
+	while (sampcycles) {
+		// Compute the maximal amount we can step ahead before triggering
+		// an action (i.e. compute the minimum of sampcycles, frame_Cycles
+		// and obj.Cycles).
+		uint max_step = sampcycles;
+		if (max_step > frame_Cycles)
+			max_step = frame_Cycles;
+		if (max_step > obj.Cycles)
+			max_step = obj.Cycles;
+
+		// During all but the last of these steps, we just add the value of obj.Pos
+		// to samppos -- so we can to that all at once with a simple multiplication:
+		samppos += obj.Pos * (max_step - 1);
+
+		// Now step ahead...
+		sampcycles -= max_step;
+		frame_Cycles -= max_step;
+		obj.Cycles -= max_step;
+
+		if (!frame_Cycles) {
+			frame_Cycles = 7457;
+
+			if (frame_Num < 4) {
+				obj.QuarterFrame();
+
+				if (frame_Num & 1)
+					frame_Cycles++;
+				else
+					obj.HalfFrame();
+
+				frame_Num++;
+			} else
+				frame_Num = 0;
+		}
+
+		if (!obj.Cycles)
+			obj.Run();
+
+		samppos += obj.Pos;
+	}
+
+	return samppos;
+}
+
+int16 APU::GetSample() {
+	int samppos = 0;
+
+	const int sampcycles = 1+(1789773-BufPos-1)/SampleRate;
+	BufPos = BufPos + sampcycles * SampleRate - 1789773;
+
+	samppos += step( _square0, sampcycles, Frame.Cycles, Frame.Num);
+	samppos += step( _square1, sampcycles, Frame.Cycles, Frame.Num);
+	samppos += step(_triangle, sampcycles, Frame.Cycles, Frame.Num);
+	samppos += step(   _noise, sampcycles, Frame.Cycles, Frame.Num);
+
+	uint tmp = sampcycles;
+	while (tmp >= Frame.Cycles) {
+		tmp -= Frame.Cycles;
+		Frame.Cycles = 7457;
+
+		if (Frame.Num < 4) {
+			if (Frame.Num & 1)
+				Frame.Cycles++;
+			Frame.Num++;
+		} else
+			Frame.Num = 0;
+	}
+
+	Frame.Cycles -= tmp;
+
+	return (samppos << 6) / sampcycles;
+}
+
+} // End of namespace APUe
+
+Player_NES::Player_NES(ScummEngine *scumm, Audio::Mixer *mixer) {
+	int i;
+	_vm = scumm;
+	_mixer = mixer;
+	_sampleRate = _mixer->getOutputRate();
+	_apu = new APUe::APU(_sampleRate);
+
+	_samples_per_frame = _sampleRate / 60;
+	_current_sample = 0;
+
+	for (i = 0; i < NUMSLOTS; i++) {
+		_slot[i].id = -1;
+		_slot[i].framesleft = 0;
+		_slot[i].type = 0;
+		_slot[i].offset = 0;
+		_slot[i].data = NULL;
+	}
+
+	for (i = 0; i < NUMCHANS; i++) {
+		_mchan[i].command = 0;
+		_mchan[i].framedelay = 0;
+		_mchan[i].pitch = 0;
+		_mchan[i].volume = 0;
+		_mchan[i].voldelta = 0;
+		_mchan[i].envflags = 0;
+		_mchan[i].cmdlock = 0;
+	}
+	isSFXplaying = wasSFXplaying = false;
+
+	auxData1 = auxData2 = NULL;
+	numNotes = 0;
+
+	APU_writeControl(0);
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_NES::~Player_NES() {
+	_mixer->stopHandle(_soundHandle);
+	delete _apu;
+}
+
+void Player_NES::setMusicVolume (int vol) {
+	_maxvol = vol;
+}
+
+int Player_NES::readBuffer(int16 *buffer, const int numSamples) {
+	for (int n = 0; n < numSamples; n++) {
+		buffer[n] = _apu->GetSample() * _maxvol / 255;
+		_current_sample++;
+
+		if (_current_sample == _samples_per_frame) {
+			_current_sample = 0;
+			sound_play();
+		}
+	}
+	return numSamples;
+}
+void Player_NES::stopAllSounds() {
+	for (int i = 0; i < NUMSLOTS; i++) {
+		_slot[i].framesleft = 0;
+		_slot[i].type = 0;
+		_slot[i].id = -1;
+	}
+
+	isSFXplaying = 0;
+	checkSilenceChannels(0);
+}
+
+void Player_NES::stopSound(int nr) {
+	if (nr == -1)
+		return;
+
+	for (int i = 0; i < NUMSLOTS; i++) {
+		if (_slot[i].id != nr)
+			continue;
+
+		isSFXplaying = 0;
+		_slot[i].framesleft = 0;
+		_slot[i].type = 0;
+		_slot[i].id = -1;
+		checkSilenceChannels(i);
+	}
+}
+
+void Player_NES::startSound(int nr) {
+	byte *data = _vm->getResourceAddress(rtSound, nr) + 2;
+	assert(data);
+
+	int soundType = data[1];
+	int chan = data[0];
+
+	if (chan == 4) {
+		if (_slot[2].framesleft)
+			return;
+		chan = 0;
+	}
+
+	if (soundType < _slot[chan].type)
+		return;
+
+	_slot[chan].type = soundType;
+	_slot[chan].id = nr;
+	_slot[chan].data = data;
+	_slot[chan].offset = 2;
+	_slot[chan].framesleft = 1;
+	checkSilenceChannels(chan);
+	if (chan == 2) {
+		numNotes = _slot[chan].data[2];
+		auxData1 = _slot[chan].data + 3;
+		auxData2 = auxData1 + numNotes;
+		_slot[chan].data = auxData2 + numNotes;
+		_slot[chan].offset = 0;
+
+		for (int i = 0; i < NUMCHANS; i++)
+			_mchan[i].cmdlock = 0;
+	}
+}
+
+void Player_NES::checkSilenceChannels(int chan) {
+	for (chan--; chan >= 0; chan--) {
+		if (_slot[chan].framesleft)
+			return;
+	}
+	APU_writeControl(0);
+}
+
+void Player_NES::sound_play() {
+	if (_slot[0].framesleft)
+		playSFX(0);
+	else if (_slot[1].framesleft)
+		playSFX(1);
+
+	playMusic();
+}
+
+void Player_NES::playSFX (int nr) {
+	if (--_slot[nr].framesleft)
+		return;
+
+	while (1) {
+		int a = _slot[nr].data[_slot[nr].offset++];
+		if (a < 16) {
+			a >>= 2;
+			APU_writeControl(APU_readStatus() | channelMask[a]);
+			isSFXplaying = true;
+			APU_writeChannel(a, 0, _slot[nr].data[_slot[nr].offset++]);
+			APU_writeChannel(a, 1, _slot[nr].data[_slot[nr].offset++]);
+			APU_writeChannel(a, 2, _slot[nr].data[_slot[nr].offset++]);
+			APU_writeChannel(a, 3, _slot[nr].data[_slot[nr].offset++]);
+		} else if (a == 0xFE) {
+			_slot[nr].offset = 2;
+		} else if (a == 0xFF) {
+			_slot[nr].id = -1;
+			_slot[nr].type = 0;
+			isSFXplaying = false;
+			APU_writeControl(0);
+
+			if (!nr && _slot[1].framesleft) {
+				_slot[1].framesleft = 1;
+				isSFXplaying = true;
+			}
+			return;
+		} else {
+			_slot[nr].framesleft = _slot[nr].data[_slot[nr].offset++];
+			return;
+		}
+	}
+}
+
+void Player_NES::playMusic() {
+	if (!_slot[2].framesleft)
+		return;
+
+	if (wasSFXplaying && !isSFXplaying)
+		for (int x = 1; x >= 0; x--)
+			if (_mchan[x].cmdlock) {
+				_mchan[x].command = _mchan[x].cmdlock;
+				_mchan[x].framedelay = 1;
+			}
+
+	wasSFXplaying = isSFXplaying;
+	if (!--_slot[2].framesleft) {
+top:
+		int b = _slot[2].data[_slot[2].offset++];
+		if (b == 0xFF) {
+			_slot[2].id = -1;
+			_slot[2].type = 0;
+			b = 0;
+		} else if (b == 0xFE) {
+			_slot[2].offset = 0;
+			goto top;
+		} else {
+			if (b < numNotes) {
+				int inst = auxData1[b];
+				int ch = instChannel[inst];
+				_mchan[ch].pitch = auxData2[b];
+				_mchan[ch].cmdlock = startCmd[inst];
+				_mchan[ch].command = startCmd[inst];
+				_mchan[ch].framedelay = 1;
+				goto top;
+			}
+			b -= numNotes;
+			if (b < 16) {
+				int inst = b;
+				int ch = instChannel[inst];
+				_mchan[ch].cmdlock = 0;
+				_mchan[ch].command = releaseCmd[inst];
+				_mchan[ch].framedelay = 1;
+				goto top;
+			}
+			b -= 16;
+		}
+		_slot[2].framesleft = b;
+	}
+
+	for (int x = NUMCHANS - 1; x >= 0; x--) {
+		if (_slot[0].framesleft || _slot[1].framesleft) {
+			_mchan[x].volume = 0;
+			_mchan[x].framedelay = 0;
+			continue;
+		}
+
+		if (_mchan[x].framedelay && !--_mchan[x].framedelay) {
+			switch (_mchan[x].command) {
+			case 0x00:
+			case 0x13:
+				_mchan[x].voldelta = -10;
+				break;
+
+			case 0x01:
+			case 0x03:
+			case 0x08:
+			case 0x16:
+				_mchan[x].envflags = 0x30;
+				_mchan[x].volume = 0x6F;
+				_mchan[x].voldelta = 0;
+
+				APU_writeChannel(x, 0, 0x00);
+				APU_writeChannel(x, 1, 0x7F);
+				APU_writeControl(APU_readStatus() | channelMask[x]);
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x02:
+				_mchan[x].envflags = 0xB0;
+				_mchan[x].volume = 0x6F;
+				_mchan[x].voldelta = 0;
+
+				APU_writeChannel(x, 0, 0x00);
+				APU_writeChannel(x, 1, 0x84);
+				APU_writeControl(APU_readStatus() | channelMask[x]);
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x04:
+				_mchan[x].envflags = 0x80;
+				_mchan[x].volume = 0x6F;
+				_mchan[x].voldelta = 0;
+
+				APU_writeChannel(x, 0, 0x00);
+				APU_writeChannel(x, 1, 0x7F);
+				APU_writeControl(APU_readStatus() | channelMask[x]);
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x05:
+				_mchan[x].envflags = 0xF0;
+				_mchan[x].volume = 0x6F;
+				_mchan[x].voldelta = -15;
+
+				APU_writeChannel(x, 1, 0x7F);
+				APU_writeControl(APU_readStatus() | channelMask[x]);
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x06:
+				_mchan[x].pitch += 0x18;
+				_mchan[x].envflags = 0x80;
+				_mchan[x].volume = 0x6F;
+				_mchan[x].voldelta = 0;
+
+				APU_writeChannel(x, 0, 0x00);
+				APU_writeChannel(x, 1, 0x7F);
+				APU_writeControl(APU_readStatus() | channelMask[x]);
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x07:
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch - 0x0C] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch - 0x0C] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x09:
+				_mchan[x].voldelta = -2;
+
+				APU_writeChannel(x, 1, 0x7F);
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x0A:
+				APU_writeChannel(x, 1, 0x86);
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x0B:	case 0x1A:
+				_mchan[x].envflags = 0x70;
+				_mchan[x].volume = 0x6F;
+				_mchan[x].voldelta = 0;
+
+				APU_writeChannel(x, 0, 0x00);
+				APU_writeChannel(x, 1, 0x7F);
+				APU_writeControl(APU_readStatus() | channelMask[x]);
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x0C:
+				_mchan[x].envflags = 0xB0;
+
+				chainCommand(x);
+				break;
+
+			case 0x0D:
+				_mchan[x].envflags = 0x30;
+				_mchan[x].volume = 0x5F;
+				_mchan[x].voldelta = -22;
+
+				APU_writeChannel(x, 0, 0x00);
+				APU_writeControl(APU_readStatus() | channelMask[x]);
+				APU_writeChannel(x, 2, _mchan[x].pitch & 0xF);
+				APU_writeChannel(x, 3, 0xFF);
+
+				chainCommand(x);
+				break;
+
+			case 0x0E:
+			case 0x10:
+				_mchan[x].envflags = 0x30;
+				_mchan[x].volume = 0x5F;
+				_mchan[x].voldelta = -6;
+
+				APU_writeChannel(x, 0, 0x00);
+				APU_writeControl(APU_readStatus() | channelMask[x]);
+				APU_writeChannel(x, 2, _mchan[x].pitch & 0xF);
+				APU_writeChannel(x, 3, 0xFF);
+
+				chainCommand(x);
+				break;
+
+			case 0x0F:
+				chainCommand(x);
+				break;
+
+			case 0x11:
+				APU_writeChannel(x, 2, _mchan[x].pitch & 0xF);
+				APU_writeChannel(x, 3, 0xFF);
+
+				chainCommand(x);
+				break;
+
+			case 0x12:
+				APU_writeChannel(x, 2, (_mchan[x].pitch + 3) & 0xF);
+				APU_writeChannel(x, 3, 0xFF);
+
+				chainCommand(x);
+				break;
+
+			case 0x14:
+				_mchan[x].voldelta = -12;
+
+				APU_writeChannel(x, 1, 0x8C);
+
+				chainCommand(x);
+				break;
+
+			case 0x15:
+				_mchan[x].voldelta = -12;
+
+				APU_writeChannel(x, 1, 0x84);
+
+				chainCommand(x);
+				break;
+
+			case 0x17:
+				_mchan[x].pitch += 0x0C;
+				_mchan[x].envflags = 0x80;
+				_mchan[x].volume = 0x6F;
+				_mchan[x].voldelta = 0;
+
+				APU_writeChannel(x, 0, 0x00);
+				APU_writeChannel(x, 1, 0x7F);
+				APU_writeControl(APU_readStatus() | channelMask[x]);
+				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
+				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);
+
+				chainCommand(x);
+				break;
+
+			case 0x18:
+				_mchan[x].envflags = 0x70;
+
+				chainCommand(x);
+				break;
+
+			case 0x19:
+				_mchan[x].envflags = 0xB0;
+
+				chainCommand(x);
+				break;
+
+			case 0x1B:
+				_mchan[x].envflags = 0x00;
+				_mchan[x].voldelta = -10;
+				break;
+			}
+		}
+
+		_mchan[x].volume += _mchan[x].voldelta;
+
+		if (_mchan[x].volume < 0)
+			_mchan[x].volume = 0;
+		if (_mchan[x].volume > MAXVOLUME)
+			_mchan[x].volume = MAXVOLUME;
+
+		APU_writeChannel(x, 0, (_mchan[x].volume >> 3) | _mchan[x].envflags);
+	}
+}
+
+void Player_NES::chainCommand(int c) {
+	int i = _mchan[c].command;
+	_mchan[c].command = nextCmd[i];
+	_mchan[c].framedelay = nextDelay[i];
+}
+
+int Player_NES::getSoundStatus(int nr) const {
+	for (int i = 0; i < NUMSLOTS; i++)
+		if (_slot[i].id == nr)
+			return 1;
+	return 0;
+}
+
+void Player_NES::APU_writeChannel(int chan, int offset, byte value) {
+	_apu->WriteReg(0x000 + 4 * chan + offset, value);
+}
+void Player_NES::APU_writeControl(byte value) {
+	_apu->WriteReg(0x015, value);
+}
+byte Player_NES::APU_readStatus() {
+	return _apu->Read4015();
+}
+
+} // End of namespace Scumm
+
+#endif // DISABLE_NES_APU
diff --git a/engines/scumm/player/nes.h b/engines/scumm/player/nes.h
new file mode 100644
index 0000000..be1617e
--- /dev/null
+++ b/engines/scumm/player/nes.h
@@ -0,0 +1,114 @@
+/* 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_NES_H
+#define SCUMM_PLAYER_NES_H
+
+#include "common/scummsys.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+class ScummEngine;
+namespace APUe {
+class APU;
+}
+
+static const int MAXVOLUME = 0x7F;
+static const int NUMSLOTS = 3;
+static const int NUMCHANS = 4;
+
+/**
+ * Scumm NES sound/music driver.
+ */
+class Player_NES : public Audio::AudioStream, public MusicEngine {
+public:
+	Player_NES(ScummEngine *scumm, Audio::Mixer *mixer);
+	virtual ~Player_NES();
+
+	virtual void setMusicVolume(int vol);
+	virtual void startSound(int sound);
+	virtual void stopSound(int sound);
+	virtual void stopAllSounds();
+	virtual int  getSoundStatus(int sound) const;
+
+	// AudioStream API
+	int readBuffer(int16 *buffer, const int numSamples);
+	bool isStereo() const { return false; }
+	bool endOfData() const { return false; }
+	int getRate() const { return _sampleRate; }
+
+private:
+
+	void sound_play();
+	void playSFX(int nr);
+	void playMusic();
+	byte fetchSoundByte(int nr);
+	void chainCommand(int chan);
+	void checkSilenceChannels(int chan);
+
+	void APU_writeChannel(int chan, int offset, byte value);
+	void APU_writeControl(byte value);
+	byte APU_readStatus();
+
+	ScummEngine *_vm;
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundHandle;
+	APUe::APU *_apu;
+	int _sampleRate;
+	int _samples_per_frame;
+	int _current_sample;
+	int _maxvol;
+
+	struct slot {
+		int framesleft;
+		int id;
+		int type;
+		byte *data;
+		int offset;
+	} _slot[NUMSLOTS];
+
+	struct mchan {
+		int command;
+		int framedelay;
+		int pitch;
+		int volume;
+		int voldelta;
+		int envflags;
+		int cmdlock;
+	} _mchan[NUMCHANS];
+
+	bool isSFXplaying, wasSFXplaying;
+
+	byte *dataStart;
+	int numNotes;
+	byte *auxData1;
+	byte *auxData2;
+
+	byte *soundptr;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/pce.cpp b/engines/scumm/player/pce.cpp
new file mode 100644
index 0000000..0a9cbaa
--- /dev/null
+++ b/engines/scumm/player/pce.cpp
@@ -0,0 +1,756 @@
+/* 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.
+ *
+ */
+
+/*
+ * The PSG_HuC6280 class is based on the HuC6280 sound chip emulator
+ * by Charles MacDonald (E-mail: cgfm2 at hotmail.com, WWW: http://cgfm2.emuviews.com)
+ * The implementation used here was taken from MESS (http://www.mess.org/)
+ * the Multiple Emulator Super System (sound/c6280.c).
+ * LFO and noise channel support have been removed (not used by Loom PCE).
+ */
+
+#include <math.h>
+#include "scumm/player/pce.h"
+#include "common/endian.h"
+
+// PCE sound engine is only used by Loom, which requires 16bit color support
+#ifdef USE_RGB_COLOR
+
+namespace Scumm {
+
+// CPU and PSG use the same base clock but with a different divider
+const double MASTER_CLOCK = 21477270.0;      // ~21.48 MHz
+const double CPU_CLOCK = MASTER_CLOCK / 3;   // ~7.16 MHz
+const double PSG_CLOCK = MASTER_CLOCK / 6;   // ~3.58 MHz
+const double TIMER_CLOCK = CPU_CLOCK / 1024; // ~6.9 kHz
+
+// The PSG update routine is originally triggered by the timer IRQ (not by VSYNC)
+// approx. 120 times per second (TIML=0x39). But as just every second call is used
+// to update the PSG we will call the update routine approx. 60 times per second.
+const double UPDATE_FREQ = TIMER_CLOCK / (57 + 1) / 2; // ~60 Hz
+
+// $AFA5
+static const byte wave_table[7][32] = {
+	{ // sine
+	0x10, 0x19, 0x1C, 0x1D, 0x1E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F,	0x1E, 0x1D, 0x1C, 0x19,
+	0x10, 0x05, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x05,
+	}, { // mw-shaped
+	0x10, 0x1C, 0x1D, 0x1E,	0x1E, 0x1F, 0x1F, 0x1E, 0x1C, 0x1E, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, 0x1C,
+	0x10, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0x01, 0x03, 0x01, 0x00, 0x00,	0x01, 0x01, 0x02, 0x03,
+	}, { // square
+	0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,	0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
+	0x01, 0x01, 0x01, 0x01,	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+	}, { // triangle
+	0x10, 0x0C, 0x08, 0x04, 0x01, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C,	0x1F, 0x1C, 0x18, 0x14,
+	0x10, 0x0C, 0x08, 0x04, 0x01, 0x04, 0x08, 0x0C,	0x10, 0x14, 0x18, 0x1C, 0x1F, 0x1C, 0x18, 0x14,
+	}, { // saw-tooth
+	0x00, 0x01, 0x02, 0x03,	0x04, 0x06, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B,	0x1C, 0x1D, 0x1E, 0x1F,
+	}, { // sigmoid
+	0x07, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,	0x1F, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19,
+	0x08, 0x06, 0x05, 0x03,	0x02, 0x01, 0x00, 0x00, 0x0F, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x16,
+	}, { // MW-shaped
+	0x1F, 0x1E, 0x1D, 0x1D, 0x1C, 0x1A, 0x17, 0x0F, 0x0F, 0x17, 0x1A, 0x1C,	0x1D, 0x1D, 0x1E, 0x1F,
+	0x00, 0x01, 0x02, 0x02, 0x03, 0x05, 0x08, 0x0F,	0x0F, 0x08, 0x05, 0x03, 0x02, 0x02, 0x01, 0x00
+	}
+};
+
+// AEBC
+static const int control_offsets[14] = {
+	0, 7, 20, 33, 46, 56, 75, 88, 116, 126, 136, 152, 165, 181
+};
+
+// AED8
+static const byte control_data[205] = {
+	/*  0*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0xFF,
+	/*  7*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF0, 0x3A, 0x00, 0xFD, 0xFF,
+	/* 20*/ 0xF0, 0x00, 0xF8, 0x01,	0x00, 0x00, 0x01, 0x00, 0xF0, 0x1D, 0x00, 0xF8, 0xFF,
+	/* 33*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x02, 0x00, 0xF0, 0x1E, 0x00, 0xFC, 0xFF,
+	/* 46*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x07, 0x00, 0xE0, 0xFF,
+	/* 56*/ 0xF0, 0x00, 0xF8, 0x01,	0x00, 0x00, 0x01, 0x00, 0xD8, 0xF0, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x04,	0x00, 0xF0, 0xFF,
+	/* 75*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x03, 0x00, 0xF8, 0x6E, 0x00, 0xFF, 0xFF,
+	/* 88*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x08, 0x00,	0xF0, 0xF0, 0x00, 0xD0, 0x01, 0x00, 0x00, 0x05, 0x00, 0xF0, 0xF0, 0x00,	0xB8, 0xE6, 0x80, 0xFF, 0xE6, 0x80, 0xFF, 0xFF,
+	/*116*/ 0xF0, 0x00, 0xF8, 0x01,	0x00, 0x00, 0x05, 0x00, 0xD0, 0xFF,
+	/*126*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x04, 0x00, 0xF8, 0xFF,
+	/*136*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x03, 0x00,	0xF4, 0xE6, 0xC0, 0xFF, 0xE6, 0xC0, 0xFF, 0xFF,
+	/*152*/ 0xF0, 0x00, 0xD0, 0x01,	0x00, 0x00, 0x02, 0x00, 0x10, 0x0E, 0x00, 0xFE, 0xFF,
+	/*165*/ 0xF0, 0x00, 0xA8, 0x01, 0x00, 0x00, 0x18, 0x00, 0x02, 0xE6, 0x80, 0xFE, 0xE6, 0xC0, 0xFF, 0xFF,
+	/*181*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x02, 0x00, 0xF8, 0x02, 0x00, 0x00, 0x02, 0x00, 0xF0, 0x01, 0x00, 0xF8, 0x02, 0x00, 0x08, 0xF1, 0x99, 0xAF
+};
+
+static const uint16 lookup_table[87] = {
+	0x0D40, 0x0C80, 0x0BC0, 0x0B20, 0x0A80, 0x09E0, 0x0940, 0x08C0,
+	0x0840, 0x07E0, 0x0760, 0x0700, 0x06A0, 0x0640, 0x05E0, 0x0590,
+	0x0540, 0x04F0, 0x04A0, 0x0460, 0x0420, 0x03F0, 0x03B0, 0x0380,
+	0x0350, 0x0320, 0x02F0, 0x02C8, 0x02A0, 0x0278, 0x0250, 0x0230,
+	0x0210, 0x01F8, 0x01D8, 0x01C0, 0x01A8, 0x0190, 0x0178, 0x0164,
+	0x0150, 0x013C, 0x0128, 0x0118, 0x0108, 0x00FC, 0x00EC, 0x00E0,
+	0x00D4, 0x00C8, 0x00BC, 0x00B2, 0x00A8, 0x009E, 0x0094, 0x008C,
+	0x0084, 0x007E, 0x0076, 0x0070, 0x006A, 0x0064, 0x005E, 0x0059,
+	0x0054, 0x004F, 0x004A, 0x0046, 0x0042, 0x003F, 0x003B, 0x0038,
+	0x0035, 0x0032, 0x0030, 0x002D, 0x002A, 0x0028, 0x0026, 0x0024,
+	0x0022, 0x0020, 0x001E, 0x001C, 0x001B, 0x8E82, 0xB500
+};
+
+// B27B
+static const uint16 freq_offset[3] = {
+	0, 2, 9
+};
+
+static const uint16 freq_table[] = {
+	0x0000, 0x0800,
+	0xFFB0, 0xFFD1, 0xFFE8, 0xFFF1, 0x0005, 0x0000, 0x0800,
+	0xFF9C, 0xFFD8, 0x0000, 0x000F, 0x0005, 0x0000, 0x0800
+};
+
+static const int sound_table[13] = {
+	0, 2, 3, 4, 5, 6, 7, 8, 9, 11, 1, 10, 11
+};
+
+// 0xAE12
+// Note:
+// - offsets relative to data_table
+// - byte one of each sound was always 0x3F (= use all channels) -> removed from table
+static const uint16 sounds[13][6] = {
+	{ 481, 481, 481, 481, 481, 481 },
+	{ 395, 408, 467, 480, 480, 480 },
+	{  85,  96, 109, 109, 109, 109 },
+	{ 110, 121, 134, 134, 134, 134 },
+	{ 135, 146, 159, 159, 159, 159 },
+	{ 160, 171, 184, 184, 184, 184 },
+	{ 185, 196, 209, 209, 209, 209 },
+	{ 210, 221, 234, 234, 234, 234 },
+	{ 235, 246, 259, 259, 259, 259 },
+	{ 260, 271, 284, 284, 284, 284 },
+	{ 285, 298, 311, 324, 335, 348 },
+	{ 349, 360, 361, 362, 373, 384 },
+	{   0,  84,  84,  84,  84,  84 } // unused
+};
+
+// 0xB2A1
+static const byte data_table[482] = {
+	/*  0*/ 0xE2, 0x0A, 0xE1, 0x0D, 0xE6, 0xED, 0xE0, 0x0F, 0xE2, 0x00, 0xE1, 0x00,
+			0xF2, 0xF2, 0xB2, 0xE1, 0x01, 0xF2, 0xF2, 0xB2, 0xE1, 0x02, 0xF2, 0xF2,
+			0xB2, 0xE1, 0x03, 0xF2, 0xF2, 0xB2, 0xE1, 0x04, 0xF2, 0xF2, 0xB2, 0xE1,
+			0x05, 0xF2, 0xF2, 0xB2, 0xE1, 0x06, 0xF2, 0xF2, 0xB2, 0xE1, 0x07, 0xF2,
+			0xF2, 0xB2, 0xE1, 0x08, 0xF2, 0xF2, 0xB2, 0xE1, 0x09, 0xF2, 0xF2, 0xB2,
+			0xE1, 0x0A, 0xF2, 0xF2, 0xB2, 0xE1, 0x0B, 0xF2, 0xF2, 0xB2, 0xE1, 0x0C,
+			0xF2, 0xF2, 0xB2, 0xE1, 0x0D, 0xF2, 0xF2, 0xB2, 0xFF, 0xD1, 0x03, 0xF3,
+	/* 84*/ 0xFF,
+
+	/* 85*/	0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x07, 0xFF,
+	/* 96*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x07, 0xFF,
+	/*109*/ 0xFF,
+
+	/*110*/	0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x27, 0xFF,
+	/*121*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x27, 0xFF,
+	/*134*/ 0xFF,
+
+	/*135*/	0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x47, 0xFF,
+	/*146*/	0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x47, 0xFF,
+	/*159*/ 0xFF,
+
+	/*160*/	0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x57, 0xFF,
+	/*171*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x57, 0xFF,
+	/*184*/ 0xFF,
+
+	/*185*/	0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x77, 0xFF,
+	/*196*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x77, 0xFF,
+	/*209*/ 0xFF,
+
+	/*210*/	0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x97, 0xFF,
+	/*221*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x97, 0xFF,
+	/*234*/ 0xFF,
+
+	/*235*/	0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0xB7, 0xFF,
+	/*246*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0xB7, 0xFF,
+	/*259*/ 0xFF,
+
+	/*260*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0x07, 0xFF,
+	/*271*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD5, 0xF0, 0x0C, 0x07, 0xFF,
+	/*284*/ 0xFF,
+
+	/*285*/ 0xE2, 0x0B, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x14, 0x0E, 0xFF,
+	/*298*/ 0xE2, 0x0B, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x32, 0x1E, 0xFF,
+	/*311*/ 0xE2, 0x0B, 0xE1, 0x0B, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x50, 0x1E, 0xFF,
+	/*324*/ 0xE2, 0x0B, 0xE1, 0x0B, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0x0E, 0xFF,
+	/*335*/ 0xE2, 0x0B, 0xE1, 0x02, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x0A, 0x0E, 0xFF,
+	/*348*/ 0xFF,
+
+	/*349*/	0xE2, 0x03, 0xE1, 0x01, 0xE6, 0xED, 0xE0, 0x06, 0xD6, 0x17, 0xFF,
+	/*360*/ 0xFF,
+	/*361*/ 0xFF,
+	/*362*/ 0xE2, 0x04, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x06, 0xD5, 0xA7, 0xFF,
+	/*373*/ 0xE2, 0x03, 0xE1, 0x06, 0xE6, 0xED, 0xE0, 0x06, 0xD6, 0x37, 0xFF,
+	/*384*/ 0xE2, 0x04, 0xE1, 0x06, 0xE6, 0xED, 0xE0, 0x06, 0xD3, 0x87, 0xFF,
+
+	/*395*/	0xE2, 0x0C, 0xE1, 0x00, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0x0B, 0xE8, 0x0B, 0xFF,
+	/*408*/ 0xE2, 0x0C, 0xE1, 0x03, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x00,
+			0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00,
+			0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00,
+			0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00,
+			0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xFF,
+	/*467*/ 0xE2, 0x0C, 0xE1, 0x00, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0x1B, 0xE8, 0x1B, 0xFF,
+	/*480*/ 0xFF,
+
+	/*481*/	0xFF
+};
+
+
+/*
+ * PSG_HuC6280
+ */
+
+class PSG_HuC6280 {
+private:
+	typedef struct {
+		uint16 frequency;
+		uint8 control;
+		uint8 balance;
+		uint8 waveform[32];
+		uint8 index;
+		int16 dda;
+		uint32 counter;
+	} channel_t;
+
+	double _clock;
+	double _rate;
+	uint8 _select;
+	uint8 _balance;
+	channel_t _channel[8];
+	int16 _volumeTable[32];
+	uint32 _noiseFreqTable[32];
+	uint32 _waveFreqTable[4096];
+
+public:
+	void init();
+	void reset();
+	void write(int offset, byte data);
+	void update(int16* samples, int sampleCnt);
+
+	PSG_HuC6280(double clock, double samplerate);
+};
+
+PSG_HuC6280::PSG_HuC6280(double clock, double samplerate) {
+	_clock = clock;
+	_rate = samplerate;
+
+	// Initialize PSG_HuC6280 emulator
+	init();
+}
+
+void PSG_HuC6280::init() {
+	int i;
+	double step;
+
+	// Loudest volume level for table
+	double level = 65535.0 / 6.0 / 32.0;
+
+	// Clear context
+	reset();
+
+	// Make waveform frequency table
+	for (i = 0; i < 4096; i++) {
+		step = ((_clock / _rate) * 4096) / (i+1);
+		_waveFreqTable[(1 + i) & 0xFFF] = (uint32)step;
+	}
+
+	// Make noise frequency table
+	for (i = 0; i < 32; i++) {
+		step = ((_clock / _rate) * 32) / (i+1);
+		_noiseFreqTable[i] = (uint32)step;
+	}
+
+	// Make volume table
+	// PSG_HuC6280 has 48dB volume range spread over 32 steps
+	step = 48.0 / 32.0;
+	for (i = 0; i < 31; i++) {
+		_volumeTable[i] = (uint16)level;
+		level /= pow(10.0, step / 20.0);
+	}
+	_volumeTable[31] = 0;
+}
+
+void PSG_HuC6280::reset() {
+	_select = 0;
+	_balance = 0xFF;
+	memset(_channel, 0, sizeof(_channel));
+	memset(_volumeTable, 0, sizeof(_volumeTable));
+	memset(_noiseFreqTable, 0, sizeof(_noiseFreqTable));
+	memset(_waveFreqTable, 0, sizeof(_waveFreqTable));
+}
+
+void PSG_HuC6280::write(int offset, byte data) {
+	channel_t *chan = &_channel[_select];
+
+	switch(offset & 0x0F) {
+	case 0x00: // Channel select
+		_select = data & 0x07;
+		break;
+
+	case 0x01: // Global balance
+		_balance  = data;
+		break;
+
+	case 0x02: // Channel frequency (LSB)
+		chan->frequency = (chan->frequency & 0x0F00) | data;
+		chan->frequency &= 0x0FFF;
+		break;
+
+	case 0x03: // Channel frequency (MSB)
+		chan->frequency = (chan->frequency & 0x00FF) | (data << 8);
+		chan->frequency &= 0x0FFF;
+		break;
+
+	case 0x04: // Channel control (key-on, DDA mode, volume)
+		// 1-to-0 transition of DDA bit resets waveform index
+		if ((chan->control & 0x40) && ((data & 0x40) == 0)) {
+			chan->index = 0;
+		}
+		chan->control = data;
+		break;
+
+	case 0x05: // Channel balance
+		chan->balance = data;
+		break;
+
+	case 0x06: // Channel waveform data
+		switch(chan->control & 0xC0) {
+		case 0x00:
+			chan->waveform[chan->index & 0x1F] = data & 0x1F;
+			chan->index = (chan->index + 1) & 0x1F;
+			break;
+
+		case 0x40:
+			break;
+
+		case 0x80:
+			chan->waveform[chan->index & 0x1F] = data & 0x1F;
+			chan->index = (chan->index + 1) & 0x1F;
+			break;
+
+		case 0xC0:
+			chan->dda = data & 0x1F;
+			break;
+		}
+
+		break;
+
+	case 0x07: // Noise control (enable, frequency)
+	case 0x08: // LFO frequency
+	case 0x09: // LFO control (enable, mode)
+		break;
+
+	default:
+		break;
+	}
+}
+
+void PSG_HuC6280::update(int16* samples, int sampleCnt) {
+	static const int scale_tab[] = {
+		0x00, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
+		0x10, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F
+	};
+	int ch;
+	int i;
+
+	int lmal = (_balance >> 4) & 0x0F;
+	int rmal = (_balance >> 0) & 0x0F;
+	int vll, vlr;
+
+	lmal = scale_tab[lmal];
+	rmal = scale_tab[rmal];
+
+	// Clear buffer
+	memset(samples, 0, 2 * sampleCnt * sizeof(int16));
+
+	for (ch = 0; ch < 6; ch++) {
+		// Only look at enabled channels
+		if (_channel[ch].control & 0x80) {
+			int lal = (_channel[ch].balance >> 4) & 0x0F;
+			int ral = (_channel[ch].balance >> 0) & 0x0F;
+			int al  = _channel[ch].control & 0x1F;
+
+			lal = scale_tab[lal];
+			ral = scale_tab[ral];
+
+			// Calculate volume just as the patent says
+			vll = (0x1F - lal) + (0x1F - al) + (0x1F - lmal);
+			if (vll > 0x1F) vll = 0x1F;
+
+			vlr = (0x1F - ral) + (0x1F - al) + (0x1F - rmal);
+			if (vlr > 0x1F) vlr = 0x1F;
+
+			vll = _volumeTable[vll];
+			vlr = _volumeTable[vlr];
+
+			// Check channel mode
+			if (_channel[ch].control & 0x40) {
+				/* DDA mode */
+				for (i = 0; i < sampleCnt; i++) {
+					samples[2*i]     += (int16)(vll * (_channel[ch].dda - 16));
+					samples[2*i + 1] += (int16)(vlr * (_channel[ch].dda - 16));
+				}
+			} else {
+				/* Waveform mode */
+				uint32 step = _waveFreqTable[_channel[ch].frequency];
+				for (i = 0; i < sampleCnt; i += 1) {
+					int offset;
+					int16 data;
+					offset = (_channel[ch].counter >> 12) & 0x1F;
+					_channel[ch].counter += step;
+					_channel[ch].counter &= 0x1FFFF;
+					data = _channel[ch].waveform[offset];
+					samples[2*i]     += (int16)(vll * (data - 16));
+					samples[2*i + 1] += (int16)(vlr * (data - 16));
+				}
+			}
+		}
+	}
+}
+
+
+/*
+ * Player_PCE
+ */
+
+void Player_PCE::PSG_Write(int reg, byte data) {
+	_psg->write(reg, data);
+}
+
+void Player_PCE::setupWaveform(byte bank) {
+	const byte *ptr = wave_table[bank];
+	PSG_Write(4, 0x40);
+	PSG_Write(4, 0x00);
+	for (int i = 0; i < 32; ++i) {
+		PSG_Write(6, ptr[i]);
+	}
+}
+
+// A541
+void Player_PCE::procA541(channel_t *channel) {
+	channel->soundDataPtr = NULL;
+	channel->controlVecShort10 = 0;
+
+	channel->controlVecShort03 = 0;
+	channel->controlVecShort06 = 0;
+	channel->controlVec8 = 0;
+	channel->controlVec9 = 0;
+	channel->controlVec10 = 0;
+	channel->soundUpdateCounter = 0;
+	channel->controlVec18 = 0;
+	channel->controlVec19 = 0;
+	channel->controlVec23 = false;
+	channel->controlVec24 = false;
+	channel->controlVec21 = 0;
+
+	channel->waveformCtrl = 0x80;
+}
+
+// A592
+void Player_PCE::startSound(int sound) {
+	channel_t *channel;
+	const uint16 *ptr = sounds[sound_table[sound]];
+
+	for (int i = 0; i < 6; ++i) {
+		channel = &channels[i];
+		procA541(channel);
+
+		channel->controlVec24 = true;
+		channel->waveformCtrl = 0;
+		channel->controlVec0 = 0;
+		channel->controlVec19 = 0;
+		channel->controlVec18 = 0;
+		channel->soundDataPtr = &data_table[*ptr++];
+	}
+}
+
+// A64B
+void Player_PCE::updateSound() {
+	for (int i = 0; i < 12; i++) {
+		channel_t *channel = &channels[i];
+		bool cond = true;
+		if (i < 6) {
+			channel->controlVec21 ^= 0xFF;
+			if (!channel->controlVec21)
+				cond = false;
+		}
+		if (cond) {
+			processSoundData(channel);
+			procAB7F(channel);
+			procAC24(channel);
+			channel->controlVec11 = (channel->controlVecShort10 >> 11) | 0x80;
+			channel->balance = channel->balance2;
+		}
+	}
+
+	for (int i = 0; i < 6; ++i) {
+		procA731(&channels[i]);
+	}
+}
+
+int Player_PCE::readBuffer(int16 *buffer, const int numSamples) {
+	int sampleCopyCnt;
+	int samplesLeft = numSamples;
+
+	Common::StackLock lock(_mutex);
+
+	while (true) {
+		// copy samples to output buffer
+		sampleCopyCnt = (samplesLeft < _sampleBufferCnt) ? samplesLeft : _sampleBufferCnt;
+		if (sampleCopyCnt > 0) {
+			memcpy(buffer, _sampleBuffer, sampleCopyCnt * sizeof(int16));
+			buffer += sampleCopyCnt;
+			samplesLeft -= sampleCopyCnt;
+			_sampleBufferCnt -= sampleCopyCnt;
+		}
+
+		if (samplesLeft == 0)
+			break;
+
+		// retrieve samples for one timer period
+		updateSound();
+		_psg->update(_sampleBuffer, _samplesPerPeriod / 2);
+		_sampleBufferCnt = _samplesPerPeriod;
+	}
+
+	// copy remaining samples to the front of the buffer
+	if (_sampleBufferCnt > 0) {
+		memmove(&_sampleBuffer[0],
+			&_sampleBuffer[_samplesPerPeriod - _sampleBufferCnt],
+			_sampleBufferCnt * sizeof(int16));
+	}
+
+	return numSamples;
+}
+
+void Player_PCE::procA731(channel_t *channel) {
+	PSG_Write(0, channel->id);
+	PSG_Write(2, channel->freq & 0xFF);
+	PSG_Write(3, (channel->freq >> 8) & 0xFF);
+
+	int tmp = channel->controlVec11;
+	if ((channel->controlVec11 & 0xC0) == 0x80) {
+		tmp = channel->controlVec11 & 0x1F;
+		if (tmp != 0) {
+			tmp -= channel->controlVec0;
+			if (tmp >= 0) {
+				tmp |= 0x80;
+			} else {
+				tmp = 0;
+			}
+		}
+	}
+
+	PSG_Write(5, channel->balance);
+	if ((channel->waveformCtrl & 0x80) == 0) {
+		channel->waveformCtrl |= 0x80;
+		PSG_Write(0, channel->id);
+		setupWaveform(channel->waveformCtrl & 0x7F);
+	}
+
+	PSG_Write(4, tmp);
+}
+
+// A793
+void Player_PCE::processSoundData(channel_t *channel) {
+	channel->soundUpdateCounter--;
+	if (channel->soundUpdateCounter > 0) {
+		return;
+	}
+
+	while (true) {
+		const byte *ptr = channel->soundDataPtr;
+		byte value = (ptr ? *ptr++ : 0xFF);
+		if (value < 0xD0) {
+			int mult = (value & 0x0F) + 1;
+			channel->soundUpdateCounter = mult * channel->controlVec1;
+			value >>= 4;
+			procAA62(channel, value);
+			channel->soundDataPtr = ptr;
+			return;
+		}
+
+		// jump_table (A7F7)
+		switch (value - 0xD0) {
+		case 0: /*A85A*/
+		case 1: /*A85D*/
+		case 2: /*A861*/
+		case 3: /*A865*/
+		case 4: /*A869*/
+		case 5: /*A86D*/
+		case 6: /*A871*/
+			channel->controlVec2 = (value - 0xD0) * 12;
+			break;
+		case 11: /*A8A8*/
+			channel->controlVecShort06 = (int8)*ptr++;
+			break;
+		case 16: /*A8C2*/
+			channel->controlVec1 = *ptr++;
+			break;
+		case 17: /*A8CA*/
+			channel->waveformCtrl = *ptr++;
+			break;
+		case 18: /*A8D2*/
+			channel->controlVec10 = *ptr++;
+			break;
+		case 22: /*A8F2*/
+			value = *ptr;
+			channel->balance = value;
+			channel->balance2 = value;
+			ptr++;
+			break;
+		case 24: /*A905*/
+			channel->controlVec23 = true;
+			break;
+		case 32: /*A921*/
+			ptr++;
+			break;
+		case 47:
+			channel->controlVec24 = false;
+			channel->controlVec10 &= 0x7F;
+			channel->controlVecShort10 &= 0x00FF;
+			return;
+		default:
+			// unused -> ignore
+			break;
+		}
+
+		channel->soundDataPtr = ptr;
+	}
+}
+
+void Player_PCE::procAA62(channel_t *channel, int a) {
+	procACEA(channel, a);
+	if (channel->controlVec23) {
+		channel->controlVec23 = false;
+		return;
+	}
+
+	channel->controlVec18 = 0;
+
+	channel->controlVec10 |= 0x80;
+	int y = channel->controlVec10 & 0x7F;
+	channel->controlBufferPos = &control_data[control_offsets[y]];
+	channel->controlVec5 = 0;
+}
+
+void Player_PCE::procAB7F(channel_t *channel) {
+	uint16 freqValue = channel->controlVecShort02;
+	channel->controlVecShort02 += channel->controlVecShort03;
+
+	int pos = freq_offset[channel->controlVec19] + channel->controlVec18;
+	freqValue += freq_table[pos];
+	if (freq_table[pos + 1] != 0x0800) {
+		channel->controlVec18++;
+	}
+	freqValue += channel->controlVecShort06;
+
+	channel->freq = freqValue;
+}
+
+void Player_PCE::procAC24(channel_t *channel) {
+	if ((channel->controlVec10 & 0x80) == 0)
+		return;
+
+	if (channel->controlVec5 == 0) {
+		const byte *ctrlPtr = channel->controlBufferPos;
+		byte value = *ctrlPtr++;
+		while (value >= 0xF0) {
+			if (value == 0xF0) {
+				channel->controlVecShort10 = READ_LE_UINT16(ctrlPtr);
+				ctrlPtr += 2;
+			} else if (value == 0xFF) {
+				channel->controlVec10 &= 0x7F;
+				return;
+			} else {
+				// unused
+			}
+			value = *ctrlPtr++;
+		}
+		channel->controlVec5 = value;
+		channel->controlVecShort09 = READ_LE_UINT16(ctrlPtr);
+		ctrlPtr += 2;
+		channel->controlBufferPos = ctrlPtr;
+	}
+
+	channel->controlVecShort10 += channel->controlVecShort09;
+	channel->controlVec5--;
+}
+
+void Player_PCE::procACEA(channel_t *channel, int a) {
+	int x = a +
+		channel->controlVec2 +
+		channel->controlVec8 +
+		channel->controlVec9;
+	channel->controlVecShort02 = lookup_table[x];
+}
+
+Player_PCE::Player_PCE(ScummEngine *scumm, Audio::Mixer *mixer) {
+	for (int i = 0; i < 12; ++i) {
+		memset(&channels[i], 0, sizeof(channel_t));
+		channels[i].id = i;
+	}
+
+	_mixer = mixer;
+	_sampleRate = _mixer->getOutputRate();
+	_vm = scumm;
+
+	_samplesPerPeriod = 2 * (int)(_sampleRate / UPDATE_FREQ);
+	_sampleBuffer = new int16[_samplesPerPeriod];
+	_sampleBufferCnt = 0;
+
+	_psg = new PSG_HuC6280(PSG_CLOCK, _sampleRate);
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_PCE::~Player_PCE() {
+	_mixer->stopHandle(_soundHandle);
+	delete[] _sampleBuffer;
+	delete _psg;
+}
+
+void Player_PCE::stopSound(int nr) {
+	// TODO: implement
+}
+
+void Player_PCE::stopAllSounds() {
+	// TODO: implement
+}
+
+int Player_PCE::getSoundStatus(int nr) const {
+	// TODO: status for each sound
+	for (int i = 0; i < 6; ++i) {
+		if (channels[i].controlVec24)
+			return 1;
+	}
+	return 0;
+}
+
+int Player_PCE::getMusicTimer() {
+	return 0;
+}
+
+} // End of namespace Scumm
+
+#endif // USE_RGB_COLOR
diff --git a/engines/scumm/player/pce.h b/engines/scumm/player/pce.h
new file mode 100644
index 0000000..427fb1a
--- /dev/null
+++ b/engines/scumm/player/pce.h
@@ -0,0 +1,133 @@
+/* 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_PCE_H
+#define SCUMM_PLAYER_PCE_H
+
+#include "common/scummsys.h"
+#include "common/mutex.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+// PCE sound engine is only used by Loom, which requires 16bit color support
+#ifdef USE_RGB_COLOR
+
+namespace Scumm {
+
+class ScummEngine;
+class PSG_HuC6280;
+
+class Player_PCE : public Audio::AudioStream, public MusicEngine {
+private:
+	struct channel_t {
+		int id;
+
+		byte controlVec0;
+		byte controlVec1;
+		byte controlVec2;
+		byte controlVec5;
+		byte balance;
+		byte balance2;
+		byte controlVec8;
+		byte controlVec9;
+		byte controlVec10;
+		byte controlVec11;
+		int16 soundUpdateCounter;
+		byte controlVec18;
+		byte controlVec19;
+		byte waveformCtrl;
+		byte controlVec21;
+		bool controlVec23;
+		bool controlVec24;
+
+		uint16 controlVecShort02;
+		uint16 controlVecShort03;
+		int16 controlVecShort06;
+		uint16 freq;
+		uint16 controlVecShort09;
+		uint16 controlVecShort10;
+
+		const byte* soundDataPtr;
+		const byte* controlBufferPos;
+	};
+
+public:
+	Player_PCE(ScummEngine *scumm, Audio::Mixer *mixer);
+	virtual ~Player_PCE();
+
+	virtual void setMusicVolume(int vol) { _maxvol = vol; }
+	virtual void startSound(int sound);
+	virtual void stopSound(int sound);
+	virtual void stopAllSounds();
+	virtual int  getSoundStatus(int sound) const;
+	virtual int  getMusicTimer();
+
+	// AudioStream API
+	int readBuffer(int16 *buffer, const int numSamples);
+	bool isStereo() const { return true; }
+	bool endOfData() const { return false; }
+	int getRate() const { return _sampleRate; }
+
+private:
+	ScummEngine *_vm;
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundHandle;
+	int _sampleRate;
+	int _maxvol;
+
+private:
+	PSG_HuC6280 *_psg;
+	channel_t channels[12];
+	Common::Mutex _mutex;
+
+	// number of samples per timer period
+	int _samplesPerPeriod;
+	int16* _sampleBuffer;
+	int _sampleBufferCnt;
+
+	void init();
+	bool isPlaying();
+
+	void PSG_Write(int reg, byte data);
+
+	void setupWaveform(byte bank);
+	void procA541(channel_t *channel);
+	void updateSound();
+	void procA731(channel_t *channel);
+	void processSoundData(channel_t *channel);
+	void procA9F3(int x);
+	void procAA62(channel_t *channel, int a);
+	uint16 procAAF6(int x);
+	void procAB7F(channel_t *channel);
+	void procAC24(channel_t *channel);
+	void procACEA(channel_t *channel, int a);
+	void procAD21(int a, int x);
+	void procAD29(int value);
+	void procAD3D(int a, int x);
+};
+
+} // End of namespace Scumm
+
+#endif // USE_RGB_COLOR
+
+#endif
diff --git a/engines/scumm/player/sid.cpp b/engines/scumm/player/sid.cpp
new file mode 100644
index 0000000..ae5346f
--- /dev/null
+++ b/engines/scumm/player/sid.cpp
@@ -0,0 +1,1384 @@
+/* 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 DISABLE_SID
+
+#include "engines/engine.h"
+#include "scumm/player/sid.h"
+#include "scumm/scumm.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+/*
+ * The player's update() routine is called once per (NTSC/PAL) frame as it is
+ * called by the VIC Rasterline interrupt handler which is in turn called
+ * approx. 50 (PAL) or 60 (NTSC) times per second.
+ * The SCUMM V0/V1 music playback routines or sound data have not been adjusted
+ * to PAL systems. As a consequence, music is played audibly (-16%) slower
+ * on PAL systems.
+ * In addition, the SID oscillator frequency depends on the video clock too.
+ * As SCUMM games use an NTSC frequency table for both NTSC and PAL versions
+ * all tone frequencies on PAL systems are slightly (-4%) lower than on NTSC ones.
+ *
+ * For more info on the SID chip see:
+ * - http://www.dopeconnection.net/C64_SID.htm (German)
+ * For more info on the VIC chip see:
+ * - http://www.htu.tugraz.at/~herwig/c64/man-vic.php (German)
+ * - http://www.c64-wiki.de/index.php/VIC (German)
+ */
+
+struct TimingProps {
+	double clockFreq;
+	int cyclesPerFrame;
+};
+
+static const TimingProps timingProps[2] = {
+	{ 17734472.0 / 18, 312 * 63 }, // PAL:  312*63 cycles/frame @  985248 Hz (~50Hz)
+	{ 14318180.0 / 14, 263 * 65 }  // NTSC: 263*65 cycles/frame @ 1022727 Hz (~60Hz)
+};
+
+static const uint8 BITMASK[7] = {
+	0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40
+};
+static const uint8 BITMASK_INV[7] = {
+	0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF
+};
+
+static const int SID_REG_OFFSET[7] = {
+	0, 7, 14, 21, 2, 9, 16
+};
+
+// NTSC frequency table (also used for PAL versions).
+// FREQ_TBL[i] = tone_freq[i] * 2^24 / clockFreq
+static const uint16 FREQ_TBL[97] = {
+	0x0000, 0x010C, 0x011C, 0x012D, 0x013E, 0x0151, 0x0166, 0x017B,
+	0x0191, 0x01A9, 0x01C3, 0x01DD, 0x01FA, 0x0218, 0x0238, 0x025A,
+	0x027D, 0x02A3, 0x02CC, 0x02F6, 0x0323, 0x0353, 0x0386, 0x03BB,
+	0x03F4, 0x0430, 0x0470, 0x04B4, 0x04FB, 0x0547, 0x0598, 0x05ED,
+	0x0647, 0x06A7, 0x070C, 0x0777, 0x07E9, 0x0861, 0x08E1, 0x0968,
+	0x09F7, 0x0A8F, 0x0B30, 0x0BDA, 0x0C8F, 0x0D4E, 0x0E18, 0x0EEF,
+	0x0FD2, 0x10C3, 0x11C3, 0x12D1, 0x13EF, 0x151F, 0x1660, 0x17B5,
+	0x191E, 0x1A9C, 0x1C31, 0x1DDF, 0x1FA5, 0x2187, 0x2386, 0x25A2,
+	0x27DF, 0x2A3E, 0x2CC1, 0x2F6B, 0x323C, 0x3539, 0x3863, 0x3BBE,
+	0x3F4B, 0x430F, 0x470C, 0x4B45, 0x4FBF, 0x547D, 0x5983, 0x5ED6,
+	0x6479, 0x6A73, 0x70C7, 0x777C, 0x7E97, 0x861E, 0x8E18, 0x968B,
+	0x9F7E, 0xA8FA, 0xB306, 0xBDAC, 0xC8F3, 0xD4E6, 0xE18F, 0xEEF8,
+	0xFD2E
+};
+
+static const int SONG_CHANNEL_OFFSET[3] = { 6, 8, 10 };
+static const int RES_ID_CHANNEL[3] = { 3, 4, 5 };
+
+#define LOBYTE_(a) ((a) & 0xFF)
+#define HIBYTE_(a) (((a) >> 8) & 0xFF)
+
+#define GETBIT(var, pos) ((var) & (1<<(pos)))
+
+void Player_SID::handleMusicBuffer() { // $33cd
+	int channel = 2;
+	while (channel >= 0) {
+		if ((statusBits1A & BITMASK[channel]) == 0 ||
+		    (busyChannelBits & BITMASK[channel]) != 0) {
+			--channel;
+			continue;
+		}
+
+		if (setupSongFileData() == 1)
+			return;
+
+		uint8* l_chanFileDataPtr = chanFileData[channel];
+
+		uint16 l_freq = 0;
+		bool l_keepFreq = false;
+
+		int y = 0;
+		uint8 curByte = l_chanFileDataPtr[y++];
+
+		// freq or 0/0xFF
+		if (curByte == 0) {
+			func_3674(channel);
+			if (!isMusicPlaying)
+				return;
+			continue;
+		} else if (curByte == 0xFF) {
+			l_keepFreq = true;
+		} else {
+			l_freq = FREQ_TBL[curByte];
+		}
+
+		uint8 local1 = 0;
+		curByte = l_chanFileDataPtr[y++];
+		bool isLastCmdByte = (curByte & 0x80) != 0;
+		uint16 curStepSum = stepTbl[curByte & 0x7f];
+
+		for (int i = 0; !isLastCmdByte && (i < 2); ++i) {
+			curByte = l_chanFileDataPtr[y++];
+			isLastCmdByte = (curByte & 0x80) != 0;
+			if (curByte & 0x40) {
+				// note: bit used in zak theme (95) only (not used/handled in MM)
+				_music_timer = curByte & 0x3f;
+			} else {
+				local1 = curByte & 0x3f;
+			}
+		}
+
+		chanFileData[channel] += y;
+		chanDataOffset[channel] += y;
+
+		uint8 *l_chanBuf = getResource(RES_ID_CHANNEL[channel]);
+
+		if (local1 != 0) {
+			// TODO: signed or unsigned?
+			uint16 offset = READ_LE_UINT16(&actSongFileData[local1*2 + 12]);
+			l_chanFileDataPtr = actSongFileData + offset;
+
+			// next five bytes: freqDelta, attack, sustain and phase bit
+			for (int i = 0; i < 5; ++i) {
+				l_chanBuf[15 + i] = l_chanFileDataPtr[i];
+			}
+			phaseBit[channel] = l_chanFileDataPtr[4];
+
+			for (int i = 0; i < 17; ++i) {
+				l_chanBuf[25 + i] = l_chanFileDataPtr[5 + i];
+			}
+		}
+
+		if (l_keepFreq) {
+			if (!releasePhase[channel]) {
+				l_chanBuf[10] &= 0xfe; // release phase
+			}
+			releasePhase[channel] = true;
+		} else {
+			if (releasePhase[channel]) {
+				l_chanBuf[19] = phaseBit[channel];
+				l_chanBuf[10] |= 0x01; // attack phase
+			}
+			l_chanBuf[11] = LOBYTE_(l_freq);
+			l_chanBuf[12] = HIBYTE_(l_freq);
+			releasePhase[channel] = false;
+		}
+
+		// set counter value for frequency update (freqDeltaCounter)
+		l_chanBuf[13] = LOBYTE_(curStepSum);
+		l_chanBuf[14] = HIBYTE_(curStepSum);
+
+		_soundQueue[channel] = RES_ID_CHANNEL[channel];
+		processSongData(channel);
+		_soundQueue[channel+4] = RES_ID_CHANNEL[channel];
+		processSongData(channel+4);
+		--channel;
+	}
+}
+
+int Player_SID::setupSongFileData() { // $36cb
+	// no song playing
+	// TODO: remove (never NULL)
+	if (_music == NULL) {
+		for (int i = 2; i >= 0; --i) {
+			if (songChannelBits & BITMASK[i]) {
+				func_3674(i);
+			}
+		}
+		return 1;
+	}
+
+	// no new song
+	songFileOrChanBufData = _music;
+	if (_music == actSongFileData) {
+		return 0;
+	}
+
+	// new song selected
+	actSongFileData = _music;
+	for (int i = 0; i < 3; ++i) {
+		chanFileData[i] = _music + chanDataOffset[i];
+	}
+
+	return -1;
+}
+
+//x:0..2
+void Player_SID::func_3674(int channel) { // $3674
+	statusBits1B &= BITMASK_INV[channel];
+	if (statusBits1B == 0) {
+		isMusicPlaying = false;
+		unlockCodeLocation();
+		safeUnlockResource(resID_song);
+		for (int i = 0; i < 3; ++i) {
+			safeUnlockResource(RES_ID_CHANNEL[i]);
+		}
+	}
+
+	chanPrio[channel] = 2;
+
+	statusBits1A &= BITMASK_INV[channel];
+	phaseBit[channel] = 0;
+
+	func_4F45(channel);
+}
+
+void Player_SID::resetPlayerState() { // $48f7
+	for (int i = 6; i >= 0; --i)
+		releaseChannel(i);
+
+	isMusicPlaying = false;
+	unlockCodeLocation(); // does nothing
+	statusBits1B = 0;
+	statusBits1A = 0;
+	freeChannelCount = 3;
+	swapPrepared = false;
+	filterSwapped = false;
+	pulseWidthSwapped = false;
+	//var5163 = 0;
+}
+
+void Player_SID::resetSID() { // $48D8
+	SIDReg24 = 0x0f;
+
+	SID_Write( 4, 0);
+	SID_Write(11, 0);
+	SID_Write(18, 0);
+	SID_Write(23, 0);
+	SID_Write(21, 0);
+	SID_Write(22, 0);
+	SID_Write(24, SIDReg24);
+
+	resetPlayerState();
+}
+
+void Player_SID::update() { // $481B
+	if (initializing)
+		return;
+
+	if (_soundInQueue) {
+		for (int i = 6; i >= 0; --i) {
+			if (_soundQueue[i] != -1)
+				processSongData(i);
+		}
+		_soundInQueue = false;
+	}
+
+	// no sound
+	if (busyChannelBits == 0)
+		return;
+
+	for (int i = 6; i >= 0; --i) {
+		if (busyChannelBits & BITMASK[i]) {
+			updateFreq(i);
+		}
+	}
+
+	// seems to be used for background (prio=1?) sounds.
+	// If a bg sound cannot be played because all SID
+	// voices are used by higher priority sounds, the
+	// bg sound's state is updated here so it will be at
+	// the correct state when a voice is available again.
+	if (swapPrepared) {
+		swapVars(0, 0);
+		swapVarLoaded = true;
+		updateFreq(0);
+		swapVars(0, 0);
+		if (pulseWidthSwapped) {
+			swapVars(4, 1);
+			updateFreq(4);
+			swapVars(4, 1);
+		}
+		swapVarLoaded = false;
+	}
+
+	for (int i = 6; i >= 0; --i) {
+		if (busyChannelBits & BITMASK[i])
+			setSIDWaveCtrlReg(i);
+	};
+
+	if (isMusicPlaying) {
+		handleMusicBuffer();
+	}
+
+	return;
+}
+
+// channel: 0..6
+void Player_SID::processSongData(int channel) { // $4939
+	// always: _soundQueue[channel] != -1
+	// -> channelMap[channel] != -1
+	channelMap[channel] = _soundQueue[channel];
+	_soundQueue[channel] = -1;
+	songPosUpdateCounter[channel] = 0;
+
+	isVoiceChannel = (channel < 3);
+
+	songFileOrChanBufOffset[channel] = vec6[channel];
+
+	setupSongPtr(channel);
+
+	//vec5[channel] = songFileOrChanBufData; // not used
+
+	if (songFileOrChanBufData == NULL) { // chanBuf (4C1C)
+		/*
+		// TODO: do we need this?
+		LOBYTE_(vec20[channel]) = 0;
+		LOBYTE_(songPosPtr[channel]) = LOBYTE_(songFileOrChanBufOffset[channel]);
+		*/
+		releaseResourceUnk(channel);
+		return;
+	}
+
+	vec20[channel] = songFileOrChanBufData; // chanBuf (4C1C)
+	songPosPtr[channel] = songFileOrChanBufData + songFileOrChanBufOffset[channel]; // chanBuf (4C1C)
+	uint8* ptr1 = songPosPtr[channel];
+
+	int y = -1;
+	if (channel < 4) {
+		++y;
+		if (channel == 3) {
+			readSetSIDFilterAndProps(&y, ptr1);
+		} else if (statusBits1A & BITMASK[channel]) {
+			++y;
+		} else { // channel = 0/1/2
+			waveCtrlReg[channel] = ptr1[y];
+
+			++y;
+			if (ptr1[y] & 0x0f) {
+				// filter on for voice channel
+				SIDReg23 |= BITMASK[channel];
+			} else {
+				// filter off for voice channel
+				SIDReg23 &= BITMASK_INV[channel];
+			}
+			SID_Write(23, SIDReg23);
+		}
+	}
+
+	saveSongPos(y, channel);
+	busyChannelBits |= BITMASK[channel];
+	readSongChunk(channel);
+}
+
+void Player_SID::readSetSIDFilterAndProps(int *offset, uint8* dataPtr) {  // $49e7
+	SIDReg23 |= dataPtr[*offset];
+	SID_Write(23, SIDReg23);
+	++*offset;
+	SIDReg24 = dataPtr[*offset];
+	SID_Write(24, SIDReg24);
+}
+
+void Player_SID::saveSongPos(int y, int channel) {
+	++y;
+	songPosPtr[channel] += y;
+	songFileOrChanBufOffset[channel] += y;
+}
+
+// channel: 0..6
+void Player_SID::updateFreq(int channel) {
+	isVoiceChannel = (channel < 3);
+
+	--freqDeltaCounter[channel];
+	if (freqDeltaCounter[channel] < 0) {
+		readSongChunk(channel);
+	} else {
+		freqReg[channel] += freqDelta[channel];
+	}
+	setSIDFreqAS(channel);
+}
+
+void Player_SID::resetFreqDelta(int channel) {
+	freqDeltaCounter[channel] = 0;
+	freqDelta[channel] = 0;
+}
+
+void Player_SID::readSongChunk(int channel) { // $4a6b
+	while (true) {
+		if (setupSongPtr(channel) == 1) {
+			// do something with code resource
+			releaseResourceUnk(1);
+			return;
+		}
+
+		uint8* ptr1 = songPosPtr[channel];
+
+		//curChannelActive = true;
+
+		uint8 l_cmdByte = ptr1[0];
+		if (l_cmdByte == 0) {
+			//curChannelActive = false;
+			songPosUpdateCounter[channel] = 0;
+
+			var481A = -1;
+			releaseChannel(channel);
+			return;
+		}
+
+		//vec19[channel] = l_cmdByte;
+
+		// attack (1) / release (0) phase
+		if (isVoiceChannel) {
+			if (GETBIT(l_cmdByte, 0))
+				waveCtrlReg[channel] |= 0x01; // start attack phase
+			else
+				waveCtrlReg[channel] &= 0xfe; // start release phase
+		}
+
+		// channel finished bit
+		if (GETBIT(l_cmdByte, 1)) {
+			var481A = -1;
+			releaseChannel(channel);
+			return;
+		}
+
+		int y = 0;
+
+		// frequency
+		if (GETBIT(l_cmdByte, 2)) {
+			y += 2;
+			freqReg[channel] = READ_LE_UINT16(&ptr1[y-1]);
+			if (!GETBIT(l_cmdByte, 6)) {
+				y += 2;
+				freqDeltaCounter[channel] = READ_LE_UINT16(&ptr1[y-1]);
+				y += 2;
+				freqDelta[channel] = READ_LE_UINT16(&ptr1[y-1]);
+			} else {
+				resetFreqDelta(channel);
+			}
+		} else {
+			resetFreqDelta(channel);
+		}
+
+		// attack / release
+		if (isVoiceChannel && GETBIT(l_cmdByte, 3)) {
+			// start release phase
+			waveCtrlReg[channel] &= 0xfe;
+			setSIDWaveCtrlReg(channel);
+
+			++y;
+			attackReg[channel] = ptr1[y];
+			++y;
+			sustainReg[channel] = ptr1[y];
+
+			// set attack (1) or release (0) phase
+			waveCtrlReg[channel]  |= (l_cmdByte & 0x01);
+		}
+
+		if (GETBIT(l_cmdByte, 4)) {
+			++y;
+			uint8 curByte = ptr1[y];
+
+			// pulse width
+			if (isVoiceChannel && GETBIT(curByte, 0)) {
+				int reg = SID_REG_OFFSET[channel+4];
+
+				y += 2;
+				SID_Write(reg, ptr1[y-1]);
+				SID_Write(reg+1, ptr1[y]);
+			}
+
+			if (GETBIT(curByte, 1)) {
+				++y;
+				readSetSIDFilterAndProps(&y, ptr1);
+
+				y += 2;
+				SID_Write(21, ptr1[y-1]);
+				SID_Write(22, ptr1[y]);
+			}
+
+			if (GETBIT(curByte, 2)) {
+				resetFreqDelta(channel);
+
+				y += 2;
+				freqDeltaCounter[channel] = READ_LE_UINT16(&ptr1[y-1]);
+			}
+		}
+
+		// set waveform (?)
+		if (GETBIT(l_cmdByte, 5)) {
+			++y;
+			waveCtrlReg[channel] = (waveCtrlReg[channel] & 0x0f) | ptr1[y];
+		}
+
+		// song position
+		if (GETBIT(l_cmdByte, 7)) {
+			if (songPosUpdateCounter[channel] == 1) {
+				y += 2;
+				--songPosUpdateCounter[channel];
+				saveSongPos(y, channel);
+			} else {
+				// looping / skipping / ...
+				++y;
+				songPosPtr[channel] -= ptr1[y];
+				songFileOrChanBufOffset[channel] -= ptr1[y];
+
+				++y;
+				if (songPosUpdateCounter[channel] == 0) {
+					songPosUpdateCounter[channel] = ptr1[y];
+				} else {
+					--songPosUpdateCounter[channel];
+				}
+			}
+		} else {
+			saveSongPos(y, channel);
+			return;
+		}
+	}
+}
+
+/**
+ * Sets frequency, attack and sustain register
+ */
+void Player_SID::setSIDFreqAS(int channel) { // $4be6
+	if (swapVarLoaded)
+		return;
+	int reg = SID_REG_OFFSET[channel];
+	SID_Write(reg,   LOBYTE_(freqReg[channel]));   // freq/pulseWidth voice 1/2/3
+	SID_Write(reg+1, HIBYTE_(freqReg[channel]));
+	if (channel < 3) {
+		SID_Write(reg+5, attackReg[channel]); // attack
+		SID_Write(reg+6, sustainReg[channel]); // sustain
+	}
+}
+
+void Player_SID::setSIDWaveCtrlReg(int channel) { // $4C0D
+	if (channel < 3) {
+		int reg = SID_REG_OFFSET[channel];
+		SID_Write(reg+4, waveCtrlReg[channel]);
+	}
+}
+
+// channel: 0..6
+int Player_SID::setupSongPtr(int channel) { // $4C1C
+	//resID:5,4,3,songid
+	int resID = channelMap[channel];
+
+	// TODO: when does this happen, only if resID == 0?
+	if (getResource(resID) == NULL) {
+		releaseResourceUnk(resID);
+		if (resID == bgSoundResID) {
+			bgSoundResID = 0;
+			bgSoundActive = false;
+			swapPrepared = false;
+			pulseWidthSwapped = false;
+		}
+		return 1;
+	}
+
+	songFileOrChanBufData = getResource(resID); // chanBuf (4C1C)
+	if (songFileOrChanBufData == vec20[channel]) {
+		return 0;
+	} else {
+		vec20[channel] = songFileOrChanBufData;
+		songPosPtr[channel] = songFileOrChanBufData + songFileOrChanBufOffset[channel];
+		return -1;
+	}
+}
+
+// ignore: no effect
+// chanResIndex: 3,4,5 or 58
+void Player_SID::unlockResource(int chanResIndex) { // $4CDA
+	if ((resStatus[chanResIndex] & 0x7F) != 0)
+		--resStatus[chanResIndex];
+}
+
+void Player_SID::countFreeChannels() { // $4f26
+	freeChannelCount = 0;
+	for (int i = 0; i < 3; ++i) {
+		if (GETBIT(usedChannelBits, i) == 0)
+			++freeChannelCount;
+	}
+}
+
+void Player_SID::func_4F45(int channel) { // $4F45
+	if (swapVarLoaded) {
+		if (channel == 0) {
+			swapPrepared = false;
+			resetSwapVars();
+		}
+		pulseWidthSwapped = false;
+	} else {
+		if (channel == 3) {
+			filterUsed = false;
+		}
+
+		if (chanPrio[channel] == 1) {
+			if (var481A == 1)
+				prepareSwapVars(channel);
+			else if (channel < 3)
+				clearSIDWaveform(channel);
+		} else if (channel < 3 && bgSoundActive && swapPrepared &&
+		    !(filterSwapped && filterUsed))
+		{
+			busyChannelBits |= BITMASK[channel];
+			useSwapVars(channel);
+			waveCtrlReg[channel] |= 0x01;
+			setSIDWaveCtrlReg(channel);
+
+			safeUnlockResource(channelMap[channel]);
+			return;
+		}
+
+		chanPrio[channel] = 0;
+		usedChannelBits &= BITMASK_INV[channel];
+		countFreeChannels();
+	}
+
+	int resIndex = channelMap[channel];
+	channelMap[channel] = 0;
+	safeUnlockResource(resIndex);
+}
+
+// chanResIndex: 3,4,5 or 58
+void Player_SID::safeUnlockResource(int resIndex) { // $4FEA
+	if (!isMusicPlaying) {
+		unlockResource(resIndex);
+	}
+}
+
+void Player_SID::releaseResource(int resIndex) { // $5031
+	releaseResChannels(resIndex);
+	if (resIndex == bgSoundResID && var481A == -1) {
+		safeUnlockResource(resIndex);
+
+		bgSoundResID = 0;
+		bgSoundActive = false;
+		swapPrepared = false;
+		pulseWidthSwapped = false;
+
+		resetSwapVars();
+	}
+}
+
+void Player_SID::releaseResChannels(int resIndex) { // $5070
+	for (int i = 3; i >= 0; --i) {
+		if (resIndex == channelMap[i]) {
+			releaseChannel(i);
+		}
+	}
+}
+
+void Player_SID::stopSound_intern(int soundResID) { // $5093
+	for (int i = 0; i < 7; ++i) {
+		if (soundResID == _soundQueue[i]) {
+			_soundQueue[i] = -1;
+		}
+	}
+	var481A = -1;
+	releaseResource(soundResID);
+}
+
+void Player_SID::stopMusic_intern() { // $4CAA
+	statusBits1B = 0;
+	isMusicPlaying = false;
+
+	if (resID_song != 0) {
+		unlockResource(resID_song);
+	}
+
+	chanPrio[0] = 2;
+	chanPrio[1] = 2;
+	chanPrio[2] = 2;
+
+	statusBits1A = 0;
+	phaseBit[0] = 0;
+	phaseBit[1] = 0;
+	phaseBit[2] = 0;
+}
+
+void Player_SID::releaseResourceUnk(int resIndex) { // $50A4
+	var481A = -1;
+	releaseResource(resIndex);
+}
+
+// a: 0..6
+void Player_SID::releaseChannel(int channel) {
+	stopChannel(channel);
+	if (channel >= 4) {
+		return;
+	}
+	if (channel < 3) {
+		SIDReg23Stuff = SIDReg23;
+		clearSIDWaveform(channel);
+	}
+	func_4F45(channel);
+	if (channel >= 3) {
+		return;
+	}
+	if ((SIDReg23 != SIDReg23Stuff) &&
+	    (SIDReg23 & 0x07) == 0)
+	{
+		if (filterUsed) {
+			func_4F45(3);
+			stopChannel(3);
+		}
+	}
+
+	stopChannel(channel + 4);
+}
+
+void Player_SID::clearSIDWaveform(int channel) {
+	if (!isMusicPlaying && var481A == -1) {
+		waveCtrlReg[channel] &= 0x0e;
+		setSIDWaveCtrlReg(channel);
+	}
+}
+
+void Player_SID::stopChannel(int channel) {
+	songPosUpdateCounter[channel] = 0;
+	// clear "channel" bit
+	busyChannelBits &= BITMASK_INV[channel];
+	if (channel >= 4) {
+		// pulsewidth = 0
+		channelMap[channel] = 0;
+	}
+}
+
+// channel: 0..6, swapIndex: 0..2
+void Player_SID::swapVars(int channel, int swapIndex) { // $51a5
+	if (channel < 3) {
+		SWAP(attackReg[channel], swapAttack[swapIndex]);
+		SWAP(sustainReg[channel], swapSustain[swapIndex]);
+	}
+	//SWAP(vec5[channel],  swapVec5[swapIndex]);  // not used
+	//SWAP(vec19[channel], swapVec19[swapIndex]); // not used
+
+	SWAP(chanPrio[channel], swapSongPrio[swapIndex]);
+	SWAP(channelMap[channel], swapVec479C[swapIndex]);
+	SWAP(songPosUpdateCounter[channel], swapSongPosUpdateCounter[swapIndex]);
+	SWAP(waveCtrlReg[channel], swapWaveCtrlReg[swapIndex]);
+	SWAP(songPosPtr[channel],  swapSongPosPtr[swapIndex]);
+	SWAP(freqReg[channel],  swapFreqReg[swapIndex]);
+	SWAP(freqDeltaCounter[channel], swapVec11[swapIndex]);
+	SWAP(freqDelta[channel], swapVec10[swapIndex]);
+	SWAP(vec20[channel], swapVec20[swapIndex]);
+	SWAP(songFileOrChanBufOffset[channel],  swapVec8[swapIndex]);
+}
+
+void Player_SID::resetSwapVars() { // $52d0
+	for (int i = 0; i < 2; ++i) {
+		swapAttack[i] = 0;
+		swapSustain[i] = 0;
+	}
+	for (int i = 0; i < 3; ++i) {
+		swapVec5[i] = 0;
+		swapSongPrio[i] = 0;
+		swapVec479C[i] = 0;
+		swapVec19[i] = 0;
+		swapSongPosUpdateCounter[i] = 0;
+		swapWaveCtrlReg[i] = 0;
+		swapSongPosPtr[i] = 0;
+		swapFreqReg[i] = 0;
+		swapVec11[i] = 0;
+		swapVec10[i] = 0;
+		swapVec20[i] = 0;
+		swapVec8[i] = 0;
+	}
+}
+
+void Player_SID::prepareSwapVars(int channel) { // $52E5
+	if (channel >= 4)
+		return;
+
+	if (channel < 3) {
+		if (!keepSwapVars) {
+			resetSwapVars();
+		}
+		swapVars(channel, 0);
+		if (busyChannelBits & BITMASK[channel+4]) {
+			swapVars(channel+4, 1);
+			pulseWidthSwapped = true;
+		}
+	} else if (channel == 3) {
+		SIDReg24_HiNibble = SIDReg24 & 0x70;
+		resetSwapVars();
+		keepSwapVars = true;
+		swapVars(3, 2);
+		filterSwapped = true;
+	}
+	swapPrepared = true;
+}
+
+void Player_SID::useSwapVars(int channel) { // $5342
+	if (channel >= 3)
+		return;
+
+	swapVars(channel, 0);
+	setSIDFreqAS(channel);
+	if (pulseWidthSwapped) {
+		swapVars(channel+4, 1);
+		setSIDFreqAS(channel+4);
+	}
+	if (filterSwapped) {
+		swapVars(3, 2);
+
+		// resonating filter freq. or voice-to-filter mapping?
+		SIDReg23 = (SIDReg23Stuff & 0xf0) | BITMASK[channel];
+		SID_Write(23, SIDReg23);
+
+		// filter props
+		SIDReg24 = (SIDReg24 & 0x0f) | SIDReg24_HiNibble;
+		SID_Write(24, SIDReg24);
+
+		// filter freq.
+		SID_Write(21, LOBYTE_(freqReg[3]));
+		SID_Write(22, HIBYTE_(freqReg[3]));
+	} else {
+		SIDReg23 = SIDReg23Stuff & BITMASK_INV[channel];
+		SID_Write(23, SIDReg23);
+	}
+
+	swapPrepared = false;
+	pulseWidthSwapped = false;
+	keepSwapVars = false;
+	SIDReg24_HiNibble = 0;
+	filterSwapped = false;
+}
+
+// ignore: no effect
+// resIndex: 3,4,5 or 58
+void Player_SID::lockResource(int resIndex) { // $4ff4
+	if (!isMusicPlaying)
+		++resStatus[resIndex];
+}
+
+void Player_SID::reserveChannel(int channel, uint8 prioValue, int chanResIndex) { // $4ffe
+	if (channel == 3) {
+		filterUsed = true;
+	} else if (channel < 3) {
+		usedChannelBits |= BITMASK[channel];
+		countFreeChannels();
+	}
+
+	chanPrio[channel] = prioValue;
+	lockResource(chanResIndex);
+}
+
+// ignore: no effect
+void Player_SID::unlockCodeLocation() { // $513e
+	resStatus[1] &= 0x80;
+	resStatus[2] &= 0x80;
+}
+
+// ignore: no effect
+void Player_SID::lockCodeLocation() { // $514f
+	resStatus[1] |= 0x01;
+	resStatus[2] |= 0x01;
+}
+
+void Player_SID::initMusic(int songResIndex) { // $7de6
+	unlockResource(resID_song);
+
+	resID_song = songResIndex;
+	_music = getResource(resID_song);
+	if (_music == NULL) {
+		return;
+	}
+
+	// song base address
+	uint8* songFileDataPtr = _music;
+	actSongFileData = _music;
+
+	initializing = true;
+	_soundInQueue = false;
+	isMusicPlaying = false;
+
+	unlockCodeLocation();
+	resetPlayerState();
+
+	lockResource(resID_song);
+	buildStepTbl(songFileDataPtr[5]);
+
+	// fetch sound
+	songChannelBits = songFileDataPtr[4];
+	for (int i = 2; i >= 0; --i) {
+		if ((songChannelBits & BITMASK[i]) != 0) {
+			func_7eae(i, songFileDataPtr);
+		}
+	}
+
+	isMusicPlaying = true;
+	lockCodeLocation();
+
+	SIDReg23 &= 0xf0;
+	SID_Write(23, SIDReg23);
+
+	handleMusicBuffer();
+
+	initializing = false;
+	_soundInQueue = true;
+}
+
+// params:
+//   channel: channel 0..2
+void Player_SID::func_7eae(int channel, uint8* songFileDataPtr) {
+	int pos = SONG_CHANNEL_OFFSET[channel];
+	chanDataOffset[channel] = READ_LE_UINT16(&songFileDataPtr[pos]);
+	chanFileData[channel] = songFileDataPtr + chanDataOffset[channel];
+
+	//vec5[channel+4] = vec5[channel] = CHANNEL_BUFFER_ADDR[RES_ID_CHANNEL[channel]]; // not used
+	vec6[channel+4] = 0x0019;
+	vec6[channel]   = 0x0008;
+
+	func_819b(channel);
+
+	waveCtrlReg[channel] = 0;
+}
+
+void Player_SID::func_819b(int channel) {
+	reserveChannel(channel, 127, RES_ID_CHANNEL[channel]);
+
+	statusBits1B |= BITMASK[channel];
+	statusBits1A |= BITMASK[channel];
+}
+
+void Player_SID::buildStepTbl(int step) { // $82B4
+	stepTbl[0] = 0;
+	stepTbl[1] = step - 2;
+	for (int i = 2; i < 33; ++i) {
+		stepTbl[i] = stepTbl[i-1] + step;
+	}
+}
+
+int Player_SID::reserveSoundFilter(uint8 value, uint8 chanResIndex) { // $4ED0
+	int channel = 3;
+	reserveChannel(channel, value, chanResIndex);
+	return channel;
+}
+
+int Player_SID::reserveSoundVoice(uint8 value, uint8 chanResIndex) { // $4EB8
+	for (int i = 2; i >= 0; --i) {
+		if ((usedChannelBits & BITMASK[i]) == 0) {
+			reserveChannel(i, value, chanResIndex);
+			return i;
+		}
+	}
+	return 0;
+}
+
+void Player_SID::findLessPrioChannels(uint8 soundPrio) { // $4ED8
+	minChanPrio = 127;
+
+	chansWithLowerPrioCount = 0;
+	for (int i = 2; i >= 0; --i) {
+		if (usedChannelBits & BITMASK[i]) {
+			if (chanPrio[i] < soundPrio)
+				++chansWithLowerPrioCount;
+			if (chanPrio[i] < minChanPrio) {
+				minChanPrio = chanPrio[i];
+				minChanPrioIndex = i;
+			}
+		}
+	}
+
+	if (chansWithLowerPrioCount == 0)
+		return;
+
+	if (soundPrio >= chanPrio[3]) {
+		actFilterHasLowerPrio = true;
+	} else {
+		/* TODO: is this really a no-op?
+		if (minChanPrioIndex < chanPrio[3])
+			minChanPrioIndex = minChanPrioIndex;
+		*/
+
+		actFilterHasLowerPrio = false;
+	}
+}
+
+void Player_SID::releaseResourceBySound(int resID) { // $5088
+	var481A = 1;
+	releaseResource(resID);
+}
+
+void Player_SID::readVec6Data(int x, int *offset, uint8 *songFilePtr, int chanResID) { // $4E99
+	//vec5[x] = songFilePtr;
+	vec6[x] = songFilePtr[*offset];
+	*offset += 2;
+	_soundQueue[x] = chanResID;
+}
+
+int Player_SID::initSound(int soundResID) { // $4D0A
+	initializing = true;
+
+	if (isMusicPlaying && (statusBits1A & 0x07) == 0x07) {
+		initializing = false;
+		return -2;
+	}
+
+	uint8 *songFilePtr = getResource(soundResID);
+	if (songFilePtr == NULL) {
+		initializing = false;
+		return 1;
+	}
+
+	uint8 soundPrio = songFilePtr[4];
+	// for (mostly but not always looped) background sounds
+	if (soundPrio == 1) {
+		bgSoundResID = soundResID;
+		bgSoundActive = true;
+	}
+
+	uint8 requestedChannels = 0;
+	if ((songFilePtr[5] & 0x40) == 0) {
+		++requestedChannels;
+		if (songFilePtr[5] & 0x02)
+			++requestedChannels;
+		if (songFilePtr[5] & 0x08)
+			++requestedChannels;
+	}
+
+	bool filterNeeded = (songFilePtr[5] & 0x20) != 0;
+	bool filterBlocked = (filterUsed && filterNeeded);
+	if (filterBlocked || (freeChannelCount < requestedChannels)) {
+		findLessPrioChannels(soundPrio);
+
+		if ((freeChannelCount + chansWithLowerPrioCount < requestedChannels) ||
+		    (filterBlocked && !actFilterHasLowerPrio)) {
+			initializing = false;
+			return -1;
+		}
+
+		if (filterBlocked) {
+			if (soundPrio < chanPrio[3]) {
+				initializing = false;
+				return -1;
+			}
+
+			uint8 l_resID = channelMap[3];
+			releaseResourceBySound(l_resID);
+		}
+
+		while ((freeChannelCount < requestedChannels) || (filterNeeded && filterUsed)) {
+			findLessPrioChannels(soundPrio);
+			if (minChanPrio >= soundPrio) {
+				initializing = false;
+				return -1;
+			}
+
+			uint8 l_resID = channelMap[minChanPrioIndex];
+			releaseResourceBySound(l_resID);
+		}
+	}
+
+	int x;
+	uint8 soundByte5 = songFilePtr[5];
+	if (soundByte5 & 0x40)
+		x = reserveSoundFilter(soundPrio, soundResID);
+	else
+		x = reserveSoundVoice(soundPrio, soundResID);
+
+	uint8 var4CF3 = x;
+	int y = 6;
+	if (soundByte5 & 0x01) {
+		x += 4;
+		readVec6Data(x, &y, songFilePtr, soundResID);
+	}
+	if (soundByte5 & 0x02) {
+		x = reserveSoundVoice(soundPrio, soundResID);
+		readVec6Data(x, &y, songFilePtr, soundResID);
+	}
+	if (soundByte5 & 0x04) {
+		x += 4;
+		readVec6Data(x, &y, songFilePtr, soundResID);
+	}
+	if (soundByte5 & 0x08) {
+		x = reserveSoundVoice(soundPrio, soundResID);
+		readVec6Data(x, &y, songFilePtr, soundResID);
+	}
+	if (soundByte5 & 0x10) {
+		x += 4;
+		readVec6Data(x, &y, songFilePtr, soundResID);
+	}
+	if (soundByte5 & 0x20) {
+		x = reserveSoundFilter(soundPrio, soundResID);
+		readVec6Data(x, &y, songFilePtr, soundResID);
+	}
+
+	//vec5[var4CF3] = songFilePtr;
+	vec6[var4CF3] = y;
+	_soundQueue[var4CF3] = soundResID;
+
+	initializing = false;
+	_soundInQueue = true;
+
+	return soundResID;
+}
+
+void Player_SID::unused1() { // $50AF
+	var481A = -1;
+	if (bgSoundResID != 0) {
+		releaseResourceUnk(bgSoundResID);
+	}
+}
+
+///////////////////////////
+///////////////////////////
+
+#define ZEROMEM(a) memset(a, 0, sizeof(a))
+
+Player_SID::Player_SID(ScummEngine *scumm, Audio::Mixer *mixer) {
+	/*
+	 * clear memory
+	 */
+
+	resID_song = 0;
+	statusBits1A = 0;
+	statusBits1B = 0;
+	busyChannelBits = 0;
+	SIDReg23 = 0;
+	SIDReg23Stuff = 0;
+	SIDReg24 = 0;
+	bgSoundResID = 0;
+	freeChannelCount = 0;
+	usedChannelBits = 0;
+	var481A = 0;
+	songChannelBits = 0;
+	//var5163 = 0;
+	SIDReg24_HiNibble = 0;
+	chansWithLowerPrioCount = 0;
+	minChanPrio = 0;
+	minChanPrioIndex = 0;
+
+	_music = NULL;
+	songFileOrChanBufData = NULL;
+	actSongFileData = NULL;
+
+	initializing = false;
+	_soundInQueue = false;
+	isVoiceChannel = false;
+	isMusicPlaying = false;
+	swapVarLoaded = false;
+	bgSoundActive = false;
+	filterUsed = false;
+	pulseWidthSwapped = false;
+	swapPrepared = false;
+	filterSwapped = false;
+	keepSwapVars = false;
+	actFilterHasLowerPrio = false;
+
+	ZEROMEM(chanFileData);
+	ZEROMEM(chanDataOffset);
+	ZEROMEM(songPosPtr);
+	ZEROMEM(freqReg);
+	ZEROMEM(vec6);
+	ZEROMEM(songFileOrChanBufOffset);
+	ZEROMEM(freqDelta);
+	ZEROMEM(freqDeltaCounter);
+	ZEROMEM(swapSongPosPtr);
+	ZEROMEM(swapVec5);
+	ZEROMEM(swapVec8);
+	ZEROMEM(swapVec10);
+	ZEROMEM(swapFreqReg);
+	ZEROMEM(swapVec11);
+	ZEROMEM(vec20);
+	ZEROMEM(swapVec20);
+	ZEROMEM(resStatus);
+	ZEROMEM(attackReg);
+	ZEROMEM(sustainReg);
+	ZEROMEM(phaseBit);
+	ZEROMEM(releasePhase);
+	ZEROMEM(_soundQueue);
+	ZEROMEM(channelMap);
+	ZEROMEM(songPosUpdateCounter);
+	ZEROMEM(chanPrio);
+	ZEROMEM(waveCtrlReg);
+	ZEROMEM(swapAttack);
+	ZEROMEM(swapSustain);
+	ZEROMEM(swapSongPrio);
+	ZEROMEM(swapVec479C);
+	ZEROMEM(swapVec19);
+	ZEROMEM(swapSongPosUpdateCounter);
+	ZEROMEM(swapWaveCtrlReg);
+	ZEROMEM(stepTbl);
+
+	/*
+	 * initialize data
+	 */
+
+	const uint8 chanBuffer_const[3][45] = {
+		{
+			0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00,
+			0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00,
+			0x00,0x00,0xf0,0x40,0x10,0x04,0x00,0x00,
+			0x00,0x04,0x27,0x03,0xff,0xff,0x01,0x00,
+			0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+			0x00,0x00,0x00,0x00,0x00
+		},
+		{
+			0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00,
+			0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00,
+			0x00,0x00,0xf0,0x20,0x10,0x04,0x00,0x00,
+			0x00,0x04,0x27,0x03,0xff,0xff,0x02,0x00,
+			0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+			0x00,0x00,0x00,0x00,0x00
+		},
+		{
+			0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00,
+			0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00,
+			0x00,0x00,0xf0,0x20,0x10,0x04,0x00,0x00,
+			0x00,0x04,0x27,0x03,0xff,0xff,0x02,0x00,
+			0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+			0x00,0x00,0x00,0x00,0x00
+		}
+	};
+	memcpy(chanBuffer, chanBuffer_const, sizeof(chanBuffer_const));
+
+	for (int i = 0; i < 7; ++i) {
+		_soundQueue[i] = -1;
+	};
+
+	_music_timer = 0;
+
+	_mixer = mixer;
+	_sampleRate = _mixer->getOutputRate();
+	_vm = scumm;
+
+	// sound speed is slightly different on NTSC and PAL machines
+	// as the SID clock depends on the frame rate.
+	// ScummVM does not distinguish between NTSC and PAL targets
+	// so we use the NTSC timing here as the music was composed for
+	// NTSC systems (music on PAL systems is slower).
+	_videoSystem = NTSC;
+	_cpuCyclesLeft = 0;
+
+	initSID();
+	resetSID();
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_SID::~Player_SID() {
+	_mixer->stopHandle(_soundHandle);
+	delete _sid;
+}
+
+uint8 *Player_SID::getResource(int resID) {
+	switch (resID) {
+	case 0:
+		return NULL;
+	case 3:
+	case 4:
+	case 5:
+		return 	chanBuffer[resID-3];
+	default:
+		return _vm->getResourceAddress(rtSound, resID);
+	}
+}
+
+int Player_SID::readBuffer(int16 *buffer, const int numSamples) {
+	int samplesLeft = numSamples;
+
+	Common::StackLock lock(_mutex);
+
+	while (samplesLeft > 0) {
+		// update SID status after each frame
+		if (_cpuCyclesLeft <= 0) {
+			update();
+			_cpuCyclesLeft = timingProps[_videoSystem].cyclesPerFrame;
+		}
+		// fetch samples
+		int sampleCount = _sid->updateClock(_cpuCyclesLeft, (short *)buffer, samplesLeft);
+		samplesLeft -= sampleCount;
+		buffer += sampleCount;
+	}
+
+	return numSamples;
+}
+
+void Player_SID::SID_Write(int reg, uint8 data) {
+	_sid->write(reg, data);
+}
+
+void Player_SID::initSID() {
+	_sid = new Resid::SID();
+	_sid->set_sampling_parameters(
+		timingProps[_videoSystem].clockFreq,
+		_sampleRate);
+	_sid->enable_filter(true);
+
+	_sid->reset();
+	// Synchronize the waveform generators (must occur after reset)
+	_sid->write( 4, 0x08);
+	_sid->write(11, 0x08);
+	_sid->write(18, 0x08);
+	_sid->write( 4, 0x00);
+	_sid->write(11, 0x00);
+	_sid->write(18, 0x00);
+}
+
+void Player_SID::startSound(int nr) {
+	byte *data = _vm->getResourceAddress(rtSound, nr);
+	assert(data);
+
+	// WORKAROUND:
+	// sound[4] contains either a song prio or a music channel usage byte.
+	// As music channel usage is always 0x07 for all music files and
+	// prio 7 is never used in any sound file use this byte for auto-detection.
+	bool isMusic = (data[4] == 0x07);
+
+	Common::StackLock lock(_mutex);
+
+	if (isMusic) {
+		initMusic(nr);
+	} else {
+		stopSound_intern(nr);
+		initSound(nr);
+	}
+}
+
+void Player_SID::stopSound(int nr) {
+	if (nr == -1)
+		return;
+
+	Common::StackLock lock(_mutex);
+	stopSound_intern(nr);
+}
+
+void Player_SID::stopAllSounds() {
+	Common::StackLock lock(_mutex);
+	resetPlayerState();
+}
+
+int Player_SID::getSoundStatus(int nr) const {
+	int result = 0;
+
+	//Common::StackLock lock(_mutex);
+
+	if (resID_song == nr && isMusicPlaying) {
+		result = 1;
+	}
+
+	for (int i = 0; (i < 4) && (result == 0); ++i) {
+		if (nr == _soundQueue[i] || nr == channelMap[i]) {
+			result = 1;
+		}
+	}
+
+	return result;
+}
+
+int Player_SID::getMusicTimer() {
+	int result = _music_timer;
+	_music_timer = 0;
+	return result;
+}
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/sid.h b/engines/scumm/player/sid.h
new file mode 100644
index 0000000..12e3573
--- /dev/null
+++ b/engines/scumm/player/sid.h
@@ -0,0 +1,276 @@
+/* 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_SID_H
+#define SCUMM_PLAYER_SID_H
+
+#include "common/mutex.h"
+#include "common/scummsys.h"
+#include "scumm/music.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/softsynth/sid.h"
+
+namespace Scumm {
+
+// the "channel" parameters seem to be in fact SID register
+// offsets. Should be replaced.
+enum sid_reg_t {
+	FREQ_VOICE1,
+	FREQ_VOICE2,
+	FREQ_VOICE3,
+	FREQ_FILTER,
+	PULSE_VOICE1,
+	PULSE_VOICE2,
+	PULSE_VOICE3
+};
+
+enum VideoStandard {
+	PAL,
+	NTSC
+};
+
+class ScummEngine;
+
+class Player_SID : public Audio::AudioStream, public MusicEngine {
+public:
+	Player_SID(ScummEngine *scumm, Audio::Mixer *mixer);
+	virtual ~Player_SID();
+
+	virtual void setMusicVolume(int vol) { _maxvol = vol; }
+	virtual void startSound(int sound);
+	virtual void stopSound(int sound);
+	virtual void stopAllSounds();
+	virtual int  getSoundStatus(int sound) const;
+	virtual int  getMusicTimer();
+
+	// AudioStream API
+	int readBuffer(int16 *buffer, const int numSamples);
+	bool isStereo() const { return false; }
+	bool endOfData() const { return false; }
+	int getRate() const { return _sampleRate; }
+
+private:
+	Resid::SID *_sid;
+	void SID_Write(int reg, uint8 data);
+	void initSID();
+	uint8 *getResource(int resID);
+
+	// number of cpu cycles until next frame update
+	Resid::cycle_count _cpuCyclesLeft;
+
+	ScummEngine *_vm;
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundHandle;
+	int _sampleRate;
+	int _maxvol;
+	Common::Mutex _mutex;
+
+	VideoStandard _videoSystem;
+
+	int _music_timer;
+	uint8* _music;
+
+private:
+	void initMusic(int songResIndex); // $7de6
+	int initSound(int soundResID); // $4D0A
+	void stopSound_intern(int soundResID); // $5093
+	void stopMusic_intern(); // $4CAA
+
+	void resetSID(); // $48D8
+	void update(); // $481B
+	void handleMusicBuffer();
+	int setupSongFileData(); // $36cb
+	void func_3674(int channel); // $3674
+	void resetPlayerState(); // $48f7
+	void processSongData(int channel); // $4939
+	void readSetSIDFilterAndProps(int *offset, uint8* dataPtr);  // $49e7
+	void saveSongPos(int y, int channel);
+	void updateFreq(int channel);
+	void resetFreqDelta(int channel);
+	void readSongChunk(int channel); // $4a6b
+	void setSIDFreqAS(int channel); // $4be6
+	void setSIDWaveCtrlReg(int channel); // $4C0D
+	int setupSongPtr(int channel); // $4C1C
+	void unlockResource(int chanResIndex); // $4CDA
+	void countFreeChannels(); // $4f26
+	void func_4F45(int channel); // $4F45
+	void safeUnlockResource(int resIndex); // $4FEA
+	void releaseResource(int resIndex); // $5031
+	void releaseResChannels(int resIndex); // $5070
+	void releaseResourceUnk(int resIndex); // $50A4
+	void releaseChannel(int channel);
+	void clearSIDWaveform(int channel);
+	void stopChannel(int channel);
+	void swapVars(int channel, int swapIndex); // $51a5
+	void resetSwapVars(); // $52d0
+	void prepareSwapVars(int channel); // $52E5
+	void useSwapVars(int channel); // $5342
+	void lockResource(int resIndex); // $4ff4
+	void reserveChannel(int channel, uint8 prioValue, int chanResIndex); // $4ffe
+	void unlockCodeLocation(); // $513e
+	void lockCodeLocation(); // $514f
+	void func_7eae(int channel, uint8* songFileDataPtr); // $7eae
+	void func_819b(int channel); // $819b
+	void buildStepTbl(int step); // $82B4
+	int reserveSoundFilter(uint8 value, uint8 chanResIndex); // $4ED0
+	int reserveSoundVoice(uint8 value, uint8 chanResIndex); // $4EB8
+	void findLessPrioChannels(uint8 soundPrio); // $4ED8
+	void releaseResourceBySound(int resID); // $5088
+	void readVec6Data(int x, int *offset, uint8 *songFilePtr, int chanResID); // $4E99
+
+	void unused1(); // $50AF
+
+	uint8 chanBuffer[3][45];
+
+	int resID_song;
+
+	// statusBits1A/1B are always equal
+	uint8 statusBits1A;
+	uint8 statusBits1B;
+
+	uint8 busyChannelBits;
+
+	uint8 SIDReg23;
+	uint8 SIDReg23Stuff;
+	uint8 SIDReg24;
+
+	uint8* chanFileData[3];
+	uint16 chanDataOffset[3];
+	uint8* songPosPtr[7];
+
+	// 0..2: freq value voice1/2/3
+	// 3:    filter freq
+	// 4..6: pulse width
+	uint16 freqReg[7];
+
+	// start offset[i] for songFileOrChanBufData to obtain songPosPtr[i]
+	//	vec6[0..2] = 0x0008;
+	//	vec6[4..6] = 0x0019;
+	uint16 vec6[7];
+
+	// current offset[i] for songFileOrChanBufData to obtain songPosPtr[i] (starts with vec6[i], increased later)
+	uint16 songFileOrChanBufOffset[7];
+
+	uint16 freqDelta[7];
+	int freqDeltaCounter[7];
+	uint8* swapSongPosPtr[3];
+	uint8* swapVec5[3];
+	uint16 swapVec8[3];
+	uint16 swapVec10[3];
+	uint16 swapFreqReg[3];
+	int swapVec11[3];
+
+	// never read
+	//uint8* vec5[7];
+	// never read
+	//uint8 vec19[7];
+	// never read (needed by scumm engine?)
+	//bool curChannelActive;
+
+	uint8* vec20[7];
+
+	uint8* swapVec20[3];
+
+	// resource status (never read)
+	// bit7: some flag
+	// bit6..0: counter (use-count?), maybe just bit0 as flag (used/unused?)
+	uint8 resStatus[70];
+
+	uint8* songFileOrChanBufData;
+	uint8* actSongFileData;
+
+	uint16 stepTbl[33];
+
+	bool initializing;
+	bool _soundInQueue;
+	bool isVoiceChannel;
+
+	bool isMusicPlaying;
+	bool swapVarLoaded;
+	bool bgSoundActive;
+	bool filterUsed;
+
+	uint8 bgSoundResID;
+	uint8 freeChannelCount;
+
+	// seems to be used for managing the three voices
+	// bit[0..2]: 0 -> unused, 1 -> already in use
+	uint8 usedChannelBits;
+	uint8 attackReg[3];
+	uint8 sustainReg[3];
+
+	// -1/0/1
+	int var481A;
+
+	// bit-array: 00000cba
+	// a/b/c: channel1/2/3
+	uint8 songChannelBits;
+
+	bool pulseWidthSwapped;
+	bool swapPrepared;
+
+	// never read
+	//uint8 var5163;
+
+	bool filterSwapped;
+	uint8 SIDReg24_HiNibble;
+	bool keepSwapVars;
+
+	uint8 phaseBit[3];
+	bool releasePhase[3];
+
+	// values: a resID or -1
+	// resIDs: 3, 4, 5 or song-number
+	int _soundQueue[7];
+
+	// values: a resID or 0
+	// resIDs: 3, 4, 5 or song-number
+	int channelMap[7];
+
+	uint8 songPosUpdateCounter[7];
+
+	// priortity of channel contents
+	// MM:  1: lowest .. 120: highest (1,2,A,64,6E,73,78)
+	// Zak: -???: lowest .. 120: highest (5,32,64,65,66,6E,78, A5,A6,AF,D7)
+	uint8 chanPrio[7];
+
+	// only [0..2] used?
+	uint8 waveCtrlReg[7];
+
+	uint8 swapAttack[2];
+	uint8 swapSustain[2];
+	uint8 swapSongPrio[3];
+	int swapVec479C[3];
+	uint8 swapVec19[3];
+	uint8 swapSongPosUpdateCounter[3];
+	uint8 swapWaveCtrlReg[3];
+
+	bool actFilterHasLowerPrio;
+	uint8 chansWithLowerPrioCount;
+	uint8 minChanPrio;
+	uint8 minChanPrioIndex;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/towns.cpp b/engines/scumm/player/towns.cpp
new file mode 100644
index 0000000..4adb11b
--- /dev/null
+++ b/engines/scumm/player/towns.cpp
@@ -0,0 +1,753 @@
+/* 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 "scumm/sound.h"
+#include "scumm/player/towns.h"
+
+namespace Scumm {
+
+Player_Towns::Player_Towns(ScummEngine *vm, bool isVersion2) : _vm(vm), _v2(isVersion2), _intf(0), _numSoundMax(isVersion2 ? 256 : 200), _unkFlags(0x33) {
+	memset(_pcmCurrentSound, 0, sizeof(_pcmCurrentSound));
+}
+
+void Player_Towns::setSfxVolume(int vol) {
+	if (!_intf)
+		return;
+	_intf->setSoundEffectVolume(vol);
+}
+
+int Player_Towns::getSoundStatus(int sound) const {
+	if (!_intf)
+		return 0;
+	for (int i = 1; i < 9; i++) {
+		if (_pcmCurrentSound[i].index == sound)
+			return _intf->callback(40, 0x3f + i) ? 1 : 0;
+	}
+	return 0;
+}
+
+void Player_Towns::saveLoadWithSerializer(Serializer *ser) {
+	static const SaveLoadEntry pcmEntries[] = {
+		MKLINE(PcmCurrentSound, index, sleInt16, VER(81)),
+		MKLINE(PcmCurrentSound, chan, sleInt16, VER(81)),
+		MKLINE(PcmCurrentSound, note, sleUint8, VER(81)),
+		MKLINE(PcmCurrentSound, velo, sleUint8, VER(81)),
+		MKLINE(PcmCurrentSound, pan, sleUint8, VER(81)),
+		MKLINE(PcmCurrentSound, paused, sleUint8, VER(81)),
+		MKLINE(PcmCurrentSound, looping, sleUint8, VER(81)),
+		MKLINE(PcmCurrentSound, priority, sleUint32, VER(81)),
+		MKEND()
+	};
+
+	for (int i = 1; i < 9; i++) {
+		if (!_pcmCurrentSound[i].index)
+			continue;
+
+		if (_intf->callback(40, i + 0x3f))
+			continue;
+
+		_intf->callback(39, i + 0x3f);
+
+		_pcmCurrentSound[i].index = 0;
+	}
+
+	ser->saveLoadArrayOf(_pcmCurrentSound, 9, sizeof(PcmCurrentSound), pcmEntries);
+}
+
+void Player_Towns::restoreAfterLoad() {
+	Common::Array<uint16> restoredSounds;
+
+	for (int i = 1; i < 9; i++) {
+		if (!_pcmCurrentSound[i].index || _pcmCurrentSound[i].index == 0xffff)
+			continue;
+
+		// Don't restart multichannel sounds more than once
+		if (Common::find(restoredSounds.begin(), restoredSounds.end(), _pcmCurrentSound[i].index) != restoredSounds.end())
+			continue;
+
+		if (!_v2)
+			restoredSounds.push_back(_pcmCurrentSound[i].index);
+
+		uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index);
+		if (!ptr)
+			continue;
+
+		if (_vm->_game.version != 3)
+			ptr += 2;
+
+		if (ptr[13])
+			continue;
+
+		playPcmTrack(_pcmCurrentSound[i].index, ptr + 6, _pcmCurrentSound[i].velo, _pcmCurrentSound[i].pan, _pcmCurrentSound[i].note, _pcmCurrentSound[i].priority);
+	}
+}
+
+void Player_Towns::playPcmTrack(int sound, const uint8 *data, int velo, int pan, int note, int priority) {
+	if (!_intf)
+		return;
+
+	const uint8 *sfxData = data + 16;
+
+	int numChan = _v2 ? 1 : data[14];
+	for (int i = 0; i < numChan; i++) {
+		int chan = allocatePcmChannel(sound, i, priority);
+		if (!chan)
+			return;
+
+		_intf->callback(70, _unkFlags);
+		_intf->callback(3, chan + 0x3f, pan);
+		_intf->callback(37, chan + 0x3f, note, velo, sfxData);
+
+		_pcmCurrentSound[chan].note = note;
+		_pcmCurrentSound[chan].velo = velo;
+		_pcmCurrentSound[chan].pan = pan;
+		_pcmCurrentSound[chan].paused = 0;
+		_pcmCurrentSound[chan].looping = READ_LE_UINT32(&sfxData[20]) ? 1 : 0;
+
+		sfxData += (READ_LE_UINT32(&sfxData[12]) + 32);
+	}
+}
+
+void Player_Towns::stopPcmTrack(int sound) {
+	if (!_intf)
+		return;
+
+	for (int i = 1; i < 9; i++) {
+		if (sound == _pcmCurrentSound[i].index || !sound) {
+			_intf->callback(39, i + 0x3f);
+			_pcmCurrentSound[i].index = 0;
+		}
+	}
+}
+
+int Player_Towns::allocatePcmChannel(int sound, int sfxChanRelIndex, uint32 priority) {
+	if (!_intf)
+		return 0;
+
+	int chan = 0;
+
+	if (_v2 && priority > 255) {
+		chan = 8;
+		if (_intf->callback(40, 0x47))
+			_intf->callback(39, 0x47);
+	} else {
+		for (int i = 8; i; i--) {
+			if (!_pcmCurrentSound[i].index) {
+				chan = i;
+				continue;
+			}
+
+			if (_intf->callback(40, i + 0x3f))
+				continue;
+
+			chan = i;
+			if (_pcmCurrentSound[chan].index == 0xffff)
+				_intf->callback(39, chan + 0x3f);
+			else
+				_vm->_sound->stopSound(_pcmCurrentSound[chan].index);
+		}
+
+		if (!chan) {
+			for (int i = 1; i < 9; i++) {
+				if (priority >= _pcmCurrentSound[i].priority)
+					chan = i;
+			}
+			if (_pcmCurrentSound[chan].index == 0xffff)
+				_intf->callback(39, chan + 0x3f);
+			else
+				_vm->_sound->stopSound(_pcmCurrentSound[chan].index);
+		}
+	}
+
+	if (chan) {
+		_pcmCurrentSound[chan].index = sound;
+		_pcmCurrentSound[chan].chan = sfxChanRelIndex;
+		_pcmCurrentSound[chan].priority = priority;
+	}
+
+	return chan;
+}
+
+Player_Towns_v1::Player_Towns_v1(ScummEngine *vm, Audio::Mixer *mixer) : Player_Towns(vm, false) {
+	_soundOverride = 0;
+	_cdaCurrentSound = _eupCurrentSound = _cdaNumLoops = 0;
+	_cdaForceRestart = 0;
+	_cdaVolLeft = _cdaVolRight = 0;
+
+	_eupVolLeft = _eupVolRight = 0;
+	_eupLooping = false;
+
+	if (_vm->_game.version == 3) {
+		_soundOverride = new SoundOvrParameters[_numSoundMax];
+		memset(_soundOverride, 0, _numSoundMax * sizeof(SoundOvrParameters));
+	}
+
+	_driver = new TownsEuphonyDriver(mixer);
+}
+
+Player_Towns_v1::~Player_Towns_v1() {
+	delete _driver;
+	delete[] _soundOverride;
+}
+
+bool Player_Towns_v1::init() {
+	if (!_driver)
+		return false;
+
+	if (!_driver->init())
+		return false;
+
+	_driver->reserveSoundEffectChannels(8);
+	_intf = _driver->intf();
+
+	// Treat all 6 fm channels and all 8 pcm channels as sound effect channels
+	// since music seems to exist as CD audio only in the games which use this
+	// MusicEngine implementation.
+	_intf->setSoundEffectChanMask(-1);
+
+	setVolumeCD(255, 255);
+
+	return true;
+}
+
+void Player_Towns_v1::setMusicVolume(int vol) {
+	_driver->setMusicVolume(vol);
+}
+
+void Player_Towns_v1::startSound(int sound) {
+	uint8 *ptr = _vm->getResourceAddress(rtSound, sound);
+	if (_vm->_game.version != 3)
+		ptr += 2;
+
+	int type = ptr[13];
+
+	if (type == 0) {
+		uint8 velocity = 0;
+		uint8 note = 0;
+
+		if (_vm->_game.version == 3) {
+			velocity = (_soundOverride[sound].vLeft + _soundOverride[sound].vRight);
+			note = _soundOverride[sound].note;
+		}
+
+		velocity = velocity ? velocity >> 2 : ptr[14] >> 1;
+		uint16 len = READ_LE_UINT16(ptr) + 2;
+		playPcmTrack(sound, ptr + 6, velocity, 64, note ? note : (len > 50 ? ptr[50] : 60), READ_LE_UINT16(ptr + 10));
+
+	} else if (type == 1) {
+		playEuphonyTrack(sound, ptr + 6);
+
+	} else if (type == 2) {
+		playCdaTrack(sound, ptr + 6);
+	}
+
+	if (_vm->_game.version == 3)
+		_soundOverride[sound].vLeft = _soundOverride[sound].vRight = _soundOverride[sound].note = 0;
+}
+
+void Player_Towns_v1::stopSound(int sound) {
+	if (sound == 0 || sound == _cdaCurrentSound) {
+		_cdaCurrentSound = 0;
+		_vm->_sound->stopCD();
+		_vm->_sound->stopCDTimer();
+	}
+
+	if (sound != 0 && sound == _eupCurrentSound) {
+		_eupCurrentSound = 0;
+		_eupLooping = false;
+		_driver->stopParser();
+	}
+
+	stopPcmTrack(sound);
+}
+
+void Player_Towns_v1::stopAllSounds() {
+	_cdaCurrentSound = 0;
+	_vm->_sound->stopCD();
+	_vm->_sound->stopCDTimer();
+
+	_eupCurrentSound = 0;
+	_eupLooping = false;
+	_driver->stopParser();
+
+	stopPcmTrack(0);
+}
+
+int Player_Towns_v1::getSoundStatus(int sound) const {
+	if (sound == _cdaCurrentSound)
+		return _vm->_sound->pollCD();
+	if (sound == _eupCurrentSound)
+		return _driver->parserIsPlaying() ? 1 : 0;
+	return Player_Towns::getSoundStatus(sound);
+}
+
+int32 Player_Towns_v1::doCommand(int numargs, int args[]) {
+	int32 res = 0;
+
+	switch (args[0]) {
+	case 2:
+		_driver->intf()->callback(73, 0);
+		break;
+
+	case 3:
+		restartLoopingSounds();
+		break;
+
+	case 8:
+		startSound(args[1]);
+		break;
+
+	case 9:
+		_vm->_sound->stopSound(args[1]);
+		break;
+
+	case 11:
+		stopPcmTrack(0);
+		break;
+
+	case 14:
+		startSoundEx(args[1], args[2], args[3], args[4]);
+		break;
+
+	case 15:
+		stopSoundSuspendLooping(args[1]);
+		break;
+
+	default:
+		warning("Player_Towns_v1::doCommand: Unknown command %d", args[0]);
+		break;
+	}
+
+	return res;
+}
+
+void Player_Towns_v1::setVolumeCD(int left, int right) {
+	_cdaVolLeft = left & 0xff;
+	_cdaVolRight = right & 0xff;
+	_driver->setOutputVolume(1, left >> 1, right >> 1);
+}
+
+void Player_Towns_v1::setSoundVolume(int sound, int left, int right) {
+	if (_soundOverride && sound > 0 && sound < _numSoundMax) {
+		_soundOverride[sound].vLeft = left;
+		_soundOverride[sound].vRight = right;
+	}
+}
+
+void Player_Towns_v1::setSoundNote(int sound, int note) {
+	if (_soundOverride && sound > 0 && sound < _numSoundMax)
+		_soundOverride[sound].note = note;
+}
+
+void Player_Towns_v1::saveLoadWithSerializer(Serializer *ser) {
+	_cdaCurrentSoundTemp = (_vm->_sound->pollCD() && _cdaNumLoops > 1) ? _cdaCurrentSound & 0xff : 0;
+	_cdaNumLoopsTemp = _cdaNumLoops & 0xff;
+
+	static const SaveLoadEntry cdEntries[] = {
+		MKLINE(Player_Towns_v1, _cdaCurrentSoundTemp, sleUint8, VER(81)),
+		MKLINE(Player_Towns_v1, _cdaNumLoopsTemp, sleUint8, VER(81)),
+		MKLINE(Player_Towns_v1, _cdaVolLeft, sleUint8, VER(81)),
+		MKLINE(Player_Towns_v1, _cdaVolRight, sleUint8, VER(81)),
+		MKEND()
+	};
+
+	ser->saveLoadEntries(this, cdEntries);
+
+	if (!_eupLooping && !_driver->parserIsPlaying())
+		_eupCurrentSound = 0;
+
+	static const SaveLoadEntry eupEntries[] = {
+		MKLINE(Player_Towns_v1, _eupCurrentSound, sleUint8, VER(81)),
+		MKLINE(Player_Towns_v1, _eupLooping, sleUint8, VER(81)),
+		MKLINE(Player_Towns_v1, _eupVolLeft, sleUint8, VER(81)),
+		MKLINE(Player_Towns_v1, _eupVolRight, sleUint8, VER(81)),
+		MKEND()
+	};
+
+	ser->saveLoadEntries(this, eupEntries);
+
+	Player_Towns::saveLoadWithSerializer(ser);
+}
+
+void Player_Towns_v1::restoreAfterLoad() {
+	setVolumeCD(_cdaVolLeft, _cdaVolRight);
+
+	if (_cdaCurrentSoundTemp) {
+		uint8 *ptr = _vm->getResourceAddress(rtSound, _cdaCurrentSoundTemp) + 6;
+		if (_vm->_game.version != 3)
+			ptr += 2;
+
+		if (ptr[7] == 2) {
+			playCdaTrack(_cdaCurrentSoundTemp, ptr, true);
+			_cdaCurrentSound = _cdaCurrentSoundTemp;
+			_cdaNumLoops = _cdaNumLoopsTemp;
+		}
+	}
+
+	if (_eupCurrentSound) {
+		uint8 *ptr = _vm->getResourceAddress(rtSound, _eupCurrentSound) + 6;
+		if (_vm->_game.version != 3)
+			ptr += 2;
+
+		if (ptr[7] == 1) {
+			setSoundVolume(_eupCurrentSound, _eupVolLeft, _eupVolRight);
+			playEuphonyTrack(_eupCurrentSound, ptr);
+		}
+	}
+
+	Player_Towns::restoreAfterLoad();
+}
+
+void Player_Towns_v1::restartLoopingSounds() {
+	if (_cdaNumLoops && !_cdaForceRestart)
+		_cdaForceRestart = 1;
+
+	for (int i = 1; i < 9; i++) {
+		if (!_pcmCurrentSound[i].paused)
+			continue;
+
+		_pcmCurrentSound[i].paused = 0;
+
+		uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index);
+		if (!ptr)
+			continue;
+		ptr += 24;
+
+		int c = 1;
+		while (_pcmCurrentSound[i].chan != c) {
+			ptr = ptr + READ_LE_UINT32(&ptr[12]) + 32;
+			c++;
+		}
+
+		_driver->playSoundEffect(i + 0x3f, _pcmCurrentSound[i].note, _pcmCurrentSound[i].velo, ptr);
+	}
+
+	_driver->intf()->callback(73, 1);
+}
+
+void Player_Towns_v1::startSoundEx(int sound, int velo, int pan, int note) {
+	uint8 *ptr = _vm->getResourceAddress(rtSound, sound) + 2;
+
+	if (pan > 99)
+		pan = 99;
+
+	velo = velo ? (velo * ptr[14] + 50) / 100 : ptr[14];
+	velo = CLIP(velo, 1, 255);
+	uint16 pri = READ_LE_UINT16(ptr + 10);
+
+	if (ptr[13] == 0) {
+		velo >>= 1;
+
+		if (!velo)
+			velo = 1;
+
+		pan = pan ? (((pan << 7) - pan) + 50) / 100 : 64;
+
+		playPcmTrack(sound, ptr + 6, velo ? velo : ptr[14] >> 1, pan, note ? note : ptr[50], pri);
+
+	} else if (ptr[13] == 2) {
+		int volLeft = velo;
+		int volRight = velo;
+
+		if (pan < 50)
+			volRight = ((pan * 2 + 1) * velo + 50) / 100;
+		else if (pan > 50)
+			volLeft = (((99 - pan) * 2 + 1) * velo + 50) / 100;
+
+		setVolumeCD(volLeft, volRight);
+
+		if (!_cdaForceRestart && sound == _cdaCurrentSound)
+			return;
+
+		playCdaTrack(sound, ptr + 6, true);
+	}
+}
+
+void Player_Towns_v1::stopSoundSuspendLooping(int sound) {
+	if (!sound) {
+		return;
+	} else if (sound == _cdaCurrentSound) {
+		if (_cdaNumLoops && _cdaForceRestart)
+			_cdaForceRestart = 1;
+	} else {
+		for (int i = 1; i < 9; i++) {
+			if (sound == _pcmCurrentSound[i].index) {
+				if (!_driver->soundEffectIsPlaying(i + 0x3f))
+					continue;
+				_driver->stopSoundEffect(i + 0x3f);
+				if (_pcmCurrentSound[i].looping)
+					_pcmCurrentSound[i].paused = 1;
+				else
+					_pcmCurrentSound[i].index = 0;
+			}
+		}
+	}
+}
+
+void Player_Towns_v1::playEuphonyTrack(int sound, const uint8 *data) {
+	const uint8 *pos = data + 16;
+	const uint8 *src = pos + data[14] * 48;
+	const uint8 *trackData = src + 150;
+
+	for (int i = 0; i < 32; i++)
+		_driver->configChan_enable(i, *src++);
+	for (int i = 0; i < 32; i++)
+		_driver->configChan_setMode(i, 0xff);
+	for (int i = 0; i < 32; i++)
+		_driver->configChan_remap(i, *src++);
+	for (int i = 0; i < 32; i++)
+		_driver->configChan_adjustVolume(i, *src++);
+	for (int i = 0; i < 32; i++)
+		_driver->configChan_setTranspose(i, *src++);
+
+	src += 8;
+	for (int i = 0; i < 6; i++)
+		_driver->assignChannel(i, *src++);
+
+	for (int i = 0; i < data[14]; i++) {
+		_driver->loadInstrument(i, i, pos + i * 48);
+		_driver->intf()->callback(4, i, i);
+	}
+
+	_eupVolLeft = _soundOverride[sound].vLeft;
+	_eupVolRight = _soundOverride[sound].vRight;
+	int lvl = _soundOverride[sound].vLeft + _soundOverride[sound].vRight;
+	if (!lvl)
+		lvl = data[8] + data[9];
+	lvl >>= 2;
+
+	for (int i = 0; i < 6; i++)
+		_driver->chanVolume(i, lvl);
+
+	uint32 trackSize = READ_LE_UINT32(src);
+	src += 4;
+	uint8 startTick = *src++;
+
+	_driver->setMusicTempo(*src++);
+	_driver->startMusicTrack(trackData, trackSize, startTick);
+
+	_eupLooping = (*src != 1) ? 1 : 0;
+	_driver->setMusicLoop(_eupLooping != 0);
+	_driver->continueParsing();
+	_eupCurrentSound = sound;
+}
+
+void Player_Towns_v1::playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo) {
+	const uint8 *ptr = data;
+
+	if (!sound)
+		return;
+
+	if (!skipTrackVelo) {
+		if (_vm->_game.version == 3) {
+			if (_soundOverride[sound].vLeft + _soundOverride[sound].vRight)
+				setVolumeCD(_soundOverride[sound].vLeft, _soundOverride[sound].vRight);
+			else
+				setVolumeCD(ptr[8], ptr[9]);
+		} else {
+			setVolumeCD(ptr[8], ptr[9]);
+		}
+	}
+
+	if (sound == _cdaCurrentSound && _vm->_sound->pollCD() == 1)
+		return;
+
+	ptr += 16;
+
+	int track = ptr[0];
+	_cdaNumLoops = ptr[1];
+	int start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4];
+	int end = (ptr[5] * 60 + ptr[6]) * 75 + ptr[7];
+
+	_vm->_sound->playCDTrack(track, _cdaNumLoops == 0xff ? -1 : _cdaNumLoops, start, end <= start ? 0 : end - start);
+	_cdaForceRestart = 0;
+	_cdaCurrentSound = sound;
+}
+
+Player_Towns_v2::Player_Towns_v2(ScummEngine *vm, Audio::Mixer *mixer, IMuse *imuse, bool disposeIMuse) : Player_Towns(vm, true), _imuse(imuse), _imuseDispose(disposeIMuse), _sblData(0) {
+	_soundOverride = new SoundOvrParameters[_numSoundMax];
+	memset(_soundOverride, 0, _numSoundMax * sizeof(SoundOvrParameters));
+	_intf = new TownsAudioInterface(mixer, 0);
+}
+
+Player_Towns_v2::~Player_Towns_v2() {
+	delete _intf;
+	_intf = 0;
+
+	if (_imuseDispose)
+		delete _imuse;
+
+	delete[] _sblData;
+	delete[] _soundOverride;
+}
+
+bool Player_Towns_v2::init() {
+	if (!_intf)
+		return false;
+
+	if (!_intf->init())
+		return false;
+
+	_intf->callback(33, 8);
+	_intf->setSoundEffectChanMask(~0x3f);
+
+	return true;
+}
+
+void Player_Towns_v2::setMusicVolume(int vol) {
+	_imuse->setMusicVolume(vol);
+}
+
+int Player_Towns_v2::getSoundStatus(int sound) const {
+	if (_soundOverride[sound].type == 7)
+		return Player_Towns::getSoundStatus(sound);
+	return _imuse->getSoundStatus(sound);
+}
+
+void Player_Towns_v2::startSound(int sound) {
+	uint8 *ptr = _vm->getResourceAddress(rtSound, sound);
+
+	if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) {
+		_soundOverride[sound].type = 7;
+		uint8 velo = _soundOverride[sound].velo ? _soundOverride[sound].velo - 1: (ptr[10] + ptr[11] + 1) >> 1;
+		uint8 pan = _soundOverride[sound].pan ? _soundOverride[sound].pan - 1 : 64;
+		uint8 pri = ptr[9];
+		_soundOverride[sound].velo = _soundOverride[sound].pan = 0;
+		playPcmTrack(sound, ptr + 8, velo, pan, ptr[52], pri);
+
+	} else if (READ_BE_UINT32(ptr) == MKTAG('S','B','L',' ')) {
+		_soundOverride[sound].type = 5;
+		playVocTrack(ptr + 27);
+
+	} else {
+		_soundOverride[sound].type = 3;
+		_imuse->startSound(sound);
+	}
+}
+
+void Player_Towns_v2::stopSound(int sound) {
+	if (_soundOverride[sound].type == 7) {
+		stopPcmTrack(sound);
+	} else {
+		_imuse->stopSound(sound);
+	}
+}
+
+void Player_Towns_v2::stopAllSounds() {
+	stopPcmTrack(0);
+	_imuse->stopAllSounds();
+}
+
+int32 Player_Towns_v2::doCommand(int numargs, int args[]) {
+	int32 res = -1;
+	uint8 *ptr = 0;
+
+	switch (args[0]) {
+	case 8:
+		startSound(args[1]);
+		res = 0;
+		break;
+
+	case 9:
+	case 15:
+		stopSound(args[1]);
+		res = 0;
+		break;
+
+	case 11:
+		stopPcmTrack(0);
+		break;
+
+	case 13:
+		res = getSoundStatus(args[1]);
+		break;
+
+	case 258:
+		if (_soundOverride[args[1]].type == 0) {
+			ptr = _vm->getResourceAddress(rtSound, args[1]);
+			if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S'))
+				_soundOverride[args[1]].type = 7;
+		}
+		if (_soundOverride[args[1]].type == 7)	{
+			_soundOverride[args[1]].velo = args[2] + 1;
+			res = 0;
+		}
+		break;
+
+	case 259:
+		if (_soundOverride[args[1]].type == 0) {
+			ptr = _vm->getResourceAddress(rtSound, args[1]);
+			if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S'))
+				_soundOverride[args[1]].type = 7;
+		}
+		if (_soundOverride[args[1]].type == 7)	{
+			_soundOverride[args[1]].pan = 64 - CLIP<int>(args[2], -63, 63);
+			res = 0;
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	if (res == -1)
+		return _imuse->doCommand(numargs, args);
+
+	return res;
+}
+
+void Player_Towns_v2::saveLoadWithSerializer(Serializer *ser) {
+	if (ser->getVersion() >= 83)
+		Player_Towns::saveLoadWithSerializer(ser);
+}
+
+void Player_Towns_v2::playVocTrack(const uint8 *data) {
+	static const uint8 header[] = {
+		0x54, 0x61, 0x6C, 0x6B, 0x69, 0x65, 0x20, 0x20,
+		0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00,	0x00, 0x00, 0x00, 0x00,
+		0x36, 0x04, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00
+	};
+
+	uint32 len = (READ_LE_UINT32(data) >> 8) - 2;
+
+	int chan = allocatePcmChannel(0xffff, 0, 0x1000);
+	if (!chan)
+		return;
+
+	delete[] _sblData;
+	_sblData = new uint8[len + 32];
+
+	memcpy(_sblData, header, 32);
+	WRITE_LE_UINT32(_sblData + 12, len);
+
+	const uint8 *src = data + 6;
+	uint8 *dst = _sblData + 32;
+	for (uint32 i = 0; i < len; i++)
+		*dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++;
+
+	_intf->callback(37, 0x3f + chan, 60, 127, _sblData);
+	_pcmCurrentSound[chan].paused = 0;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player/towns.h b/engines/scumm/player/towns.h
new file mode 100644
index 0000000..5c76d7c
--- /dev/null
+++ b/engines/scumm/player/towns.h
@@ -0,0 +1,180 @@
+/* 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_TOWNS_H
+#define SCUMM_PLAYER_TOWNS_H
+
+#include "scumm/scumm.h"
+#include "scumm/imuse/imuse.h"
+#include "audio/softsynth/fmtowns_pc98/towns_euphony.h"
+#include "audio/softsynth/fmtowns_pc98/towns_midi.h"
+
+namespace Scumm {
+
+class Player_Towns : public MusicEngine {
+public:
+	Player_Towns(ScummEngine *vm, bool isVersion2);
+	virtual ~Player_Towns() {}
+
+	virtual bool init() = 0;
+
+	void setSfxVolume(int vol);
+
+	int getSoundStatus(int sound) const;
+
+	virtual int32 doCommand(int numargs, int args[]) = 0;
+
+	virtual void saveLoadWithSerializer(Serializer *ser);
+	virtual void restoreAfterLoad();
+
+	// version 1 specific
+	virtual int getCurrentCdaSound() { return 0; }
+	virtual int getCurrentCdaVolume() { return 0; }
+	virtual void setVolumeCD(int left, int right) {}
+	virtual void setSoundVolume(int sound, int left, int right) {}
+	virtual void setSoundNote(int sound, int note) {}
+
+protected:
+	void playPcmTrack(int sound, const uint8 *data, int velo = 0, int pan = 64, int note = 0, int priority = 0);
+	void stopPcmTrack(int sound);
+
+	int allocatePcmChannel(int sound, int sfxChanRelIndex, uint32 priority);
+
+	struct PcmCurrentSound {
+		uint16 index;
+		uint16 chan;
+		uint8 note;
+		uint8 velo;
+		uint8 pan;
+		uint8 paused;
+		uint8 looping;
+		uint32 priority;
+	} _pcmCurrentSound[9];
+
+	uint8 _unkFlags;
+
+	TownsAudioInterface *_intf;
+	ScummEngine *_vm;
+
+	const int _numSoundMax;
+	const bool _v2;
+};
+
+class Player_Towns_v1 : public Player_Towns {
+public:
+	Player_Towns_v1(ScummEngine *vm, Audio::Mixer *mixer);
+	~Player_Towns_v1();
+
+	bool init();
+
+	void setMusicVolume(int vol);
+	void startSound(int sound);
+	void stopSound(int sound);
+	void stopAllSounds();
+
+	int getSoundStatus(int sound) const;
+	int getCurrentCdaSound() { return _cdaCurrentSound; }
+	int getCurrentCdaVolume() { return (_cdaVolLeft + _cdaVolRight + 1) >> 1; }
+
+	int32 doCommand(int numargs, int args[]);
+
+	void setVolumeCD(int left, int right);
+	void setSoundVolume(int sound, int left, int right);
+	void setSoundNote(int sound, int note);
+
+	void saveLoadWithSerializer(Serializer *ser);
+	void restoreAfterLoad();
+
+	TownsEuphonyDriver *driver() { return _driver; }
+
+private:
+	void restartLoopingSounds();
+	void startSoundEx(int sound, int velo, int pan, int note);
+	void stopSoundSuspendLooping(int sound);
+
+	void playEuphonyTrack(int sound, const uint8 *data);
+	void playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo = false);
+
+	struct SoundOvrParameters {
+		uint8 vLeft;
+		uint8 vRight;
+		uint8 note;
+	};
+
+	SoundOvrParameters *_soundOverride;
+
+	uint8 _cdaVolLeft;
+	uint8 _cdaVolRight;
+
+	uint8 _eupCurrentSound;
+	uint8 _eupLooping;
+	uint8 _eupVolLeft;
+	uint8 _eupVolRight;
+
+	uint8 _cdaCurrentSound;
+	uint8 _cdaNumLoops;
+	uint8 _cdaForceRestart;
+
+	uint8 _cdaCurrentSoundTemp;
+	uint8 _cdaNumLoopsTemp;
+
+	TownsEuphonyDriver *_driver;
+};
+
+class Player_Towns_v2 : public Player_Towns {
+public:
+	Player_Towns_v2(ScummEngine *vm, Audio::Mixer *mixer, IMuse *imuse, bool disposeIMuse);
+	~Player_Towns_v2();
+
+	bool init();
+
+	void setMusicVolume(int vol);
+
+	int getSoundStatus(int sound) const;
+	void startSound(int sound);
+	void stopSound(int sound);
+	void stopAllSounds();
+
+	int32 doCommand(int numargs, int args[]);
+
+	void saveLoadWithSerializer(Serializer *ser);
+
+private:
+	void playVocTrack(const uint8 *data);
+
+	struct SoundOvrParameters {
+		uint8 velo;
+		uint8 pan;
+		uint8 type;
+	};
+
+	SoundOvrParameters *_soundOverride;
+
+	uint8 *_sblData;
+
+	IMuse *_imuse;
+	const bool _imuseDispose;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/v1.cpp b/engines/scumm/player/v1.cpp
new file mode 100644
index 0000000..25f42f1
--- /dev/null
+++ b/engines/scumm/player/v1.cpp
@@ -0,0 +1,607 @@
+/* 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 "engines/engine.h"
+#include "scumm/player/v1.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+#define FB_WNOISE 0x12000       /* feedback for white noise */
+#define FB_PNOISE 0x08000       /* feedback for periodic noise */
+
+#define TIMER_BASE_FREQ 1193000
+#define FIXP_SHIFT  16
+
+Player_V1::Player_V1(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr)
+	: Player_V2(scumm, mixer, pcjr) {
+	// Initialize channel code
+	for (int i = 0; i < 4; ++i)
+		clear_channel(i);
+
+	_mplex_step = (_sampleRate << FIXP_SHIFT) / 1193000;
+	_next_chunk = _repeat_chunk = 0;
+	_forced_level = 0;
+	_random_lsr = 0;
+}
+
+Player_V1::~Player_V1() {
+}
+
+void Player_V1::chainSound(int nr, byte *data) {
+	uint i;
+	for (i = 0; i < 4; ++i)
+		clear_channel(i);
+
+	_current_nr = nr;
+	_current_data = data;
+	_repeat_chunk = _next_chunk = data + (_pcjr ? 2 : 4);
+
+	debug(4, "Chaining new sound %d", nr);
+	if (_pcjr)
+		parsePCjrChunk();
+	else
+		parseSpeakerChunk();
+}
+
+void Player_V1::startSound(int nr) {
+	Common::StackLock lock(_mutex);
+
+	byte *data = _vm->getResourceAddress(rtSound, nr);
+	assert(data);
+
+	int offset = _pcjr ? READ_LE_UINT16(data+4) : 6;
+	int cprio = _current_data ? *(_current_data) & 0x7f : 0;
+	int prio  = *(data + offset) & 0x7f;
+	int restartable = *(data + offset) & 0x80;
+
+	debug(4, "startSound %d: prio %d%s, cprio %d",
+		  nr, prio, restartable ? " restartable" : "", cprio);
+
+	if (!_current_nr || cprio <= prio) {
+		if (_current_data && (*(_current_data) & 0x80)) {
+			_next_nr = _current_nr;
+			_next_data = _current_data;
+		}
+
+		chainSound(nr, data + offset);
+	}
+}
+
+void Player_V1::stopAllSounds() {
+	Common::StackLock lock(_mutex);
+
+	for (int i = 0; i < 4; i++)
+		clear_channel(i);
+	_repeat_chunk = _next_chunk = 0;
+	_next_nr = _current_nr = 0;
+	_next_data = _current_data = 0;
+}
+
+void Player_V1::stopSound(int nr) {
+	Common::StackLock lock(_mutex);
+
+	if (_next_nr == nr) {
+		_next_nr = 0;
+		_next_data = 0;
+	}
+	if (_current_nr == nr) {
+		for (int i = 0; i < 4; i++) {
+			clear_channel(i);
+		}
+		_repeat_chunk = _next_chunk = 0;
+		_current_nr = 0;
+		_current_data = 0;
+		chainNextSound();
+	}
+}
+
+void Player_V1::clear_channel(int i) {
+	_channels[i].freq   = 0;
+	_channels[i].volume = 15;
+}
+
+int Player_V1::getMusicTimer() {
+	/* Do V1 games have a music timer? */
+	return 0;
+}
+
+void Player_V1::parseSpeakerChunk() {
+	set_mplex(3000);
+	_forced_level = 0;
+
+ parse_again:
+	_chunk_type = READ_LE_UINT16(_next_chunk);
+	debug(6, "parseSpeakerChunk: sound %d, offset %lx, chunk %x",
+			_current_nr, (long)(_next_chunk - _current_data), _chunk_type);
+
+	_next_chunk += 2;
+	switch (_chunk_type) {
+	case 0xffff:
+		_current_nr = 0;
+		_current_data = 0;
+		_channels[0].freq = 0;
+		_next_chunk = 0;
+		chainNextSound();
+		break;
+	case 0xfffe:
+		_repeat_chunk = _next_chunk;
+		goto parse_again;
+
+	case 0xfffd:
+		_next_chunk = _repeat_chunk;
+		goto parse_again;
+
+	case 0xfffc:
+		/* handle reset. We don't need this do we? */
+		goto parse_again;
+
+	case 0:
+		_time_left = 1;
+		set_mplex(READ_LE_UINT16(_next_chunk));
+		_next_chunk += 2;
+		break;
+	case 1:
+		set_mplex(READ_LE_UINT16(_next_chunk));
+		_start = READ_LE_UINT16(_next_chunk + 2);
+		_end   = READ_LE_UINT16(_next_chunk + 4);
+		_delta = (int16) READ_LE_UINT16(_next_chunk + 6);
+		_repeat_ctr = READ_LE_UINT16(_next_chunk + 8);
+		_channels[0].freq = _start;
+		_next_chunk += 10;
+		debug(6, "chunk 1: mplex %d, freq %d -> %d step %d  x %d",
+				_mplex, _start, _end, _delta, _repeat_ctr);
+		break;
+	case 2:
+		_start = READ_LE_UINT16(_next_chunk);
+		_end   = READ_LE_UINT16(_next_chunk + 2);
+		_delta = (int16) READ_LE_UINT16(_next_chunk + 4);
+		_channels[0].freq = 0;
+		_next_chunk += 6;
+		_forced_level = -1;
+		debug(6, "chunk 2: %d -> %d step %d",
+				_start, _end, _delta);
+		break;
+	case 3:
+		_start = READ_LE_UINT16(_next_chunk);
+		_end   = READ_LE_UINT16(_next_chunk + 2);
+		_delta = (int16) READ_LE_UINT16(_next_chunk + 4);
+		_channels[0].freq = 0;
+		_next_chunk += 6;
+		_forced_level = -1;
+		debug(6, "chunk 3: %d -> %d step %d",
+				_start, _end, _delta);
+		break;
+	}
+}
+
+void Player_V1::nextSpeakerCmd() {
+	uint16 lsr;
+	switch (_chunk_type) {
+	case 0:
+		if (--_time_left)
+			return;
+		_time_left = READ_LE_UINT16(_next_chunk);
+		_next_chunk += 2;
+		if (_time_left == 0xfffb) {
+			/* handle 0xfffb?? */
+			_time_left = READ_LE_UINT16(_next_chunk);
+			_next_chunk += 2;
+		}
+		debug(7, "nextSpeakerCmd: chunk %d, offset %4lx: notelen %d",
+				_chunk_type, (long)(_next_chunk - 2 - _current_data), _time_left);
+		if (_time_left == 0) {
+			parseSpeakerChunk();
+		} else {
+			_channels[0].freq = READ_LE_UINT16(_next_chunk);
+			_next_chunk += 2;
+			debug(7, "freq_current: %d", _channels[0].freq);
+		}
+		break;
+
+	case 1:
+		_channels[0].freq = (_channels[0].freq + _delta) & 0xffff;
+		if (_channels[0].freq == _end) {
+			if (!--_repeat_ctr) {
+				parseSpeakerChunk();
+				return;
+			}
+			_channels[0].freq = _start;
+		}
+		break;
+
+	case 2:
+		_start = (_start + _delta) & 0xffff;
+		if (_start == _end) {
+			parseSpeakerChunk();
+			return;
+		}
+		set_mplex(_start);
+		_forced_level = -_forced_level;
+		break;
+	case 3:
+		_start = (_start + _delta) & 0xffff;
+		if (_start == _end) {
+			parseSpeakerChunk();
+			return;
+		}
+		lsr = _random_lsr + 0x9248;
+		lsr = (lsr >> 3) | (lsr << 13);
+		_random_lsr = lsr;
+		set_mplex((_start & lsr) | 0x180);
+		_forced_level = -_forced_level;
+		break;
+	}
+}
+
+void Player_V1::parsePCjrChunk() {
+	uint tmp;
+	uint i;
+
+	set_mplex(3000);
+	_forced_level = 0;
+
+parse_again:
+
+	_chunk_type = READ_LE_UINT16(_next_chunk);
+	debug(6, "parsePCjrChunk: sound %d, offset %4lx, chunk %x",
+		  _current_nr, (long)(_next_chunk - _current_data), _chunk_type);
+
+	_next_chunk += 2;
+	switch (_chunk_type) {
+	case 0xffff:
+		for (i = 0; i < 4; ++i)
+			clear_channel(i);
+		_current_nr = 0;
+		_current_data = 0;
+		_repeat_chunk = _next_chunk = 0;
+		chainNextSound();
+		break;
+
+	case 0xfffe:
+		_repeat_chunk = _next_chunk;
+		goto parse_again;
+
+	case 0xfffd:
+		_next_chunk = _repeat_chunk;
+		goto parse_again;
+
+	case 0xfffc:
+		/* handle reset. We don't need this do we? */
+		goto parse_again;
+
+	case 0:
+		set_mplex(READ_LE_UINT16(_next_chunk));
+		_next_chunk += 2;
+		for (i = 0; i < 4; i++) {
+			tmp = READ_LE_UINT16(_next_chunk);
+			_next_chunk += 2;
+			if (tmp == 0xffff) {
+				_channels[i].cmd_ptr = 0;
+				continue;
+			}
+			_channels[i].attack    = READ_LE_UINT16(_current_data + tmp);
+			_channels[i].decay     = READ_LE_UINT16(_current_data + tmp + 2);
+			_channels[i].level     = READ_LE_UINT16(_current_data + tmp + 4);
+			_channels[i].sustain_1 = READ_LE_UINT16(_current_data + tmp + 6);
+			_channels[i].sustain_2 = READ_LE_UINT16(_current_data + tmp + 8);
+			_channels[i].notelen   = 1;
+			_channels[i].volume    = 15;
+			_channels[i].cmd_ptr   = _current_data + tmp + 10;
+		}
+		break;
+
+	case 1:
+		set_mplex(READ_LE_UINT16(_next_chunk));
+		tmp = READ_LE_UINT16(_next_chunk + 2);
+		_channels[0].cmd_ptr = tmp != 0xffff ? _current_data + tmp : NULL;
+		tmp = READ_LE_UINT16(_next_chunk + 4);
+		_start = READ_LE_UINT16(_next_chunk + 6);
+		_delta = (int16) READ_LE_UINT16(_next_chunk + 8);
+		_time_left = READ_LE_UINT16(_next_chunk + 10);
+		_next_chunk += 12;
+		if (tmp >= 0xe0) {
+			_channels[3].freq = tmp & 0xf;
+			_value_ptr = &_channels[3].volume;
+		} else {
+			assert(!(tmp & 0x10));
+			tmp = (tmp & 0x60) >> 5;
+			_value_ptr = &_channels[tmp].freq;
+			_channels[tmp].volume = 0;
+		}
+		*_value_ptr = _start;
+		if (_channels[0].cmd_ptr) {
+			tmp = READ_LE_UINT16(_channels[0].cmd_ptr);
+			_start_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 2);
+			_delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 4);
+			_time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 6);
+			_channels[0].cmd_ptr += 8;
+			if (_value_ptr == &_channels[3].volume) {
+				tmp = (tmp & 0x70) >> 4;
+				if (tmp & 1)
+					_value_ptr_2 = &_channels[tmp >> 1].volume;
+				else
+					_value_ptr_2 = &_channels[tmp >> 1].freq;
+			} else {
+				assert(!(tmp & 0x10));
+				tmp = (tmp & 0x60) >> 5;
+				_value_ptr_2 = &_channels[tmp].freq;
+				_channels[tmp].volume = 0;
+			}
+			*_value_ptr_2 = _start_2;
+		}
+		debug(6, "chunk 1: %lu: %d step %d for %d, %lu: %d step %d for %d",
+			  (long)(_value_ptr - (uint *)_channels), _start, _delta, _time_left,
+			  (long)(_value_ptr_2 - (uint *)_channels), _start_2, _delta_2, _time_left_2);
+		break;
+
+	case 2:
+		_start = READ_LE_UINT16(_next_chunk);
+		_end   = READ_LE_UINT16(_next_chunk + 2);
+		_delta = (int16) READ_LE_UINT16(_next_chunk + 4);
+		_channels[0].freq = 0;
+		_next_chunk += 6;
+		_forced_level = -1;
+		debug(6, "chunk 2: %d -> %d step %d",
+			  _start, _end, _delta);
+		break;
+	case 3:
+		set_mplex(READ_LE_UINT16(_next_chunk));
+		tmp = READ_LE_UINT16(_next_chunk + 2);
+		assert((tmp & 0xf0) == 0xe0);
+		_channels[3].freq = tmp & 0xf;
+		if ((tmp & 3) == 3) {
+			_next_chunk += 2;
+			_channels[2].freq = READ_LE_UINT16(_next_chunk + 2);
+		}
+		_channels[3].volume = READ_LE_UINT16(_next_chunk + 4);
+		_repeat_ctr = READ_LE_UINT16(_next_chunk + 6);
+		_delta = (int16) READ_LE_UINT16(_next_chunk + 8);
+		_next_chunk += 10;
+		break;
+	}
+}
+
+void Player_V1::nextPCjrCmd() {
+	uint i;
+	int dummy;
+	switch (_chunk_type) {
+	case 0:
+		for (i = 0; i < 4; i++) {
+			if (!_channels[i].cmd_ptr)
+				continue;
+			if (!--_channels[i].notelen) {
+				dummy = READ_LE_UINT16(_channels[i].cmd_ptr);
+				if (dummy >= 0xfffe) {
+					if (dummy == 0xfffe)
+						_next_chunk = _current_data + 2;
+					parsePCjrChunk();
+					return;
+				}
+				_channels[i].notelen = 4 * dummy;
+				dummy = READ_LE_UINT16(_channels[i].cmd_ptr + 2);
+				if (dummy == 0) {
+					_channels[i].hull_counter = 4;
+					_channels[i].sustctr = _channels[i].sustain_2;
+				} else {
+					_channels[i].hull_counter = 1;
+					_channels[i].freq = dummy;
+				}
+				debug(7, "chunk 0: channel %d play %d for %d",
+					  i, dummy, _channels[i].notelen);
+				_channels[i].cmd_ptr += 4;
+			}
+
+
+			switch (_channels[i].hull_counter) {
+			case 1:
+				_channels[i].volume -= _channels[i].attack;
+				if ((int) _channels[i].volume <= 0) {
+					_channels[i].volume = 0;
+					_channels[i].hull_counter++;
+				}
+				break;
+			case 2:
+				_channels[i].volume += _channels[i].decay;
+				if (_channels[i].volume >= _channels[i].level) {
+					_channels[i].volume = _channels[i].level;
+					_channels[i].hull_counter++;
+				}
+				break;
+			case 4:
+				if (--_channels[i].sustctr < 0) {
+					_channels[i].sustctr = _channels[i].sustain_2;
+					_channels[i].volume += _channels[i].sustain_1;
+					if ((int) _channels[i].volume >= 15) {
+						_channels[i].volume = 15;
+						_channels[i].hull_counter++;
+					}
+				}
+				break;
+			}
+		}
+		break;
+
+	case 1:
+		_start += _delta;
+		*_value_ptr = _start;
+		if (!--_time_left) {
+			_start = READ_LE_UINT16(_next_chunk);
+			_next_chunk += 2;
+			if (_start == 0xffff) {
+				parsePCjrChunk();
+				return;
+			}
+			_delta = (int16) READ_LE_UINT16(_next_chunk);
+			_time_left = READ_LE_UINT16(_next_chunk + 2);
+			_next_chunk += 4;
+			*_value_ptr = _start;
+		}
+
+		if (_channels[0].cmd_ptr) {
+			_start_2 += _delta_2;
+			*_value_ptr_2 = _start_2;
+			if (!--_time_left_2) {
+				_start_2 = READ_LE_UINT16(_channels[0].cmd_ptr);
+				if (_start_2 == 0xffff) {
+					_next_chunk = _channels[0].cmd_ptr + 2;
+					parsePCjrChunk();
+					return;
+				}
+				_delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 2);
+				_time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 4);
+				_channels[0].cmd_ptr += 6;
+			}
+		}
+		break;
+
+	case 2:
+		_start += _delta;
+		if (_start == _end) {
+			parsePCjrChunk();
+			return;
+		}
+		set_mplex(_start);
+		debug(7, "chunk 2: mplex %d  curve %d", _start, _forced_level);
+		_forced_level = -_forced_level;
+		break;
+	case 3:
+		dummy = _channels[3].volume + _delta;
+		if (dummy >= 15) {
+			_channels[3].volume = 15;
+		} else if (dummy <= 0) {
+			_channels[3].volume = 0;
+		} else {
+			_channels[3].volume = dummy;
+			break;
+		}
+
+		if (!--_repeat_ctr) {
+			parsePCjrChunk();
+			return;
+		}
+		_delta = READ_LE_UINT16(_next_chunk);
+		_next_chunk += 2;
+		break;
+	}
+}
+
+void Player_V1::set_mplex(uint mplex) {
+	if (mplex == 0)
+		mplex = 65536;
+	_mplex = mplex;
+	_tick_len = _mplex_step * mplex;
+}
+
+void Player_V1::nextTick() {
+	if (_next_chunk) {
+		if (_pcjr)
+			nextPCjrCmd();
+		else
+			nextSpeakerCmd();
+	}
+}
+
+void Player_V1::generateSpkSamples(int16 *data, uint len) {
+	uint i;
+
+	memset(data, 0, 2 * sizeof(int16) * len);
+	if (_channels[0].freq == 0) {
+		if (_forced_level) {
+			int sample = _forced_level * _volumetable[0];
+			for (i = 0; i < len; i++)
+				data[2*i] = data[2*i+1] = sample;
+			debug(9, "speaker: %8x: forced one", _tick_len);
+		} else if (!_level) {
+			return;
+		}
+	} else {
+		squareGenerator(0, _channels[0].freq, 0, 0, data, len);
+		debug(9, "speaker: %8x: freq %d %.1f", _tick_len,
+				_channels[0].freq, 1193000.0 / _channels[0].freq);
+	}
+	lowPassFilter(data, len);
+}
+
+void Player_V1::generatePCjrSamples(int16 *data, uint len) {
+	uint i, j;
+	uint freq, vol;
+	bool hasdata = false;
+
+	memset(data, 0, 2 * sizeof(int16) * len);
+
+	if (_forced_level) {
+		int sample = _forced_level * _volumetable[0];
+		for (i = 0; i < len; i++)
+			data[2*i] = data[2*i+1] = sample;
+		hasdata = true;
+		debug(9, "channel[4]: %8x: forced one", _tick_len);
+	}
+
+	for (i = 1; i < 3; i++) {
+		freq = _channels[i].freq;
+		if (freq) {
+			for (j = 0; j < i; j++) {
+				if (freq == _channels[j].freq) {
+					/* HACK: this channel is playing at
+					 * the same frequency as another.
+					 * Synchronize it to the same phase to
+					 * prevent interference.
+					 */
+					_timer_count[i] = _timer_count[j];
+					_timer_output ^= (1 << i) &
+						(_timer_output ^ _timer_output << (i - j));
+				}
+			}
+		}
+	}
+
+	for (i = 0; i < 4; i++) {
+		freq = _channels[i].freq;
+		vol  = _channels[i].volume;
+		if (!_volumetable[_channels[i].volume]) {
+			_timer_count[i] -= len << FIXP_SHIFT;
+			if (_timer_count[i] < 0)
+				_timer_count[i] = 0;
+		} else if (i < 3) {
+			hasdata = true;
+			squareGenerator(i, freq, vol, 0, data, len);
+			debug(9, "channel[%d]: %8x: freq %d %.1f ; volume %d",
+				  i, _tick_len, freq, 111860.0 / freq,  vol);
+		} else {
+			int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE;
+			int n = (freq & 3);
+
+			freq = (n == 3) ? 2 * (_channels[2].freq) : 1 << (5 + n);
+			hasdata = true;
+			squareGenerator(i, freq, vol, noiseFB, data, len);
+			debug(9, "channel[%d]: %x: noise freq %d %.1f ; volume %d",
+				  i, _tick_len, freq, 111860.0 / freq,  vol);
+		}
+	}
+
+	if (_level || hasdata)
+		lowPassFilter(data, len);
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player/v1.h b/engines/scumm/player/v1.h
new file mode 100644
index 0000000..d6e08bd
--- /dev/null
+++ b/engines/scumm/player/v1.h
@@ -0,0 +1,98 @@
+/* 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_V1_H
+#define SCUMM_PLAYER_V1_H
+
+#include "scumm/player/v2.h"
+
+namespace Scumm {
+
+/**
+ * Scumm V1 PC-Speaker player.
+ */
+class Player_V1 : public Player_V2 {
+public:
+	Player_V1(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr);
+	~Player_V1();
+
+	virtual void startSound(int sound);
+	virtual void stopSound(int sound);
+	virtual void stopAllSounds();
+	virtual int  getMusicTimer();
+
+protected:
+	virtual void nextTick();
+	virtual void clear_channel(int i);
+	virtual void chainSound(int nr, byte *data);
+
+	virtual void generateSpkSamples(int16 *data, uint len);
+	virtual void generatePCjrSamples(int16 *data, uint len);
+
+	void restartSound();
+
+	void set_mplex(uint mplex);
+	void parseSpeakerChunk();
+	void nextSpeakerCmd();
+	void parsePCjrChunk();
+	void nextPCjrCmd();
+
+private:
+	struct channel_data_v1 {
+		uint freq;
+		uint volume;
+		byte *cmd_ptr;
+		uint notelen;
+		uint hull_counter;
+		uint attack;
+		uint decay;
+		uint level;
+		uint sustain_1;
+		uint sustain_2;
+		int  sustctr;
+	};
+
+	channel_data_v1 _channels[4];
+
+	byte *_next_chunk;
+	byte *_repeat_chunk;
+	uint  _chunk_type;
+	uint  _mplex_step;
+	uint  _mplex;
+	uint  _repeat_ctr;
+	uint  _freq_current;
+	int   _forced_level;
+	uint16 _random_lsr;
+	uint  *_value_ptr;
+	uint  _time_left;
+	uint  _start;
+	uint  _end;
+	int   _delta;
+	uint  *_value_ptr_2;
+	uint  _time_left_2;
+	uint  _start_2;
+	int   _delta_2;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/v2.cpp b/engines/scumm/player/v2.cpp
new file mode 100644
index 0000000..e3b3c97
--- /dev/null
+++ b/engines/scumm/player/v2.cpp
@@ -0,0 +1,327 @@
+/* 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 "scumm/player/v2.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+#define SPK_DECAY   0xa000              /* Depends on sample rate */
+#define PCJR_DECAY  0xa000              /* Depends on sample rate */
+
+#define NG_PRESET 0x0f35        /* noise generator preset */
+#define FB_WNOISE 0x12000       /* feedback for white noise */
+#define FB_PNOISE 0x08000       /* feedback for periodic noise */
+
+
+Player_V2::Player_V2(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr)
+	: Player_V2Base(scumm, mixer, pcjr) {
+
+	int i;
+
+	// Initialize square generator
+	_level = 0;
+
+	_RNG = NG_PRESET;
+
+	_pcjr = pcjr;
+
+	if (_pcjr) {
+		_decay = PCJR_DECAY;
+		_update_step = (_sampleRate << FIXP_SHIFT) / (111860 * 2);
+	} else {
+		_decay = SPK_DECAY;
+		_update_step = (_sampleRate << FIXP_SHIFT) / (1193000 * 2);
+	}
+
+	// Adapt _decay to sample rate.  It must be squared when
+	// sample rate doubles.
+	for (i = 0; (_sampleRate << i) < 30000; i++)
+		_decay = _decay * _decay / 65536;
+
+	_timer_output = 0;
+	for (i = 0; i < 4; i++)
+		_timer_count[i] = 0;
+
+	setMusicVolume(255);
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Player_V2::~Player_V2() {
+	Common::StackLock lock(_mutex);
+	_mixer->stopHandle(_soundHandle);
+}
+
+void Player_V2::setMusicVolume (int vol) {
+	if (vol > 255)
+		vol = 255;
+
+	/* scale to int16, FIXME: find best value */
+	double out = vol * 128 / 3;
+
+	/* build volume table (2dB per step) */
+	for (int i = 0; i < 15; i++) {
+		/* limit volume to avoid clipping */
+		if (out > 0xffff)
+			_volumetable[i] = 0xffff;
+		else
+			_volumetable[i] = (int) out;
+
+		out /= 1.258925412;         /* = 10 ^ (2/20) = 2dB */
+	}
+	_volumetable[15] = 0;
+}
+
+void Player_V2::stopAllSounds() {
+	Common::StackLock lock(_mutex);
+
+	for (int i = 0; i < 4; i++) {
+		clear_channel(i);
+	}
+	_next_nr = _current_nr = 0;
+	_next_data = _current_data = 0;
+}
+
+void Player_V2::stopSound(int nr) {
+	Common::StackLock lock(_mutex);
+
+	if (_next_nr == nr) {
+		_next_nr = 0;
+		_next_data = 0;
+	}
+	if (_current_nr == nr) {
+		for (int i = 0; i < 4; i++) {
+			clear_channel(i);
+		}
+		_current_nr = 0;
+		_current_data = 0;
+		chainNextSound();
+	}
+}
+
+void Player_V2::startSound(int nr) {
+	Common::StackLock lock(_mutex);
+
+	byte *data = _vm->getResourceAddress(rtSound, nr);
+	assert(data);
+
+	int cprio = _current_data ? *(_current_data + _header_len) : 0;
+	int prio  = *(data + _header_len);
+	int nprio = _next_data ? *(_next_data + _header_len) : 0;
+
+	int restartable = *(data + _header_len + 1);
+
+	if (!_current_nr || cprio <= prio) {
+		int tnr = _current_nr;
+		int tprio = cprio;
+		byte *tdata  = _current_data;
+
+		chainSound(nr, data);
+		nr   = tnr;
+		prio = tprio;
+		data = tdata;
+		restartable = data ? *(data + _header_len + 1) : 0;
+	}
+
+	if (!_current_nr) {
+		nr = 0;
+		_next_nr = 0;
+		_next_data = 0;
+	}
+
+	if (nr != _current_nr
+		&& restartable
+		&& (!_next_nr
+		|| nprio <= prio)) {
+
+		_next_nr = nr;
+		_next_data = data;
+	}
+}
+
+int Player_V2::getSoundStatus(int nr) const {
+	return _current_nr == nr || _next_nr == nr;
+}
+
+int Player_V2::readBuffer(int16 *data, const int numSamples) {
+	Common::StackLock lock(_mutex);
+
+	uint step;
+	uint len = numSamples / 2;
+
+	do {
+		if (!(_next_tick >> FIXP_SHIFT)) {
+			_next_tick += _tick_len;
+			nextTick();
+		}
+
+		step = len;
+		if (step > (_next_tick >> FIXP_SHIFT))
+			step = (_next_tick >> FIXP_SHIFT);
+		if (_pcjr)
+			generatePCjrSamples(data, step);
+		else
+			generateSpkSamples(data, step);
+		data += 2 * step;
+		_next_tick -= step << FIXP_SHIFT;
+	} while (len -= step);
+
+	return numSamples;
+}
+
+void Player_V2::lowPassFilter(int16 *sample, uint len) {
+	for (uint i = 0; i < len; i++) {
+		_level = (int) (_level * _decay
+				+ sample[0] * (0x10000 - _decay)) >> 16;
+		sample[0] = sample[1] = _level;
+		sample += 2;
+	}
+}
+
+void Player_V2::squareGenerator(int channel, int freq, int vol,
+								int noiseFeedback, int16 *sample, uint len) {
+	int32 period = _update_step * freq;
+	int32 nsample;
+	if (period == 0)
+		period = _update_step;
+
+	for (uint i = 0; i < len; i++) {
+		uint32 duration = 0;
+
+		if (_timer_output & (1 << channel))
+			duration += _timer_count[channel];
+
+		_timer_count[channel] -= (1 << FIXP_SHIFT);
+		while (_timer_count[channel] <= 0) {
+
+			if (noiseFeedback) {
+				if (_RNG & 1) {
+					_RNG ^= noiseFeedback;
+					_timer_output ^= (1 << channel);
+				}
+				_RNG >>= 1;
+			} else {
+				_timer_output ^= (1 << channel);
+			}
+
+			if (_timer_output & (1 << channel))
+				duration += period;
+
+			_timer_count[channel] += period;
+		}
+
+		if (_timer_output & (1 << channel))
+			duration -= _timer_count[channel];
+
+		nsample = *sample +
+			(((int32) (duration - (1 << (FIXP_SHIFT - 1)))
+				* (int32) _volumetable[vol]) >> FIXP_SHIFT);
+		/* overflow: clip value */
+		if (nsample > 0x7fff)
+			nsample = 0x7fff;
+		if (nsample < -0x8000)
+			nsample = -0x8000;
+		*sample = nsample;
+		// The following write isn't necessary, because the lowPassFilter does it for us
+		//sample[1] = sample[0];
+		sample += 2;
+	}
+}
+
+void Player_V2::generateSpkSamples(int16 *data, uint len) {
+	int winning_channel = -1;
+	for (int i = 0; i < 4; i++) {
+		if (winning_channel == -1
+			&& _channels[i].d.volume
+			&& _channels[i].d.time_left) {
+			winning_channel = i;
+		}
+	}
+
+	memset(data, 0, 2 * sizeof(int16) * len);
+	if (winning_channel != -1) {
+		squareGenerator(0, _channels[winning_channel].d.freq, 0,
+				0, data, len);
+	} else if (_level == 0)
+		/* shortcut: no sound is being played. */
+		return;
+
+	lowPassFilter(data, len);
+}
+
+void Player_V2::generatePCjrSamples(int16 *data, uint len) {
+	int i, j;
+	int freq, vol;
+
+	memset(data, 0, 2 * sizeof(int16) * len);
+	bool hasdata = false;
+
+	for (i = 1; i < 3; i++) {
+		freq = _channels[i].d.freq >> 6;
+		if (_channels[i].d.volume && _channels[i].d.time_left) {
+			for (j = 0; j < i; j++) {
+				if (_channels[j].d.volume
+					&& _channels[j].d.time_left
+					&& freq == (_channels[j].d.freq >> 6)) {
+					/* HACK: this channel is playing at
+					 * the same frequency as another.
+					 * Synchronize it to the same phase to
+					 * prevent interference.
+					 */
+					_timer_count[i] = _timer_count[j];
+					_timer_output ^= (1 << i) &
+						(_timer_output ^ _timer_output << (i - j));
+				}
+			}
+		}
+	}
+
+	for (i = 0; i < 4; i++) {
+		freq = _channels[i].d.freq >> 6;
+		vol  = (65535 - _channels[i].d.volume) >> 12;
+		if (!_channels[i].d.volume || !_channels[i].d.time_left) {
+			_timer_count[i] -= len << FIXP_SHIFT;
+			if (_timer_count[i] < 0)
+				_timer_count[i] = 0;
+		} else if (i < 3) {
+			hasdata = true;
+			squareGenerator(i, freq, vol, 0, data, len);
+		} else {
+			int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE;
+			int n = (freq & 3);
+
+			freq = (n == 3) ? 2 * (_channels[2].d.freq>>6) : 1 << (5 + n);
+			hasdata = true;
+			squareGenerator(i, freq, vol, noiseFB, data, len);
+		}
+#if 0
+		debug(9, "channel[%d]: freq %d %.1f ; volume %d",
+				i, freq, 111860.0 / freq,  vol);
+#endif
+	}
+
+	if (_level || hasdata)
+		lowPassFilter(data, len);
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player/v2.h b/engines/scumm/player/v2.h
new file mode 100644
index 0000000..e23f74c
--- /dev/null
+++ b/engines/scumm/player/v2.h
@@ -0,0 +1,72 @@
+/* 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_V2_H
+#define SCUMM_PLAYER_V2_H
+
+#include "scumm/player/v2base.h"
+
+namespace Scumm {
+
+/**
+ * Scumm V2 PC-Speaker MIDI driver.
+ * This simulates the pc speaker sound, which is driven  by the 8253 (square
+ * wave generator) and a low-band filter.
+ */
+class Player_V2 : public Player_V2Base {
+public:
+	Player_V2(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr);
+	virtual ~Player_V2();
+
+	// 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);
+
+protected:
+	unsigned int _update_step;
+	unsigned int _decay;
+	int _level;
+	unsigned int _RNG;
+	unsigned int _volumetable[16];
+
+	int _timer_count[4];
+	int _timer_output;
+
+protected:
+	virtual void generateSpkSamples(int16 *data, uint len);
+	virtual void generatePCjrSamples(int16 *data, uint len);
+
+	void lowPassFilter(int16 *data, uint len);
+	void squareGenerator(int channel, int freq, int vol,
+						int noiseFeedback, int16 *sample, uint len);
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/player/v2a.cpp b/engines/scumm/player/v2a.cpp
new file mode 100644
index 0000000..2350d7e
--- /dev/null
+++ b/engines/scumm/player/v2a.cpp
@@ -0,0 +1,1954 @@
+/* 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 "engines/engine.h"
+#include "scumm/player/v2a.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+#define BASE_FREQUENCY 3579545
+
+static uint32 CRCtable[256];
+
+
+static void InitCRC() {
+	const uint32 poly = 0xEDB88320;
+	int i, j;
+	uint32 n;
+
+	for (i = 0; i < 256; i++) {
+		n = i;
+		for (j = 0; j < 8; j++)
+			n = (n & 1) ? ((n >> 1) ^ poly) : (n >> 1);
+		CRCtable[i] = n;
+	}
+}
+
+static uint32 GetCRC(byte *data, int len) {
+	uint32 CRC = 0xFFFFFFFF;
+	int i;
+	for (i = 0; i < len; i++)
+		CRC = (CRC >> 8) ^ CRCtable[(CRC ^ data[i]) & 0xFF];
+	return CRC ^ 0xFFFFFFFF;
+}
+
+class V2A_Sound {
+public:
+	V2A_Sound() : _id(0), _mod(NULL) { }
+	virtual ~V2A_Sound() {}
+	virtual void start(Player_MOD *mod, int id, const byte *data) = 0;
+	virtual bool update() = 0;
+	virtual void stop() = 0;
+protected:
+	int _id;
+	Player_MOD *_mod;
+};
+
+// unsupported sound effect, print warning message to console
+class V2A_Sound_Unsupported : public V2A_Sound {
+public:
+	V2A_Sound_Unsupported() { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		warning("player_v2a - sound %i not supported", id);
+	}
+	virtual bool update() { return false; }
+	virtual void stop() { }
+};
+
+// template, automatically stops all channels when a sound is silenced
+template<int numChan>
+class V2A_Sound_Base : public V2A_Sound {
+public:
+	V2A_Sound_Base() : _offset(0), _size(0), _data(0) { }
+	V2A_Sound_Base(uint16 offset, uint16 size) : _offset(offset), _size(size), _data(0) { }
+	virtual void stop() {
+		assert(_id);
+		for (int i = 0; i < numChan; i++)
+			_mod->stopChannel(_id | (i << 8));
+		_id = 0;
+		free(_data);
+		_data = 0;
+	}
+protected:
+	const uint16 _offset;
+	const uint16 _size;
+
+	char *_data;
+};
+
+// plays a music track
+class V2A_Sound_Music : public V2A_Sound {
+public:
+	V2A_Sound_Music(uint16 instoff, uint16 voloff, uint16 chan1off, uint16 chan2off, uint16 chan3off, uint16 chan4off, uint16 sampoff, bool looped) :
+		_instoff(instoff), _voloff(voloff), _chan1off(chan1off), _chan2off(chan2off), _chan3off(chan3off), _chan4off(chan4off), _sampoff(sampoff), _looped(looped) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+
+		_data = (char *)malloc(READ_LE_UINT16(data));
+		memcpy(_data, data, READ_LE_UINT16(data));
+
+		_chan[0].dataptr_i = _chan1off;
+		_chan[1].dataptr_i = _chan2off;
+		_chan[2].dataptr_i = _chan3off;
+		_chan[3].dataptr_i = _chan4off;
+		for (int i = 0; i < 4; i++) {
+			_chan[i].dataptr = _chan[i].dataptr_i;
+			_chan[i].volbase = 0;
+			_chan[i].volptr = 0;
+			_chan[i].chan = 0;
+			_chan[i].dur = 0;
+			_chan[i].ticks = 0;
+		}
+		update();
+	}
+	virtual bool update() {
+		assert(_id);
+		int i, j = 0;
+		for (i = 0; i < 4; i++) {
+			if (_chan[i].dur) {
+				if (!--_chan[i].dur) {
+					_mod->stopChannel(_id | (_chan[i].chan << 8));
+				} else {
+					_mod->setChannelVol(_id | (_chan[i].chan << 8),
+					                    READ_BE_UINT16(_data + _chan[i].volbase + (_chan[i].volptr++ << 1)));
+					if (_chan[i].volptr == 0) {
+						_mod->stopChannel(_id | (_chan[i].chan << 8));
+						_chan[i].dur = 0;
+					}
+				}
+			}
+			if (!_chan[i].dataptr) {
+				j++;
+				continue;
+			}
+			if (READ_BE_UINT16(_data + _chan[i].dataptr) <= _chan[i].ticks) {
+				if (READ_BE_UINT16(_data + _chan[i].dataptr + 2) == 0xFFFF) {
+					if (_looped) {
+						_chan[i].dataptr = _chan[i].dataptr_i;
+						_chan[i].ticks = 0;
+						if (READ_BE_UINT16(_data + _chan[i].dataptr) > 0) {
+							_chan[i].ticks++;
+							continue;
+						}
+					} else {
+						_chan[i].dataptr = 0;
+						j++;
+						continue;
+					}
+				}
+				int freq = BASE_FREQUENCY / READ_BE_UINT16(_data + _chan[i].dataptr + 2);
+				int inst = READ_BE_UINT16(_data + _chan[i].dataptr + 8);
+				_chan[i].volbase = _voloff + (READ_BE_UINT16(_data + _instoff + (inst << 5)) << 9);
+				_chan[i].volptr = 0;
+				_chan[i].chan = READ_BE_UINT16(_data + _chan[i].dataptr + 6) & 0x3;
+
+				if (_chan[i].dur) // if there's something playing, stop it
+					_mod->stopChannel(_id | (_chan[i].chan << 8));
+
+				_chan[i].dur = READ_BE_UINT16(_data + _chan[i].dataptr + 4);
+
+				int vol = READ_BE_UINT16(_data + _chan[i].volbase + (_chan[i].volptr++ << 1));
+
+				int pan;
+				if ((_chan[i].chan == 0) || (_chan[i].chan == 3))
+					pan = -127;
+				else
+					pan = 127;
+				int offset = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x14);
+				int len = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x18);
+				int loopoffset = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x16);
+				int looplen = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x10);
+
+				int size = len + looplen;
+				char *data = (char *)malloc(size);
+				memcpy(data, _data + _sampoff + offset, len);
+				memcpy(data + len, _data + _sampoff + loopoffset, looplen);
+
+				_mod->startChannel(_id | (_chan[i].chan << 8), data, size, freq, vol, len, looplen + len, pan);
+				_chan[i].dataptr += 16;
+			}
+			_chan[i].ticks++;
+		}
+		if (j == 4)
+			return false;
+		return true;
+	}
+	virtual void stop() {
+		assert(_id);
+		for (int i = 0; i < 4; i++) {
+			if (_chan[i].dur)
+				_mod->stopChannel(_id | (_chan[i].chan << 8));
+		}
+		free(_data);
+		_id = 0;
+	}
+private:
+	const uint16 _instoff;
+	const uint16 _voloff;
+	const uint16 _chan1off;
+	const uint16 _chan2off;
+	const uint16 _chan3off;
+	const uint16 _chan4off;
+	const uint16 _sampoff;
+	const bool _looped;
+
+	char *_data;
+	struct tchan {
+		uint16 dataptr_i;
+		uint16 dataptr;
+		uint16 volbase;
+		uint8 volptr;
+		uint16 chan;
+		uint16 dur;
+		uint16 ticks;
+	} _chan[4];
+};
+
+// plays a single waveform
+class V2A_Sound_Single : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Single(uint16 offset, uint16 size, uint16 freq, uint8 vol) :
+		V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		int vol = (_vol << 2) | (_vol >> 4);
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, vol, 0, 0);
+		_ticks = 1 + (60 * _size * _freq) / BASE_FREQUENCY;
+	}
+	virtual bool update() {
+		assert(_id);
+		_ticks--;
+		if (!_ticks) {
+			return false;
+		}
+		return true;
+	}
+private:
+	const uint16 _freq;
+	const uint8 _vol;
+
+	int _ticks;
+};
+
+// plays a single looped waveform
+class V2A_Sound_SingleLooped : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_SingleLooped(uint16 offset, uint16 size, uint16 freq, uint8 vol, uint16 loopoffset, uint16 loopsize) :
+		V2A_Sound_Base<1>(offset, size), _loopoffset(loopoffset), _loopsize(loopsize), _freq(freq), _vol(vol) { }
+	V2A_Sound_SingleLooped(uint16 offset, uint16 size, uint16 freq, uint8 vol) :
+		V2A_Sound_Base<1>(offset, size), _loopoffset(0), _loopsize(size), _freq(freq), _vol(vol) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		int vol = (_vol << 2) | (_vol >> 4);
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, vol, _loopoffset, _loopoffset + _loopsize);
+	}
+	virtual bool update() {
+		assert(_id);
+		return true;
+	}
+private:
+	const uint16 _loopoffset;
+	const uint16 _loopsize;
+	const uint16 _freq;
+	const uint8 _vol;
+};
+
+// plays two looped waveforms
+class V2A_Sound_MultiLooped : public V2A_Sound_Base<2> {
+public:
+	V2A_Sound_MultiLooped(uint16 offset, uint16 size, uint16 freq1, uint8 vol1, uint16 freq2, uint8 vol2) :
+		V2A_Sound_Base<2>(offset, size), _freq1(freq1), _vol1(vol1), _freq2(freq2), _vol2(vol2) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data1 = (char *)malloc(_size);
+		char *tmp_data2 = (char *)malloc(_size);
+		memcpy(tmp_data1, data + _offset, _size);
+		memcpy(tmp_data2, data + _offset, _size);
+		int vol1 = (_vol1 << 1) | (_vol1 >> 5);
+		int vol2 = (_vol2 << 1) | (_vol2 >> 5);
+		_mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, vol1, 0, _size, -127);
+		_mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, vol2, 0, _size, 127);
+	}
+	virtual bool update() {
+		assert(_id);
+		return true;
+	}
+private:
+	const uint16 _freq1;
+	const uint8 _vol1;
+	const uint16 _freq2;
+	const uint8 _vol2;
+};
+
+// plays two looped waveforms for a fixed number of frames
+class V2A_Sound_MultiLoopedDuration : public V2A_Sound_MultiLooped {
+public:
+	V2A_Sound_MultiLoopedDuration(uint16 offset, uint16 size, uint16 freq1, uint8 vol1, uint16 freq2, uint8 vol2, uint16 numframes) :
+		V2A_Sound_MultiLooped(offset, size, freq1, vol1, freq2, vol2), _duration(numframes) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		V2A_Sound_MultiLooped::start(mod, id, data);
+		_ticks = 0;
+	}
+	virtual bool update() {
+		assert(_id);
+		_ticks++;
+		if (_ticks >= _duration)
+			return false;
+		return true;
+	}
+private:
+	const uint16 _duration;
+
+	int _ticks;
+};
+
+// plays a single looped waveform which starts at one frequency and bends to another frequency, where it remains until stopped
+class V2A_Sound_SingleLoopedPitchbend : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_SingleLoopedPitchbend(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint8 vol, uint8 step) :
+		V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _vol(vol), _step(step) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		int vol = (_vol << 2) | (_vol >> 4);
+		_curfreq = _freq1;
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, vol, 0, _size);
+	}
+	virtual bool update() {
+		assert(_id);
+		if (_freq1 < _freq2) {
+			_curfreq += _step;
+			if (_curfreq > _freq2)
+				_curfreq = _freq2;
+			else
+				_mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+		} else {
+			_curfreq -= _step;
+			if (_curfreq < _freq2)
+				_curfreq = _freq2;
+			else
+				_mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+		}
+		return true;
+	}
+private:
+	const uint16 _freq1;
+	const uint16 _freq2;
+	const uint8 _vol;
+	const uint16 _step;
+
+	uint16 _curfreq;
+};
+
+// plays a single looped waveform starting at a specific frequency/volume, dropping in frequency and fading volume to zero
+// used when Maniac Mansion explodes
+class V2A_Sound_Special_Maniac69 : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_Maniac69(uint16 offset, uint16 size, uint16 freq, uint8 vol) :
+		V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		_curvol = (_vol << 3) | (_vol >> 3);
+		_curfreq = _freq;
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, _curvol >> 1, 0, _size);
+	}
+	virtual bool update() {
+		assert(_id);
+		_curfreq += 2;
+		_mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+		_curvol--;
+		if (_curvol == 0)
+			return false;
+		_mod->setChannelVol(_id, _curvol >> 1);
+		return true;
+	}
+private:
+	const uint16 _freq;
+	const uint8 _vol;
+
+	uint16 _curfreq;
+	uint16 _curvol;
+};
+
+// plays a single looped waveform, fading the volume from zero to maximum at one rate, then back to zero at another rate
+// used when a microwave oven goes 'Ding'
+class V2A_Sound_Special_ManiacDing : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_ManiacDing(uint16 offset, uint16 size, uint16 freq, uint8 fadeinrate, uint8 fadeoutrate) :
+		V2A_Sound_Base<1>(offset, size), _freq(freq), _fade1(fadeinrate), _fade2(fadeoutrate) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		_curvol = 1;
+		_dir = 0;
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, _curvol, 0, _size);
+	}
+	virtual bool update() {
+		assert(_id);
+		if (_dir == 0) {
+			_curvol += _fade1;
+			if (_curvol > 0x3F) {
+				_curvol = 0x3F;
+				_dir = 1;
+			}
+		} else {
+			_curvol -= _fade2;
+			if (_curvol < 1)
+				return false;
+		}
+		_mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4));
+		return true;
+	}
+private:
+	const uint16 _freq;
+	const uint16 _fade1;
+	const uint16 _fade2;
+
+	int _curvol;
+	int _dir;
+};
+
+// plays two looped waveforms, fading the volume from zero to maximum at one rate, then back to zero at another rate
+// used in Zak McKracken for several stereo 'Ding' sounds
+class V2A_Sound_Special_ZakStereoDing : public V2A_Sound_Base<2> {
+public:
+	V2A_Sound_Special_ZakStereoDing(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint8 fadeinrate, uint8 fadeoutrate) :
+		V2A_Sound_Base<2>(offset, size), _freq1(freq1), _freq2(freq2), _fade1(fadeinrate), _fade2(fadeoutrate) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data1 = (char *)malloc(_size);
+		char *tmp_data2 = (char *)malloc(_size);
+		memcpy(tmp_data1, data + _offset, _size);
+		memcpy(tmp_data2, data + _offset, _size);
+		_curvol = 1;
+		_dir = 0;
+		_mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, 1, 0, _size, -127);
+		_mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, 1, 0, _size, 127);
+	}
+	virtual bool update() {
+		assert(_id);
+		if (_dir == 0) {
+			_curvol += _fade1;
+			if (_curvol > 0x3F) {
+				_curvol = 0x3F;
+				_dir = 1;
+			}
+		} else {
+			_curvol -= _fade2;
+			if (_curvol < 1)
+				return false;
+		}
+		_mod->setChannelVol(_id | 0x000, (_curvol << 1) | (_curvol >> 5));
+		_mod->setChannelVol(_id | 0x100, (_curvol << 1) | (_curvol >> 5));
+		return true;
+	}
+private:
+	const uint16 _freq1;
+	const uint16 _freq2;
+	const uint16 _fade1;
+	const uint16 _fade2;
+
+	int _curvol;
+	int _dir;
+};
+
+// plays a single looped waveform, starting at one frequency and at full volume, bending down to another frequency, and then fading volume to zero
+// used in Maniac Mansion for the tentacle sounds
+class V2A_Sound_Special_ManiacTentacle : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_ManiacTentacle(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 step) :
+		V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _step(step) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		_curfreq = _freq1;
+		_curvol = 0x3F;
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_curvol << 2) | (_curvol >> 4), 0, _size);
+	}
+	virtual bool update() {
+		assert(_id);
+		if (_curfreq > _freq2)
+			_curvol = 0x3F + _freq2 - _curfreq;
+		if (_curvol < 1)
+			return false;
+		_curfreq += _step;
+		_mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+		_mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4));
+		return true;
+	}
+private:
+	const uint16 _freq1;
+	const uint16 _freq2;
+	const uint16 _step;
+
+	uint16 _curfreq;
+	int _curvol;
+};
+
+// plays a single looped waveform, starting at one frequency, bending down to another frequency, and then back up to the original frequency
+// used for electronic noises
+class V2A_Sound_Special_Maniac59 : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_Maniac59(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 step, uint8 vol) :
+		V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _step(step), _vol(vol) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		int vol = (_vol << 2) | (_vol >> 4);
+		_curfreq = _freq1;
+		_dir = 2;
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, vol, 0, _size);
+	}
+	virtual bool update() {
+		assert(_id);
+		if (_dir == 2) {
+			_curfreq += _step;
+			if (_curfreq > _freq2) {
+				_curfreq = _freq2;
+				_dir = 1;
+			}
+			_mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+		} else if (_dir == 1) {
+			_curfreq -= _step;
+			if (_curfreq < _freq1) {
+				_curfreq = _freq1;
+				_dir = 0;
+			}
+			_mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+		}
+		return true;
+	}
+private:
+	const uint16 _freq1;
+	const uint16 _freq2;
+	const uint16 _step;
+	const uint8 _vol;
+
+	uint16 _curfreq;
+	int _dir;
+};
+
+// plays a single looped waveform, simultaneously bending the frequency downward and slowly fading volume to zero
+// don't remember where this one is used
+class V2A_Sound_Special_Maniac61 : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_Maniac61(uint16 offset, uint16 size, uint16 freq1, uint16 freq2) :
+		V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		_curfreq = _freq1;
+		_curvol = 0x3F;
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_curvol << 2) | (_curvol >> 4), 0, _size);
+	}
+	virtual bool update() {
+		assert(_id);
+		_curfreq++;
+		if (!(_curfreq & 3))
+			_curvol--;
+		if ((_curfreq == _freq2) || (_curvol == 0))
+			return false;
+		_mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+		_mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4));
+		return true;
+	}
+private:
+	const uint16 _freq1;
+	const uint16 _freq2;
+
+	uint16 _curfreq;
+	uint8 _curvol;
+};
+
+// intermittently plays two looped waveforms for a specific duration
+// used for ringing telephones
+class V2A_Sound_Special_ManiacPhone : public V2A_Sound_Base<2> {
+public:
+	V2A_Sound_Special_ManiacPhone(uint16 offset, uint16 size, uint16 freq1, uint8 vol1, uint16 freq2, uint8 vol2, uint16 numframes, uint8 playwidth, uint8 loopwidth) :
+		V2A_Sound_Base<2>(offset, size), _freq1(freq1), _vol1(vol1), _freq2(freq2), _vol2(vol2), _duration(numframes), _playwidth(playwidth), _loopwidth(loopwidth) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		_data = (char *)malloc(READ_LE_UINT16(data));
+		memcpy(_data, data, READ_LE_UINT16(data));
+		soundon();
+		_ticks = 0;
+		_loop = 0;
+	}
+	virtual bool update() {
+		assert(_id);
+		if (_loop == _playwidth) {
+			_mod->stopChannel(_id | 0x000);
+			_mod->stopChannel(_id | 0x100);
+		}
+		if (_loop == _loopwidth) {
+			_loop = 0;
+			soundon();
+		}
+		_loop++;
+		_ticks++;
+		if (_ticks >= _duration)
+			return false;
+		return true;
+	}
+private:
+	const uint16 _freq1;
+	const uint8 _vol1;
+	const uint16 _freq2;
+	const uint8 _vol2;
+	const uint16 _duration;
+	const uint8 _playwidth;
+	const uint8 _loopwidth;
+
+	int _ticks;
+	int _loop;
+
+	void soundon() {
+		char *tmp_data1 = (char *)malloc(_size);
+		char *tmp_data2 = (char *)malloc(_size);
+		memcpy(tmp_data1, _data + _offset, _size);
+		memcpy(tmp_data2, _data + _offset, _size);
+		int vol1 = (_vol1 << 1) | (_vol1 >> 5);
+		int vol2 = (_vol2 << 1) | (_vol2 >> 5);
+		_mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, vol1, 0, _size, -127);
+		_mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, vol2, 0, _size, 127);
+	}
+};
+
+// intermittently plays a single waveform for a specified duration
+// used when applying a wrench to a pipe
+class V2A_Sound_Special_Maniac46 : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_Maniac46(uint16 offset, uint16 size, uint16 freq, uint8 vol, uint8 loopwidth, uint8 numloops) :
+		V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol), _loopwidth(loopwidth), _numloops(numloops) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		_data = (char *)malloc(READ_LE_UINT16(data));
+		memcpy(_data, data, READ_LE_UINT16(data));
+		soundon();
+		_loop = 0;
+		_loopctr = 0;
+	}
+	virtual bool update() {
+		assert(_id);
+		_loop++;
+		if (_loop == _loopwidth) {
+			_loop = 0;
+			_loopctr++;
+			if (_loopctr == _numloops)
+				return false;
+			_mod->stopChannel(_id);
+			soundon();
+		}
+		return true;
+	}
+private:
+	const uint16 _freq;
+	const uint8 _vol;
+	const uint8 _loopwidth;
+	const uint8 _numloops;
+
+	int _loop;
+	int _loopctr;
+
+	void soundon() {
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, _data + _offset, _size);
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, (_vol << 2) | (_vol >> 4), 0, 0);
+	}
+};
+
+// plays a single waveform at irregular intervals for a specified number of frames, possibly looped
+// used for typewriter noises, as well as tapping on the bus in Zak McKracken
+class V2A_Sound_Special_ManiacTypewriter : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_ManiacTypewriter(uint16 offset, uint16 size, uint16 freq, uint8 vol, uint8 numdurs, const uint8 *durations, bool looped) :
+		V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol), _numdurs(numdurs), _durations(durations), _looped(looped) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		_data = (char *)malloc(READ_LE_UINT16(data));
+		memcpy(_data, data, READ_LE_UINT16(data));
+		soundon();
+		_curdur = 0;
+		_ticks = _durations[_curdur++];
+	}
+	virtual bool update() {
+		assert(_id);
+		_ticks--;
+		if (!_ticks) {
+			if (_curdur == _numdurs) {
+				if (_looped)
+					_curdur = 0;
+				else
+					return false;
+			}
+			_mod->stopChannel(_id);
+			soundon();
+			_ticks = _durations[_curdur++];
+		}
+		return true;
+	}
+private:
+	const uint16 _freq;
+	const uint8 _vol;
+	const uint8 _numdurs;
+	const uint8 *_durations;
+	const bool _looped;
+
+	int _ticks;
+	int _curdur;
+
+	void soundon() {
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, _data + _offset, _size);
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, (_vol << 2) | (_vol >> 4), 0, 0);
+	}
+};
+
+// plays two looped waveforms pitch bending up at various predefined rates
+// used for some sort of siren-like noise in Maniac Mansion
+class V2A_Sound_Special_Maniac44 : public V2A_Sound_Base<2> {
+public:
+	V2A_Sound_Special_Maniac44(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint16 freq1, uint16 freq2, uint8 vol) :
+		_offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _freq1(freq1), _freq2(freq2), _vol(vol) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		_data = (char *)malloc(READ_LE_UINT16(data));
+		memcpy(_data, data, READ_LE_UINT16(data));
+
+		_loopnum = 1;
+		_step = 2;
+		_curfreq = _freq1;
+
+		soundon(_data + _offset1, _size1);
+	}
+	virtual bool update() {
+		assert(_id);
+		_mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _curfreq);
+		_mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / (_curfreq + 3));
+		_curfreq -= _step;
+		if (_loopnum == 7) {
+			if ((BASE_FREQUENCY / _curfreq) >= 65536)
+				return false;
+			else
+				return true;
+		}
+		if (_curfreq >= _freq2)
+			return true;
+		const char steps[8] = {0, 2, 2, 3, 4, 8, 15, 2};
+		_curfreq = _freq1;
+		_step = steps[++_loopnum];
+		if (_loopnum == 7) {
+			_mod->stopChannel(_id | 0x000);
+			_mod->stopChannel(_id | 0x100);
+			soundon(_data + _offset2, _size2);
+		}
+		return true;
+	}
+private:
+	const uint16 _offset1;
+	const uint16 _size1;
+	const uint16 _offset2;
+	const uint16 _size2;
+	const uint16 _freq1;
+	const uint16 _freq2;
+	const uint8 _vol;
+
+	int _curfreq;
+	uint16 _loopnum;
+	uint16 _step;
+
+	void soundon(const char *data, int size) {
+		char *tmp_data1 = (char *)malloc(size);
+		char *tmp_data2 = (char *)malloc(size);
+		memcpy(tmp_data1, data, size);
+		memcpy(tmp_data2, data, size);
+		int vol = (_vol << 1) | (_vol >> 5);
+		_mod->startChannel(_id | 0x000, tmp_data1, size, BASE_FREQUENCY / _curfreq, vol, 0, size, -127);
+		_mod->startChannel(_id | 0x100, tmp_data2, size, BASE_FREQUENCY / (_curfreq + 3), vol, 0, size, 127);
+	}
+};
+
+// plays 4 looped waveforms, each at modulating frequencies
+// used for the siren noise in Maniac Mansion
+class V2A_Sound_Special_Maniac32 : public V2A_Sound_Base<4> {
+public:
+	V2A_Sound_Special_Maniac32(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint8 vol) :
+		_offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _vol(vol) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+
+		_freq1 = 0x02D0;
+		_step1 = -0x000A;
+		_freq2 = 0x0122;
+		_step2 = 0x000A;
+		_freq3 = 0x02BC;
+		_step3 = -0x0005;
+		_freq4 = 0x010E;
+		_step4 = 0x0007;
+
+		char *tmp_data1 = (char *)malloc(_size1);
+		char *tmp_data2 = (char *)malloc(_size2);
+		char *tmp_data3 = (char *)malloc(_size1);
+		char *tmp_data4 = (char *)malloc(_size2);
+		memcpy(tmp_data1, data + _offset1, _size1);
+		memcpy(tmp_data2, data + _offset2, _size2);
+		memcpy(tmp_data3, data + _offset1, _size1);
+		memcpy(tmp_data4, data + _offset2, _size2);
+		_mod->startChannel(_id | 0x000, tmp_data1, _size1, BASE_FREQUENCY / _freq1, _vol, 0, _size1, -127);
+		_mod->startChannel(_id | 0x100, tmp_data2, _size2, BASE_FREQUENCY / _freq2, _vol, 0, _size2, 127);
+		_mod->startChannel(_id | 0x200, tmp_data3, _size1, BASE_FREQUENCY / _freq3, _vol, 0, _size1, 127);
+		_mod->startChannel(_id | 0x300, tmp_data4, _size2, BASE_FREQUENCY / _freq4, _vol, 0, _size2, -127);
+	}
+	virtual bool update() {
+		assert(_id);
+		updatefreq(_freq1, _step1, 0x00AA, 0x00FA);
+		updatefreq(_freq2, _step2, 0x019A, 0x03B6);
+		updatefreq(_freq3, _step3, 0x00AA, 0x00FA);
+		updatefreq(_freq4, _step4, 0x019A, 0x03B6);
+		_mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq1);
+		_mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / _freq2);
+		_mod->setChannelFreq(_id | 0x200, BASE_FREQUENCY / _freq3);
+		_mod->setChannelFreq(_id | 0x300, BASE_FREQUENCY / _freq4);
+		return true;
+	}
+private:
+	const uint16 _offset1;
+	const uint16 _size1;
+	const uint16 _offset2;
+	const uint16 _size2;
+	const uint8 _vol;
+
+	uint16 _freq1;
+	int16 _step1;
+	uint16 _freq2;
+	int16 _step2;
+	uint16 _freq3;
+	int16 _step3;
+	uint16 _freq4;
+	int16 _step4;
+
+	void updatefreq(uint16 &freq, int16 &step, uint16 min, uint16 max) {
+		freq += step;
+		if (freq <= min) {
+			freq = min;
+			step = -step;
+		}
+		if (freq >= max) {
+			freq = max;
+			step = -step;
+		}
+	}
+};
+
+// plays 4 looped waveforms
+// used in the white crystal chamber
+class V2A_Sound_Special_Zak70 : public V2A_Sound_Base<4> {
+public:
+	V2A_Sound_Special_Zak70(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 freq3, uint16 freq4, uint8 vol) :
+		V2A_Sound_Base<4>(offset, size), _freq1(freq1), _freq2(freq2), _freq3(freq3), _freq4(freq4), _vol(vol) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+
+		char *tmp_data1 = (char *)malloc(_size);
+		char *tmp_data2 = (char *)malloc(_size);
+		char *tmp_data3 = (char *)malloc(_size);
+		char *tmp_data4 = (char *)malloc(_size);
+		memcpy(tmp_data1, data + _offset, _size);
+		memcpy(tmp_data2, data + _offset, _size);
+		memcpy(tmp_data3, data + _offset, _size);
+		memcpy(tmp_data4, data + _offset, _size);
+		_mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, _vol, 0, _size, -127);
+		_mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, _vol, 0, _size, 127);
+		_mod->startChannel(_id | 0x200, tmp_data3, _size, BASE_FREQUENCY / _freq3, _vol, 0, _size, 127);
+		_mod->startChannel(_id | 0x300, tmp_data4, _size, BASE_FREQUENCY / _freq4, _vol, 0, _size, -127);
+	}
+	virtual bool update() {
+		assert(_id);
+		return true;
+	}
+protected:
+	const uint16 _freq1;
+	const uint16 _freq2;
+	const uint16 _freq3;
+	const uint16 _freq4;
+	const uint8 _vol;
+};
+
+// plays 4 looped waveforms and fades volume to zero after a specific delay
+// used when the Mindbender disappears
+class V2A_Sound_Special_Zak101 : public V2A_Sound_Special_Zak70 {
+public:
+	V2A_Sound_Special_Zak101(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 freq3, uint16 freq4, uint8 vol, uint16 dur) :
+		V2A_Sound_Special_Zak70(offset, size, freq1, freq2, freq3, freq4, vol), _dur(dur) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		V2A_Sound_Special_Zak70::start(mod, id, data);
+		_ticks = _dur;
+	}
+	virtual bool update() {
+		assert(_id);
+		if (!--_ticks)
+			return false;
+		if (_ticks < _vol) {
+			_mod->setChannelVol(_id | 0x000, _ticks);
+			_mod->setChannelVol(_id | 0x100, _ticks);
+			_mod->setChannelVol(_id | 0x200, _ticks);
+			_mod->setChannelVol(_id | 0x300, _ticks);
+		}
+		return true;
+	}
+private:
+	const uint16 _dur;
+
+	int _ticks;
+};
+
+// plays a single looped waveform and slowly fades volume to zero
+// used when refilling oxygen
+class V2A_Sound_Special_Zak37 : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_Zak37(uint16 offset, uint16 size, uint16 freq, uint8 vol) :
+		V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		_curvol = _vol << 2;
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, _curvol, 0, _size);
+	}
+	virtual bool update() {
+		assert(_id);
+		if (!--_curvol)
+			return false;
+		_mod->setChannelVol(_id, _curvol);
+		return true;
+	}
+private:
+	const uint16 _freq;
+	const uint8 _vol;
+
+	int _curvol;
+};
+
+// plays a single looped waveform, slowly bending from one frequency to another and then slowly fading volume from max to zero
+// used in Zak for airplane taking off and landing
+class V2A_Sound_Special_ZakAirplane : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_ZakAirplane(uint16 offset, uint16 size, uint16 freq1, uint16 freq2) :
+		V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		_curfreq = _freq1;
+		_curvol = 0x3F;
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_curvol << 2) | (_curvol >> 4), 0, _size);
+		_ticks = 0;
+	}
+	virtual bool update() {
+		assert(_id);
+		_ticks++;
+		if (_ticks < 4)
+			return true;
+		_ticks = 0;
+		if (_curfreq == _freq2) {
+			_curvol--;
+			if (_curvol == 0)
+				return false;
+			_mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4));
+		} else {
+			if (_freq1 < _freq2)
+				_curfreq++;
+			else
+				_curfreq--;
+			_mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+		}
+		return true;
+	}
+private:
+	const uint16 _freq1;
+	const uint16 _freq2;
+
+	uint16 _curfreq;
+	int _curvol;
+	int _ticks;
+};
+
+// plays 4 looped waveforms, starting at specific frequencies and bending at different rates while fading volume to zero
+// used when the white crystal machine turns off
+class V2A_Sound_Special_Zak71 : public V2A_Sound_Base<4> {
+public:
+	V2A_Sound_Special_Zak71(uint16 offset, uint16 size) :
+		_offset(offset), _size(size) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+
+		_freq1 = 0x00C8;
+		_freq2 = 0x0190;
+		_freq3 = 0x0320;
+		_freq4 = 0x0640;
+		_vol = 0x78;
+
+		char *tmp_data1 = (char *)malloc(_size);
+		char *tmp_data2 = (char *)malloc(_size);
+		char *tmp_data3 = (char *)malloc(_size);
+		char *tmp_data4 = (char *)malloc(_size);
+		memcpy(tmp_data1, data + _offset, _size);
+		memcpy(tmp_data2, data + _offset, _size);
+		memcpy(tmp_data3, data + _offset, _size);
+		memcpy(tmp_data4, data + _offset, _size);
+		_mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, MIN((_vol >> 1) + 3, 0x32), 0, _size, -127);
+		_mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, MIN((_vol >> 1) + 3, 0x32), 0, _size, 127);
+		_mod->startChannel(_id | 0x200, tmp_data3, _size, BASE_FREQUENCY / _freq3, MIN((_vol >> 1) + 3, 0x32), 0, _size, 127);
+		_mod->startChannel(_id | 0x300, tmp_data4, _size, BASE_FREQUENCY / _freq4, MIN((_vol >> 1) + 3, 0x32), 0, _size, -127);
+	}
+	virtual bool update() {
+		assert(_id);
+		_freq1 += 0x14;
+		_freq2 += 0x1E;
+		_freq3 += 0x32;
+		_freq4 += 0x50;
+		_mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq1);
+		_mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / _freq2);
+		_mod->setChannelFreq(_id | 0x200, BASE_FREQUENCY / _freq3);
+		_mod->setChannelFreq(_id | 0x300, BASE_FREQUENCY / _freq4);
+		_vol--;
+		if (_vol == 0)
+			return false;
+		_mod->setChannelVol(_id | 0x000, MIN((_vol >> 1) + 3, 0x32));
+		_mod->setChannelVol(_id | 0x100, MIN((_vol >> 1) + 3, 0x32));
+		_mod->setChannelVol(_id | 0x200, MIN((_vol >> 1) + 3, 0x32));
+		_mod->setChannelVol(_id | 0x300, MIN((_vol >> 1) + 3, 0x32));
+		return true;
+	}
+private:
+	const uint16 _offset;
+	const uint16 _size;
+
+	uint16 _freq1;
+	uint16 _freq2;
+	uint16 _freq3;
+	uint16 _freq4;
+	uint8 _vol;
+};
+
+// plays a single looped waveform, bending the frequency upward at a varying rate
+// used when the Skolarian device activates
+class V2A_Sound_Special_Zak99 : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_Zak99(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint8 vol) :
+		V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _vol(vol) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		char *tmp_data = (char *)malloc(_size);
+		memcpy(tmp_data, data + _offset, _size);
+		_curfreq = _freq1;
+		_mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_vol << 2) | (_vol >> 4), 0, _size);
+		_bendrate = 8;
+		_bendctr = 100;
+		_holdctr = 30;
+	}
+	virtual bool update() {
+		assert(_id);
+		if (_curfreq >= _freq2) {
+			_mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq);
+			_curfreq -= _bendrate;
+			if (--_bendctr)
+				return true;
+			_bendrate--;
+			if (_bendrate < 2)
+				_bendrate = 2;
+		} else {
+			if (!--_holdctr)
+				return false;
+		}
+		return true;
+	}
+private:
+	const uint16 _freq1;
+	const uint16 _freq2;
+	const uint16 _vol;
+
+	uint16 _curfreq;
+	uint16 _bendrate;
+	uint16 _bendctr;
+	uint16 _holdctr;
+};
+
+// plays one waveform, then switches to a different looped waveform and slowly fades volume to zero
+// used when depressurizing the hostel
+class V2A_Sound_Special_Zak54 : public V2A_Sound_Base<1> {
+public:
+	V2A_Sound_Special_Zak54(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint16 freq) :
+		_offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _freq(freq) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		_data = (char *)malloc(READ_LE_UINT16(data));
+		memcpy(_data, data, READ_LE_UINT16(data));
+		char *tmp_data = (char *)malloc(_size1);
+		memcpy(tmp_data, data + _offset1, _size1);
+		_vol = 0xFC;
+		_mod->startChannel(_id, tmp_data, _size1, BASE_FREQUENCY / _freq, _vol, 0, _size1);
+		_loop = _size1 * _freq * 60 / BASE_FREQUENCY;
+	}
+	virtual bool update() {
+		assert(_id);
+		if (!_loop) {
+			_vol--;
+			if (_vol)
+				_mod->setChannelVol(_id, _vol);
+			else
+				return false;
+		} else if (!--_loop) {
+			_mod->stopChannel(_id);
+			char *tmp_data = (char *)malloc(_size2);
+			memcpy(tmp_data, _data + _offset2, _size2);
+			_mod->startChannel(_id, tmp_data, _size2, BASE_FREQUENCY / _freq, _vol, 0, _size2);
+		}
+		return true;
+	}
+
+private:
+	const uint16 _offset1;
+	const uint16 _offset2;
+	const uint16 _size1;
+	const uint16 _size2;
+	const uint16 _freq;
+
+	int _vol;
+	int _loop;
+};
+
+// plays 2 looped waveforms at different frequencies, pulsing at different frequencies and ramping the volume up and down once
+// used when abducted at the Bermuda Triangle
+class V2A_Sound_Special_Zak110 : public V2A_Sound_Base<2> {
+public:
+	V2A_Sound_Special_Zak110(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint16 freq1, uint16 freq2) :
+		_offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _freq1(freq1), _freq2(freq2) { }
+	virtual void start(Player_MOD *mod, int id, const byte *data) {
+		_mod = mod;
+		_id = id;
+		_data = (char *)malloc(READ_LE_UINT16(data));
+		memcpy(_data, data, READ_LE_UINT16(data));
+
+		_loopnum = 0;
+		_vol = 0x1500;
+		_beepcount = 0;
+	}
+	virtual bool update() {
+		char *tmp_data;
+		assert(_id);
+
+		int vol = (((_vol >> 7) & 0x7E) | ((_vol >> 15) & 0x01));
+		_beepcount++;
+
+		switch (_beepcount & 0x3) {
+		case 0:
+			_mod->stopChannel(_id | 0x000);
+			break;
+		case 1:
+			tmp_data = (char *)malloc(_size1);
+			memcpy(tmp_data, _data + _offset1, _size1);
+			_mod->startChannel(_id | 0x000, tmp_data, _size1, BASE_FREQUENCY / _freq1, vol, 0, _size1, -127);
+			break;
+		default:
+			_mod->setChannelVol(_id | 0x000, vol);
+			break;
+		}
+
+		switch (_beepcount & 0x7) {
+		case 0:
+			_mod->stopChannel(_id | 0x100);
+			break;
+		case 1:
+			tmp_data = (char *)malloc(_size2);
+			memcpy(tmp_data, _data + _offset2, _size2);
+			_mod->startChannel(_id | 0x100, tmp_data, _size2, BASE_FREQUENCY / _freq2, vol, 0, _size2, 127);
+			break;
+		default:
+			_mod->setChannelVol(_id | 0x100, vol);
+			break;
+		}
+
+		if (_loopnum == 0) {
+			_vol += 0x80;
+			if (_vol == 0x4000) {
+				_vol = 0x3F00;
+				_loopnum = 1;
+			}
+		} else if (_loopnum == 1) {
+			_vol -= 0x20;
+			if (_vol == 0x2000)
+				_loopnum = 2;
+		}
+		return true;
+	}
+private:
+	const uint16 _offset1;
+	const uint16 _size1;






More information about the Scummvm-git-logs mailing list