[Scummvm-git-logs] scummvm master -> 0239ed5e963da9ce6f824386b87ef1144d3c1d9d

athrxx athrxx at scummvm.org
Sat Feb 1 20:12:23 UTC 2020


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

Summary:
46ef0109b5 KYRA: (EOB) - prepare for PC Speaker support
0820b0617d KYRA: (EOB) - add PC Speaker driver
2c91759a05 KYRA: (LOK) - add missing delay (bug #11330)
0239ed5e96 KYRA: (HOF) - fix bug #11331


Commit: 46ef0109b53e7ceb9a356c4483d318cabb3fcacc
    https://github.com/scummvm/scummvm/commit/46ef0109b53e7ceb9a356c4483d318cabb3fcacc
Author: athrxx (athrxx at scummvm.org)
Date: 2020-02-01T21:09:49+01:00

Commit Message:
KYRA: (EOB) - prepare for PC Speaker support

This commit does not include the actual driver. It only provides the necessary refactoring and resource handling.

Changed paths:
  A engines/kyra/sound/drivers/pc_base.h
  A engines/kyra/sound/drivers/pcspeaker_v1.cpp
  A engines/kyra/sound/drivers/pcspeaker_v2.cpp
  A engines/kyra/sound/sound_pc_midi.cpp
  A engines/kyra/sound/sound_pc_v1.cpp
  A engines/kyra/sound/sound_pc_v1.h
  R engines/kyra/sound/drivers/adlib.h
  R engines/kyra/sound/drivers/pcspeaker.cpp
  R engines/kyra/sound/sound_adlib.cpp
  R engines/kyra/sound/sound_adlib.h
  R engines/kyra/sound/sound_midi.cpp
    devtools/create_kyradat/create_kyradat.cpp
    devtools/create_kyradat/resources/eob1_dos.h
    dists/engine-data/kyra.dat
    engines/kyra/detection_tables.h
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/engine/kyra_v1.cpp
    engines/kyra/module.mk
    engines/kyra/resource/staticres.cpp
    engines/kyra/sound/drivers/adlib.cpp
    engines/kyra/sound/sound_intern.h


diff --git a/devtools/create_kyradat/create_kyradat.cpp b/devtools/create_kyradat/create_kyradat.cpp
index a2ec311..0ae98ac 100644
--- a/devtools/create_kyradat/create_kyradat.cpp
+++ b/devtools/create_kyradat/create_kyradat.cpp
@@ -45,7 +45,7 @@
 
 
 enum {
-	kKyraDatVersion = 95
+	kKyraDatVersion = 96
 };
 
 const ExtractFilename extractFilenames[] = {
diff --git a/devtools/create_kyradat/resources/eob1_dos.h b/devtools/create_kyradat/resources/eob1_dos.h
index 61cf3af..d35fb74 100644
--- a/devtools/create_kyradat/resources/eob1_dos.h
+++ b/devtools/create_kyradat/resources/eob1_dos.h
@@ -1755,20 +1755,23 @@ static const EoBCharacter kEoB1NpcPresetsDOS[9] = {
 
 static const EoBCharacterProvider kEoB1NpcPresetsDOSProvider = { ARRAYSIZE(kEoB1NpcPresetsDOS), kEoB1NpcPresetsDOS };
 
-static const char *const kEoB1SoundFilesIntroDOS[1] = {
-	"SOUND"
+static const char *const kEoB1SoundFilesIntroDOS[2] = {
+	"SOUND",
+	"PCSOUND"
 };
 
 static const StringListProvider kEoB1SoundFilesIntroDOSProvider = { ARRAYSIZE(kEoB1SoundFilesIntroDOS), kEoB1SoundFilesIntroDOS };
 
-static const char *const kEoB1SoundFilesIngameDOS[1] = {
-	"ADLIB"
+static const char *const kEoB1SoundFilesIngameDOS[2] = {
+	"ADLIB",
+	"PCGAMESN"
 };
 
 static const StringListProvider kEoB1SoundFilesIngameDOSProvider = { ARRAYSIZE(kEoB1SoundFilesIngameDOS), kEoB1SoundFilesIngameDOS };
 
-static const char *const kEoB1SoundFilesFinaleDOS[1] = {
-	"ADLIB"
+static const char *const kEoB1SoundFilesFinaleDOS[2] = {
+	"ADLIB",
+	"PCGAMESN"
 };
 
 static const StringListProvider kEoB1SoundFilesFinaleDOSProvider = { ARRAYSIZE(kEoB1SoundFilesFinaleDOS), kEoB1SoundFilesFinaleDOS };
diff --git a/dists/engine-data/kyra.dat b/dists/engine-data/kyra.dat
index 7b037b8..568e7f1 100644
Binary files a/dists/engine-data/kyra.dat and b/dists/engine-data/kyra.dat differ
diff --git a/engines/kyra/detection_tables.h b/engines/kyra/detection_tables.h
index 2f7f248..a81dc97 100644
--- a/engines/kyra/detection_tables.h
+++ b/engines/kyra/detection_tables.h
@@ -1654,7 +1654,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::EN_ANY,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO8(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP)
+			GUIO9(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_MIDIPCJR, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP)
 		},
 		EOB_FLAGS
 	},
@@ -1670,7 +1670,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::DE_DEU,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO8(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP)
+			GUIO9(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_MIDIPCJR, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP)
 		},
 		EOB_FLAGS
 	},
@@ -1686,7 +1686,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::IT_ITA,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO8(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP)
+			GUIO9(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_MIDIPCJR, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP)
 		},
 		EOB_FLAGS
 	},
diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index e812303..d51f132 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -25,7 +25,7 @@
 #include "kyra/engine/kyra_rpg.h"
 #include "kyra/resource/resource.h"
 #include "kyra/sound/sound_intern.h"
-#include "kyra/sound/sound_adlib.h"
+#include "kyra/sound/sound_pc_v1.h"
 #include "kyra/script/script_eob.h"
 #include "kyra/engine/timer.h"
 #include "kyra/gui/debugger.h"
@@ -411,11 +411,10 @@ Common::Error EoBCoreEngine::init() {
 	// don't really need one). We just disable the sound in the settings.
 	MidiDriver::DeviceHandle dev = 0;
 	if (_flags.platform == Common::kPlatformDOS) {
-		dev = MidiDriver::detectDevice(/*MDT_PCSPK | */MDT_ADLIB);
-		//if (MidiDriver::getMusicType(dev) == MT_ADLIB)
-			_sound = new SoundAdLibPC(this, _mixer);
-		//else
-		//	_sound = new SoundPCS(this, _mixer);
+		int flags = MDT_ADLIB | MDT_PCSPK;
+		dev = MidiDriver::detectDevice(_flags.gameID == GI_EOB1 ? flags | MDT_PCJR : flags);
+		MusicType type = MidiDriver::getMusicType(dev);
+		_sound = new SoundPC_v1(this, _mixer, type == MT_ADLIB ? Sound::kAdLib : type == MT_PCSPK ? Sound::kPCSpkr : Sound::kPCjr);
 	} else if (_flags.platform == Common::kPlatformFMTowns) {
 		dev = MidiDriver::detectDevice(MDT_TOWNS);
 		// SoundTowns_Darkmoon requires initialized _staticres
@@ -435,8 +434,8 @@ Common::Error EoBCoreEngine::init() {
 	assert(_sound);
 	_sound->init();
 
-	// This if for EOB1 PC-98 only
-	_sound->loadSfxFile("EFECT.OBJ");
+	if (_flags.platform == Common::kPlatformPC98)
+		_sound->loadSfxFile("EFECT.OBJ");
 
 	// Setup volume settings (and read in all ConfigManager settings)
 	_configNullSound = (MidiDriver::getMusicType(dev) == MT_NULL);
diff --git a/engines/kyra/engine/kyra_v1.cpp b/engines/kyra/engine/kyra_v1.cpp
index ec4f07e..b83aa3b 100644
--- a/engines/kyra/engine/kyra_v1.cpp
+++ b/engines/kyra/engine/kyra_v1.cpp
@@ -121,7 +121,7 @@ Common::Error KyraEngine_v1::init() {
 			// Kyra games.
 			MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_PCSPK | MDT_MIDI | MDT_ADLIB | ((_flags.gameID == GI_KYRA2 || _flags.gameID == GI_LOL) ? MDT_PREFER_GM : MDT_PREFER_MT32));
 			if (MidiDriver::getMusicType(dev) == MT_ADLIB) {
-				_sound = new SoundAdLibPC(this, _mixer);
+				_sound = new SoundPC_v1(this, _mixer, Sound::kAdLib);
 			} else {
 				Sound::kType type;
 				const MusicType midiType = MidiDriver::getMusicType(dev);
@@ -153,7 +153,7 @@ Common::Error KyraEngine_v1::init() {
 				// missing. It's just that at least at the time of writing they
 				// are decidedly inferior to the AdLib ones.
 				if (ConfMan.getBool("multi_midi")) {
-					SoundAdLibPC *adlib = new SoundAdLibPC(this, _mixer);
+					SoundPC_v1 *adlib = new SoundPC_v1(this, _mixer, Sound::kAdLib);
 					assert(adlib);
 
 					_sound = new MixedSoundDriver(this, _mixer, soundMidiPc, adlib);
diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk
index 3f4bab6..84fffb7 100644
--- a/engines/kyra/module.mk
+++ b/engines/kyra/module.mk
@@ -60,10 +60,10 @@ MODULE_OBJS := \
 	sequence/sequences_v2.o \
 	sequence/sequences_hof.o \
 	sequence/sequences_mr.o \
-	sound/sound_adlib.o \
 	sound/sound_amiga_lok.o \
 	sound/sound_digital_mr.o \
-	sound/sound_midi.o \
+	sound/sound_pc_midi.o \
+	sound/sound_pc_v1.o \
 	sound/sound_pc98_lok.o \
 	sound/sound_pc98_v2.o \
 	sound/sound_towns_lok.o \
@@ -72,7 +72,7 @@ MODULE_OBJS := \
 	sound/drivers/adlib.o \
 	sound/drivers/audstream.o \
 	sound/drivers/midi.o \
-	sound/drivers/pcspeaker.o \
+	sound/drivers/pcspeaker_v2.o \
 	text/text.o \
 	text/text_lok.o \
 	text/text_hof.o \
@@ -132,7 +132,8 @@ MODULE_OBJS += \
 	sound/sound_pc98_eob.o \
 	sound/sound_towns_darkmoon.o \
 	sound/drivers/audiomaster2.o \
-	sound/drivers/mlalf98.o
+	sound/drivers/mlalf98.o \
+	sound/drivers/pcspeaker_v1.o
 endif
 
 # This module can be built as a plugin
diff --git a/engines/kyra/resource/staticres.cpp b/engines/kyra/resource/staticres.cpp
index 61adcd9..9f13b71 100644
--- a/engines/kyra/resource/staticres.cpp
+++ b/engines/kyra/resource/staticres.cpp
@@ -39,7 +39,7 @@
 
 namespace Kyra {
 
-#define RESFILE_VERSION 95
+#define RESFILE_VERSION 96
 
 namespace {
 bool checkKyraDat(Common::SeekableReadStream *file) {
diff --git a/engines/kyra/sound/drivers/adlib.cpp b/engines/kyra/sound/drivers/adlib.cpp
index 0a1d277..a576eb2 100644
--- a/engines/kyra/sound/drivers/adlib.cpp
+++ b/engines/kyra/sound/drivers/adlib.cpp
@@ -36,16 +36,348 @@
  *
  */
 
+ // Basic AdLib Programming:
+ // https://web.archive.org/web/20050322080425/http://www.gamedev.net/reference/articles/article446.asp
 
-#include "kyra/sound/drivers/adlib.h"
+#include "kyra/sound/drivers/pc_base.h"
 #include "audio/fmopl.h"
-
+#include "common/mutex.h"
 
 #define CALLBACKS_PER_SECOND 72
 
 namespace Kyra {
 
-AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) {
+class AdLibDriver : public PCSoundDriver {
+public:
+	AdLibDriver(Audio::Mixer *mixer, int version);
+	virtual ~AdLibDriver();
+
+	virtual void initDriver() override;
+	virtual void setSoundData(uint8 *data, uint32 size) override;
+	virtual void queueTrack(int track, int volume) override;
+	virtual bool isChannelPlaying(int channel) const override;
+	virtual void stopAllChannels() override;
+	virtual int getSoundTrigger() const override { return _soundTrigger; }
+	virtual void resetSoundTrigger() override { _soundTrigger = 0; }
+
+	virtual void callback() override;
+
+	virtual void setSyncJumpMask(uint16 mask) override { _syncJumpMask = mask; }
+
+	virtual void setMusicVolume(uint8 volume) override;
+	virtual void setSfxVolume(uint8 volume) override;
+
+private:
+	// These variables have not yet been named, but some of them are partly
+	// known nevertheless:
+	//
+	// pitchBend - Sound-related. Possibly some sort of pitch bend.
+	// unk18 - Sound-effect. Used for secondaryEffect1()
+	// unk19 - Sound-effect. Used for secondaryEffect1()
+	// unk20 - Sound-effect. Used for secondaryEffect1()
+	// unk21 - Sound-effect. Used for secondaryEffect1()
+	// unk22 - Sound-effect. Used for secondaryEffect1()
+	// unk29 - Sound-effect. Used for primaryEffect1()
+	// unk30 - Sound-effect. Used for primaryEffect1()
+	// unk31 - Sound-effect. Used for primaryEffect1()
+	// unk32 - Sound-effect. Used for primaryEffect2()
+	// unk33 - Sound-effect. Used for primaryEffect2()
+	// unk34 - Sound-effect. Used for primaryEffect2()
+	// unk35 - Sound-effect. Used for primaryEffect2()
+	// unk36 - Sound-effect. Used for primaryEffect2()
+	// unk37 - Sound-effect. Used for primaryEffect2()
+	// unk38 - Sound-effect. Used for primaryEffect2()
+	// unk39 - Currently unused, except for updateCallback56()
+	// unk40 - Currently unused, except for updateCallback56()
+	// unk41 - Sound-effect. Used for primaryEffect2()
+
+	struct Channel {
+		bool lock;	// New to ScummVM
+		uint8 opExtraLevel2;
+		const uint8 *dataptr;
+		uint8 duration;
+		uint8 repeatCounter;
+		int8 baseOctave;
+		uint8 priority;
+		uint8 dataptrStackPos;
+		const uint8 *dataptrStack[4];
+		int8 baseNote;
+		uint8 unk29;
+		uint8 unk31;
+		uint16 unk30;
+		uint16 unk37;
+		uint8 unk33;
+		uint8 unk34;
+		uint8 unk35;
+		uint8 unk36;
+		uint8 unk32;
+		uint8 unk41;
+		uint8 unk38;
+		uint8 opExtraLevel1;
+		uint8 spacing2;
+		uint8 baseFreq;
+		uint8 tempo;
+		uint8 position;
+		uint8 regAx;
+		uint8 regBx;
+		typedef void (AdLibDriver::*Callback)(Channel&);
+		Callback primaryEffect;
+		Callback secondaryEffect;
+		uint8 fractionalSpacing;
+		uint8 opLevel1;
+		uint8 opLevel2;
+		uint8 opExtraLevel3;
+		uint8 twoChan;
+		uint8 unk39;
+		uint8 unk40;
+		uint8 spacing1;
+		uint8 durationRandomness;
+		uint8 unk19;
+		uint8 unk18;
+		int8 unk20;
+		int8 unk21;
+		uint8 unk22;
+		uint16 offset;
+		uint8 tempoReset;
+		uint8 rawNote;
+		int8 pitchBend;
+		uint8 volumeModifier;
+	};
+
+	void primaryEffect1(Channel &channel);
+	void primaryEffect2(Channel &channel);
+	void secondaryEffect1(Channel &channel);
+
+	void resetAdLibState();
+	void writeOPL(byte reg, byte val);
+	void initChannel(Channel &channel);
+	void noteOff(Channel &channel);
+	void unkOutput2(uint8 num);
+
+	uint16 getRandomNr();
+	void setupDuration(uint8 duration, Channel &channel);
+
+	void setupNote(uint8 rawNote, Channel &channel, bool flag = false);
+	void setupInstrument(uint8 regOffset, const uint8 *dataptr, Channel &channel);
+	void noteOn(Channel &channel);
+
+	void adjustVolume(Channel &channel);
+
+	uint8 calculateOpLevel1(Channel &channel);
+	uint8 calculateOpLevel2(Channel &channel);
+
+	uint16 checkValue(int16 val) {
+		if (val < 0)
+			val = 0;
+		else if (val > 0x3F)
+			val = 0x3F;
+		return val;
+	}
+
+	// The sound data has at least two lookup tables:
+	//
+	// * One for programs, starting at offset 0.
+	// * One for instruments, starting at offset 500.
+
+	uint8 *getProgram(int progId) {
+		const uint16 offset = READ_LE_UINT16(_soundData + 2 * progId);
+
+		// In case an invalid offset is specified we return nullptr to
+		// indicate an error. 0xFFFF seems to indicate "this is not a valid
+		// program/instrument". However, 0 is also invalid because it points
+		// inside the offset table itself. We also ignore any offsets outside
+		// of the actual data size.
+		// The original does not contain any safety checks and will simply
+		// read outside of the valid sound data in case an invalid offset is
+		// encountered.
+		if (offset == 0 || offset >= _soundDataSize) {
+			return nullptr;
+		} else {
+			return _soundData + offset;
+		}
+	}
+
+	const uint8 *getInstrument(int instrumentId) {
+		return getProgram(_numPrograms + instrumentId);
+	}
+
+	void setupPrograms();
+	void executePrograms();
+
+	struct ParserOpcode {
+		typedef int (AdLibDriver::*POpcode)(const uint8 *&dataptr, Channel &channel, uint8 value);
+		POpcode function;
+		const char *name;
+	};
+
+	void setupParserOpcodeTable();
+	const ParserOpcode *_parserOpcodeTable;
+	int _parserOpcodeTableSize;
+
+	int update_setRepeat(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_checkRepeat(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setupProgram(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_jump(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_jumpToSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_returnFromSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setBaseOctave(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_stopChannel(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_playRest(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_writeAdLib(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setupNoteAndDuration(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setBaseNote(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setupSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_stopOtherChannel(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_waitForEndOfProgram(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setupInstrument(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setupPrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_removePrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setBaseFreq(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setupPrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setPriority(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int updateCallback23(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int updateCallback24(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setupDuration(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_playNote(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setFractionalNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_removeSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setExtraLevel3(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_changeExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setAMDepth(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setVibratoDepth(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_changeExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int updateCallback38(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int updateCallback39(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_removePrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_pitchBend(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_resetToGlobalTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_nop(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setDurationRandomness(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_changeChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int updateCallback46(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setupRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_playRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_removeRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int updateCallback51(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int updateCallback52(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int updateCallback53(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setSoundTrigger(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int update_setTempoReset(const uint8 *&dataptr, Channel &channel, uint8 value);
+	int updateCallback56(const uint8 *&dataptr, Channel &channel, uint8 value);
+private:
+	// These variables have not yet been named, but some of them are partly
+	// known nevertheless:
+	//
+	// _unkValue1      - Unknown. Used for updating _unkValue2
+	// _unkValue2      - Unknown. Used for updating _unkValue4
+	// _unkValue4      - Unknown. Used for updating _unkValue5
+	// _unkValue5      - Unknown. Used for controlling updateCallback24().
+	// _unkValue6      - Unknown. Rhythm section volume?
+	// _unkValue7      - Unknown. Rhythm section volume?
+	// _unkValue8      - Unknown. Rhythm section volume?
+	// _unkValue9      - Unknown. Rhythm section volume?
+	// _unkValue10     - Unknown. Rhythm section volume?
+	// _unkValue11     - Unknown. Rhythm section volume?
+	// _unkValue12     - Unknown. Rhythm section volume?
+	// _unkValue13     - Unknown. Rhythm section volume?
+	// _unkValue14     - Unknown. Rhythm section volume?
+	// _unkValue15     - Unknown. Rhythm section volume?
+	// _unkValue16     - Unknown. Rhythm section volume?
+	// _unkValue17     - Unknown. Rhythm section volume?
+	// _unkValue18     - Unknown. Rhythm section volume?
+	// _unkValue19     - Unknown. Rhythm section volume?
+	// _unkValue20     - Unknown. Rhythm section volume?
+	// _freqTable[]     - Probably frequences for the 12-tone scale.
+	// _unkTable2[]    - Unknown. Currently only used by updateCallback46()
+	// _unkTable2_1[]  - One of the tables in _unkTable2[]
+	// _unkTable2_2[]  - One of the tables in _unkTable2[]
+	// _unkTable2_3[]  - One of the tables in _unkTable2[]
+
+	int _curChannel;
+	uint8 _soundTrigger;
+
+	uint16 _rnd;
+
+	uint8 _unkValue1;
+	uint8 _unkValue2;
+	uint8 _callbackTimer;
+	uint8 _unkValue4;
+	uint8 _unkValue5;
+	uint8 _unkValue6;
+	uint8 _unkValue7;
+	uint8 _unkValue8;
+	uint8 _unkValue9;
+	uint8 _unkValue10;
+	uint8 _unkValue11;
+	uint8 _unkValue12;
+	uint8 _unkValue13;
+	uint8 _unkValue14;
+	uint8 _unkValue15;
+	uint8 _unkValue16;
+	uint8 _unkValue17;
+	uint8 _unkValue18;
+	uint8 _unkValue19;
+	uint8 _unkValue20;
+
+	OPL::OPL *_adlib;
+
+	uint8 *_soundData;
+	uint32 _soundDataSize;
+
+	struct QueueEntry {
+		QueueEntry() : data(0), id(0), volume(0) {}
+		QueueEntry(uint8 *ptr, uint8 track, uint8 vol) : data(ptr), id(track), volume(vol) {}
+		uint8 *data;
+		uint8 id;
+		uint8 volume;
+	};
+
+	QueueEntry _programQueue[16];
+	int _programStartTimeout;
+	int _programQueueStart, _programQueueEnd;
+	bool _retrySounds;
+
+	void adjustSfxData(uint8 *data, int volume);
+	uint8 *_sfxPointer;
+	int _sfxPriority;
+	int _sfxVelocity;
+
+	Channel _channels[10];
+
+	uint8 _vibratoAndAMDepthBits;
+	uint8 _rhythmSectionBits;
+
+	uint8 _curRegOffset;
+	uint8 _tempo;
+
+	const uint8 *_tablePtr1;
+	const uint8 *_tablePtr2;
+
+	static const uint8 _regOffset[];
+	static const uint16 _freqTable[];
+	static const uint8 *const _unkTable2[];
+	static const uint8 _unkTable2_1[];
+	static const uint8 _unkTable2_2[];
+	static const uint8 _unkTable2_3[];
+	static const uint8 _pitchBendTables[][32];
+
+	uint16 _syncJumpMask;
+
+	Common::Mutex _mutex;
+	Audio::Mixer *_mixer;
+
+	uint8 _musicVolume, _sfxVolume;
+
+	int _numPrograms;
+	int _version;
+};
+
+AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) : PCSoundDriver() {
 	setupParserOpcodeTable();
 
 	_version = version;
@@ -1961,6 +2293,10 @@ const uint8 AdLibDriver::_pitchBendTables[][32] = {
 	  0x36, 0x37, 0x39, 0x3B, 0x3E, 0x41, 0x44, 0x47 }
 };
 
+PCSoundDriver *PCSoundDriver::createAdLib(Audio::Mixer *mixer, int version) {
+	return new AdLibDriver(mixer, version);
+}
+
 } // End of namespace Kyra
 
 #undef CALLBACKS_PER_SECOND
diff --git a/engines/kyra/sound/drivers/adlib.h b/engines/kyra/sound/drivers/adlib.h
deleted file mode 100644
index 2bdddb7..0000000
--- a/engines/kyra/sound/drivers/adlib.h
+++ /dev/null
@@ -1,392 +0,0 @@
-/* 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.
- *
- * LGPL License
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- *
- */
-
-
-#ifndef KYRA_SOUND_ADLIBDRIVER_H
-#define KYRA_SOUND_ADLIBDRIVER_H
-
-#include "kyra/resource/resource.h"
-#include "common/mutex.h"
-
-// Basic AdLib Programming:
-// https://web.archive.org/web/20050322080425/http://www.gamedev.net/reference/articles/article446.asp
-
-
-namespace Audio {
-class Mixer;
-}
-
-namespace OPL {
-class OPL;
-}
-
-namespace Kyra {
-
-class AdLibDriver {
-public:
-	AdLibDriver(Audio::Mixer *mixer, int version);
-	~AdLibDriver();
-
-	void initDriver();
-	void setSoundData(uint8 *data, uint32 size);
-	void queueTrack(int track, int volume);
-	bool isChannelPlaying(int channel) const;
-	void stopAllChannels();
-	int getSoundTrigger() const { return _soundTrigger; }
-	void resetSoundTrigger() { _soundTrigger = 0; }
-
-	void callback();
-
-	void setSyncJumpMask(uint16 mask) { _syncJumpMask = mask; }
-
-	void setMusicVolume(uint8 volume);
-	void setSfxVolume(uint8 volume);
-
-private:
-	// These variables have not yet been named, but some of them are partly
-	// known nevertheless:
-	//
-	// pitchBend - Sound-related. Possibly some sort of pitch bend.
-	// unk18 - Sound-effect. Used for secondaryEffect1()
-	// unk19 - Sound-effect. Used for secondaryEffect1()
-	// unk20 - Sound-effect. Used for secondaryEffect1()
-	// unk21 - Sound-effect. Used for secondaryEffect1()
-	// unk22 - Sound-effect. Used for secondaryEffect1()
-	// unk29 - Sound-effect. Used for primaryEffect1()
-	// unk30 - Sound-effect. Used for primaryEffect1()
-	// unk31 - Sound-effect. Used for primaryEffect1()
-	// unk32 - Sound-effect. Used for primaryEffect2()
-	// unk33 - Sound-effect. Used for primaryEffect2()
-	// unk34 - Sound-effect. Used for primaryEffect2()
-	// unk35 - Sound-effect. Used for primaryEffect2()
-	// unk36 - Sound-effect. Used for primaryEffect2()
-	// unk37 - Sound-effect. Used for primaryEffect2()
-	// unk38 - Sound-effect. Used for primaryEffect2()
-	// unk39 - Currently unused, except for updateCallback56()
-	// unk40 - Currently unused, except for updateCallback56()
-	// unk41 - Sound-effect. Used for primaryEffect2()
-
-	struct Channel {
-		bool lock;	// New to ScummVM
-		uint8 opExtraLevel2;
-		const uint8 *dataptr;
-		uint8 duration;
-		uint8 repeatCounter;
-		int8 baseOctave;
-		uint8 priority;
-		uint8 dataptrStackPos;
-		const uint8 *dataptrStack[4];
-		int8 baseNote;
-		uint8 unk29;
-		uint8 unk31;
-		uint16 unk30;
-		uint16 unk37;
-		uint8 unk33;
-		uint8 unk34;
-		uint8 unk35;
-		uint8 unk36;
-		uint8 unk32;
-		uint8 unk41;
-		uint8 unk38;
-		uint8 opExtraLevel1;
-		uint8 spacing2;
-		uint8 baseFreq;
-		uint8 tempo;
-		uint8 position;
-		uint8 regAx;
-		uint8 regBx;
-		typedef void (AdLibDriver::*Callback)(Channel&);
-		Callback primaryEffect;
-		Callback secondaryEffect;
-		uint8 fractionalSpacing;
-		uint8 opLevel1;
-		uint8 opLevel2;
-		uint8 opExtraLevel3;
-		uint8 twoChan;
-		uint8 unk39;
-		uint8 unk40;
-		uint8 spacing1;
-		uint8 durationRandomness;
-		uint8 unk19;
-		uint8 unk18;
-		int8 unk20;
-		int8 unk21;
-		uint8 unk22;
-		uint16 offset;
-		uint8 tempoReset;
-		uint8 rawNote;
-		int8 pitchBend;
-		uint8 volumeModifier;
-	};
-
-	void primaryEffect1(Channel &channel);
-	void primaryEffect2(Channel &channel);
-	void secondaryEffect1(Channel &channel);
-
-	void resetAdLibState();
-	void writeOPL(byte reg, byte val);
-	void initChannel(Channel &channel);
-	void noteOff(Channel &channel);
-	void unkOutput2(uint8 num);
-
-	uint16 getRandomNr();
-	void setupDuration(uint8 duration, Channel &channel);
-
-	void setupNote(uint8 rawNote, Channel &channel, bool flag = false);
-	void setupInstrument(uint8 regOffset, const uint8 *dataptr, Channel &channel);
-	void noteOn(Channel &channel);
-
-	void adjustVolume(Channel &channel);
-
-	uint8 calculateOpLevel1(Channel &channel);
-	uint8 calculateOpLevel2(Channel &channel);
-
-	uint16 checkValue(int16 val) {
-		if (val < 0)
-			val = 0;
-		else if (val > 0x3F)
-			val = 0x3F;
-		return val;
-	}
-
-	// The sound data has at least two lookup tables:
-	//
-	// * One for programs, starting at offset 0.
-	// * One for instruments, starting at offset 500.
-
-	uint8 *getProgram(int progId) {
-		const uint16 offset = READ_LE_UINT16(_soundData + 2 * progId);
-
-		// In case an invalid offset is specified we return nullptr to
-		// indicate an error. 0xFFFF seems to indicate "this is not a valid
-		// program/instrument". However, 0 is also invalid because it points
-		// inside the offset table itself. We also ignore any offsets outside
-		// of the actual data size.
-		// The original does not contain any safety checks and will simply
-		// read outside of the valid sound data in case an invalid offset is
-		// encountered.
-		if (offset == 0 || offset >= _soundDataSize) {
-			return nullptr;
-		} else {
-			return _soundData + offset;
-		}
-	}
-
-	const uint8 *getInstrument(int instrumentId) {
-		return getProgram(_numPrograms + instrumentId);
-	}
-
-	void setupPrograms();
-	void executePrograms();
-
-	struct ParserOpcode {
-		typedef int (AdLibDriver::*POpcode)(const uint8 *&dataptr, Channel &channel, uint8 value);
-		POpcode function;
-		const char *name;
-	};
-
-	void setupParserOpcodeTable();
-	const ParserOpcode *_parserOpcodeTable;
-	int _parserOpcodeTableSize;
-
-	int update_setRepeat(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_checkRepeat(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setupProgram(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_jump(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_jumpToSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_returnFromSubroutine(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setBaseOctave(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_stopChannel(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_playRest(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_writeAdLib(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setupNoteAndDuration(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setBaseNote(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setupSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_stopOtherChannel(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_waitForEndOfProgram(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setupInstrument(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setupPrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_removePrimaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setBaseFreq(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setupPrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setPriority(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int updateCallback23(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int updateCallback24(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setupDuration(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_playNote(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setFractionalNoteSpacing(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_removeSecondaryEffect1(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setExtraLevel3(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_changeExtraLevel2(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setAMDepth(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setVibratoDepth(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_changeExtraLevel1(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int updateCallback38(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int updateCallback39(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_removePrimaryEffect2(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_pitchBend(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_resetToGlobalTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_nop(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setDurationRandomness(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_changeChannelTempo(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int updateCallback46(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setupRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_playRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_removeRhythmSection(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int updateCallback51(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int updateCallback52(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int updateCallback53(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setSoundTrigger(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int update_setTempoReset(const uint8 *&dataptr, Channel &channel, uint8 value);
-	int updateCallback56(const uint8 *&dataptr, Channel &channel, uint8 value);
-private:
-	// These variables have not yet been named, but some of them are partly
-	// known nevertheless:
-	//
-	// _unkValue1      - Unknown. Used for updating _unkValue2
-	// _unkValue2      - Unknown. Used for updating _unkValue4
-	// _unkValue4      - Unknown. Used for updating _unkValue5
-	// _unkValue5      - Unknown. Used for controlling updateCallback24().
-	// _unkValue6      - Unknown. Rhythm section volume?
-	// _unkValue7      - Unknown. Rhythm section volume?
-	// _unkValue8      - Unknown. Rhythm section volume?
-	// _unkValue9      - Unknown. Rhythm section volume?
-	// _unkValue10     - Unknown. Rhythm section volume?
-	// _unkValue11     - Unknown. Rhythm section volume?
-	// _unkValue12     - Unknown. Rhythm section volume?
-	// _unkValue13     - Unknown. Rhythm section volume?
-	// _unkValue14     - Unknown. Rhythm section volume?
-	// _unkValue15     - Unknown. Rhythm section volume?
-	// _unkValue16     - Unknown. Rhythm section volume?
-	// _unkValue17     - Unknown. Rhythm section volume?
-	// _unkValue18     - Unknown. Rhythm section volume?
-	// _unkValue19     - Unknown. Rhythm section volume?
-	// _unkValue20     - Unknown. Rhythm section volume?
-	// _freqTable[]     - Probably frequences for the 12-tone scale.
-	// _unkTable2[]    - Unknown. Currently only used by updateCallback46()
-	// _unkTable2_1[]  - One of the tables in _unkTable2[]
-	// _unkTable2_2[]  - One of the tables in _unkTable2[]
-	// _unkTable2_3[]  - One of the tables in _unkTable2[]
-
-	int _curChannel;
-	uint8 _soundTrigger;
-
-	uint16 _rnd;
-
-	uint8 _unkValue1;
-	uint8 _unkValue2;
-	uint8 _callbackTimer;
-	uint8 _unkValue4;
-	uint8 _unkValue5;
-	uint8 _unkValue6;
-	uint8 _unkValue7;
-	uint8 _unkValue8;
-	uint8 _unkValue9;
-	uint8 _unkValue10;
-	uint8 _unkValue11;
-	uint8 _unkValue12;
-	uint8 _unkValue13;
-	uint8 _unkValue14;
-	uint8 _unkValue15;
-	uint8 _unkValue16;
-	uint8 _unkValue17;
-	uint8 _unkValue18;
-	uint8 _unkValue19;
-	uint8 _unkValue20;
-
-	OPL::OPL *_adlib;
-
-	uint8 *_soundData;
-	uint32 _soundDataSize;
-
-	struct QueueEntry {
-		QueueEntry() : data(0), id(0), volume(0) {}
-		QueueEntry(uint8 *ptr, uint8 track, uint8 vol) : data(ptr), id(track), volume(vol) {}
-		uint8 *data;
-		uint8 id;
-		uint8 volume;
-	};
-
-	QueueEntry _programQueue[16];
-	int _programStartTimeout;
-	int _programQueueStart, _programQueueEnd;
-	bool _retrySounds;
-
-	void adjustSfxData(uint8 *data, int volume);
-	uint8 *_sfxPointer;
-	int _sfxPriority;
-	int _sfxVelocity;
-
-	Channel _channels[10];
-
-	uint8 _vibratoAndAMDepthBits;
-	uint8 _rhythmSectionBits;
-
-	uint8 _curRegOffset;
-	uint8 _tempo;
-
-	const uint8 *_tablePtr1;
-	const uint8 *_tablePtr2;
-
-	static const uint8 _regOffset[];
-	static const uint16 _freqTable[];
-	static const uint8 *const _unkTable2[];
-	static const uint8 _unkTable2_1[];
-	static const uint8 _unkTable2_2[];
-	static const uint8 _unkTable2_3[];
-	static const uint8 _pitchBendTables[][32];
-
-	uint16 _syncJumpMask;
-
-	Common::Mutex _mutex;
-	Audio::Mixer *_mixer;
-
-	uint8 _musicVolume, _sfxVolume;
-
-	int _numPrograms;
-	int _version;
-};
-
-} // End of namespace Kyra
-
-#endif
diff --git a/engines/kyra/sound/drivers/pc_base.h b/engines/kyra/sound/drivers/pc_base.h
new file mode 100644
index 0000000..b13b4a2
--- /dev/null
+++ b/engines/kyra/sound/drivers/pc_base.h
@@ -0,0 +1,64 @@
+/* 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 KYRA_SOUND_PCDRIVER_H
+#define KYRA_SOUND_PCDRIVER_H
+
+#include "kyra/resource/resource.h"
+
+namespace Audio {
+	class Mixer;
+}
+
+namespace Kyra {
+
+class PCSoundDriver {
+public:
+	PCSoundDriver() {}
+	virtual ~PCSoundDriver() {}
+
+	virtual void initDriver() = 0;
+	virtual void setSoundData(uint8 *data, uint32 size) = 0;
+	virtual void queueTrack(int track, int volume) = 0;
+	virtual bool isChannelPlaying(int channel) const = 0;
+	virtual void stopAllChannels() = 0;
+	virtual int getSoundTrigger() const = 0;
+	virtual void resetSoundTrigger() = 0;
+
+	virtual void callback() = 0;
+
+	// AdLiB specific
+	virtual void setSyncJumpMask(uint16) {}
+
+	virtual void setMusicVolume(uint8 volume) = 0;
+	virtual void setSfxVolume(uint8 volume) = 0;
+
+	static PCSoundDriver *createAdLib(Audio::Mixer *mixer, int version);
+#ifdef ENABLE_EOB
+	static PCSoundDriver *createPCSpk(Audio::Mixer *mixer);
+#endif
+};
+
+} // End of namespace Kyra
+
+#endif
diff --git a/engines/kyra/sound/drivers/pcspeaker.cpp b/engines/kyra/sound/drivers/pcspeaker.cpp
deleted file mode 100644
index 110adde..0000000
--- a/engines/kyra/sound/drivers/pcspeaker.cpp
+++ /dev/null
@@ -1,366 +0,0 @@
-/* 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 "kyra/sound/sound_intern.h"
-
-#include "audio/mixer.h"
-#include "audio/softsynth/pcspk.h"
-
-namespace Kyra {
-
-MidiDriver_PCSpeaker::MidiDriver_PCSpeaker(Audio::Mixer *mixer)
-	: MidiDriver_Emulated(mixer), _rate(mixer->getOutputRate()) {
-	_timerValue = 0;
-	memset(_channel, 0, sizeof(_channel));
-	memset(_note, 0, sizeof(_note));
-
-	for (int i = 0; i < 2; ++i)
-		_note[i].hardwareChannel = 0xFF;
-
-	_speaker = new Audio::PCSpeaker(_rate);
-	assert(_speaker);
-	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
-
-	_countdown = 0xFFFF;
-	_hardwareChannel[0] = 0xFF;
-	_modulationFlag = false;
-}
-
-MidiDriver_PCSpeaker::~MidiDriver_PCSpeaker() {
-	_mixer->stopHandle(_mixerSoundHandle);
-	delete _speaker;
-	_speaker = 0;
-}
-
-void MidiDriver_PCSpeaker::send(uint32 data) {
-	Common::StackLock lock(_mutex);
-
-	uint8 channel = data & 0x0F;
-	uint8 param1 = (data >>  8) & 0xFF;
-	uint8 param2 = (data >> 16) & 0xFF;
-
-	uint8 flags = 0x00;
-
-	if (channel > 1)
-		return;
-
-	switch (data & 0xF0) {
-	case 0x80:	// note off
-		noteOff(channel, param1);
-		return;
-
-	case 0x90:	// note on
-		if (channel > 1)
-			return;
-
-		if (param2)
-			noteOn(channel, param1);
-		else
-			noteOff(channel, param1);
-		return;
-
-	case 0xB0:	// controller
-		switch (param1) {
-		case 0x01:	// modulation
-			_channel[channel].modulation = param2;
-			break;
-
-		case 0x40:	// hold
-			_channel[channel].hold = param2;
-			if (param2 < 0x40)
-				resetController(channel);
-			return;
-
-		case 0x70:	// voice protect
-			_channel[channel].voiceProtect = param2;
-			return;
-
-		case 0x79:	// all notes off
-			_channel[channel].hold = 0;
-			resetController(channel);
-			_channel[channel].modulation = 0;
-			_channel[channel].pitchBendLow = 0;
-			_channel[channel].pitchBendHigh = 0x40;
-			flags = 0x01;
-			break;
-
-		default:
-			return;
-		}
-		break;
-
-	case 0xE0:	// pitch bend
-		flags = 0x01;
-		_channel[channel].pitchBendLow = param1;
-		_channel[channel].pitchBendHigh = param2;
-		break;
-
-	default:
-		return;
-	}
-
-	for (int i = 0; i < 2; ++i) {
-		if (_note[i].enabled && _note[i].midiChannel == channel) {
-			_note[i].flags |= flags;
-			setupTone(i);
-		}
-	}
-}
-
-void MidiDriver_PCSpeaker::resetController(int channel) {
-	for (int i = 0; i < 2; ++i) {
-		if (_note[i].enabled && _note[i].midiChannel == channel && _note[i].processHold)
-			noteOff(channel, _note[i].note);
-	}
-}
-
-void MidiDriver_PCSpeaker::noteOn(int channel, int note) {
-	int n = 0;
-
-	while (n < 2 && _note[n].enabled)
-		++n;
-
-	if (n >= 2)
-		return;
-
-	_note[n].midiChannel = channel;
-	_note[n].note = note;
-	_note[n].enabled = true;
-	_note[n].processHold = false;
-	_note[n].hardwareFlags = 0x20;
-	_note[n].priority = 0x7FFF;
-	_note[n].flags = 0x01;
-
-	turnNoteOn(n);
-}
-
-void MidiDriver_PCSpeaker::turnNoteOn(int note) {
-	if (_hardwareChannel[0] == 0xFF) {
-		_note[note].hardwareChannel = 0;
-		++_channel[_note[note].midiChannel].noteCount;
-		_hardwareChannel[0] = _note[note].midiChannel;
-		_note[note].flags = 0x01;
-
-		setupTone(note);
-	} else {
-		overwriteNote(note);
-	}
-}
-
-void MidiDriver_PCSpeaker::overwriteNote(int note) {
-	int totalNotes = 0;
-	for (int i = 0; i < 2; ++i) {
-		if (_note[i].enabled) {
-			++totalNotes;
-			const int channel = _note[i].midiChannel;
-
-			uint16 priority = 0xFFFF;
-			if (_channel[channel].voiceProtect < 0x40)
-				priority = _note[i].priority;
-
-			if (_channel[channel].noteCount > priority)
-				priority = 0;
-			else
-				priority -= _channel[channel].noteCount;
-
-			_note[i].precedence = priority;
-		}
-	}
-
-	if (totalNotes <= 1)
-		return;
-
-	do {
-		uint16 maxValue = 0;
-		uint16 minValue = 0xFFFF;
-		int newNote = 0;
-
-		for (int i = 0; i < 2; ++i) {
-			if (_note[i].enabled) {
-				if (_note[i].hardwareChannel == 0xFF) {
-					if (_note[i].precedence >= maxValue) {
-						maxValue = _note[i].precedence;
-						newNote = i;
-					}
-				} else {
-					if (_note[i].precedence <= minValue) {
-						minValue = _note[i].precedence;
-						note = i;
-					}
-				}
-			}
-		}
-
-		if (maxValue < minValue)
-			return;
-
-		turnNoteOff(_note[note].hardwareChannel);
-		_note[note].enabled = false;
-
-		_note[newNote].hardwareChannel = _note[note].hardwareChannel;
-		++_channel[_note[newNote].midiChannel].noteCount;
-		_hardwareChannel[_note[note].hardwareChannel] = _note[newNote].midiChannel;
-		_note[newNote].flags = 0x01;
-
-		setupTone(newNote);
-	} while (--totalNotes);
-}
-
-void MidiDriver_PCSpeaker::noteOff(int channel, int note) {
-	for (int i = 0; i < 2; ++i) {
-		if (_note[i].enabled && _note[i].note == note && _note[i].midiChannel == channel) {
-			if (_channel[i].hold < 0x40) {
-				turnNoteOff(i);
-				_note[i].enabled = false;
-			} else {
-				_note[i].processHold = true;
-			}
-		}
-	}
-}
-
-void MidiDriver_PCSpeaker::turnNoteOff(int note) {
-	if (_note[note].hardwareChannel != 0xFF) {
-		_note[note].hardwareFlags &= 0xDF;
-		_note[note].flags |= 1;
-
-		setupTone(note);
-
-		--_channel[_note[note].midiChannel].noteCount;
-
-		_hardwareChannel[_note[note].hardwareChannel] = 0xFF;
-		_note[note].hardwareChannel = 0xFF;
-	}
-}
-
-void MidiDriver_PCSpeaker::setupTone(int note) {
-	if (_note[note].hardwareChannel == 0xFF)
-		return;
-
-	if (!(_note[note].flags & 0x01))
-		return;
-
-	if (!(_note[note].hardwareFlags & 0x20)) {
-		_speaker->stop();
-	} else {
-		const int midiChannel = _note[note].midiChannel;
-		uint16 pitchBend = (_channel[midiChannel].pitchBendHigh << 7) | _channel[midiChannel].pitchBendLow;
-
-		int noteValue = _note[note].note;
-
-		noteValue -= 24;
-		do {
-			noteValue += 12;
-		} while (noteValue < 0);
-
-		noteValue += 12;
-		do {
-			noteValue -= 12;
-		} while (noteValue > 95);
-
-		int16 modulation = _note[note].modulation;
-
-		int tableIndex = MAX(noteValue - 12, 0);
-		uint16 note1 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex];
-		tableIndex = MIN(noteValue + 12, 95);
-		uint16 note2 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex];
-		uint16 note3 = (_noteTable2[noteValue] << 8) | _noteTable1[noteValue];
-
-		int32 countdown = pitchBend - 0x2000;
-		countdown += modulation;
-
-		if (countdown >= 0)
-			countdown *= (note2 - note3);
-		else
-			countdown *= (note3 - note1);
-
-		countdown /= 0x2000;
-		countdown += note3;
-
-		countdown = uint16(countdown & 0xFFFF);
-		if (countdown != _countdown)
-			_countdown = countdown;
-
-		_speaker->play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / _countdown, -1);
-	}
-
-	_note[note].flags &= 0xFE;
-}
-
-void MidiDriver_PCSpeaker::generateSamples(int16 *buffer, int numSamples) {
-	Common::StackLock lock(_mutex);
-	_speaker->readBuffer(buffer, numSamples);
-}
-
-void MidiDriver_PCSpeaker::onTimer() {
-	/*Common::StackLock lock(_mutex);
-
-	_timerValue += 20;
-	if (_timerValue < 120)
-		return;
-	_timerValue -= 120;
-
-	_modulationFlag = !_modulationFlag;
-	for (int i = 0; i < 2; ++i) {
-		if (_note[i].enabled) {
-			uint16 modValue = 5 * _channel[_note[i].midiChannel].modulation;
-			if (_modulationFlag)
-				modValue = -modValue;
-			_note[i].modulation = modValue;
-			_note[i].flags |= 1;
-
-			setupTone(i);
-		}
-	}*/
-}
-
-const uint8 MidiDriver_PCSpeaker::_noteTable1[] = {
-	0x88, 0xB5, 0x4E, 0x40, 0x41, 0xCD, 0xC4, 0x3D,
-	0x43, 0x7C, 0x2A, 0xD6, 0x88, 0xB5, 0xFF, 0xD1,
-	0x20, 0xA7, 0xE2, 0x1E, 0xCE, 0xBE, 0xF2, 0x8A,
-	0x44, 0x41, 0x7F, 0xE8, 0x90, 0x63, 0x63, 0x8F,
-	0xE7, 0x5F, 0x01, 0xBD, 0xA2, 0xA0, 0xBF, 0xF4,
-	0x48, 0xB1, 0x31, 0xC7, 0x70, 0x2F, 0xFE, 0xE0,
-	0xD1, 0xD0, 0xDE, 0xFB, 0x24, 0x58, 0x98, 0xE3,
-	0x39, 0x97, 0xFF, 0x6F, 0xE8, 0x68, 0xEF, 0x7D,
-	0x11, 0xAC, 0x4C, 0xF1, 0x9C, 0x4B, 0xFF, 0xB7,
-	0x74, 0x34, 0xF7, 0xBE, 0x88, 0x56, 0x26, 0xF8,
-	0xCE, 0xA5, 0x7F, 0x5B, 0x3A, 0x1A, 0xFB, 0xDF,
-	0xC4, 0xAB, 0x93, 0x7C, 0x67, 0x52, 0x3F, 0x2D
-};
-
-const uint8 MidiDriver_PCSpeaker::_noteTable2[] = {
-	0x8E, 0x86, 0xFD, 0xF0, 0xE2, 0xD5, 0xC9, 0xBE,
-	0xB3, 0xA9, 0xA0, 0x96, 0x8E, 0x86, 0x7E, 0x77,
-	0x71, 0x6A, 0x64, 0x5F, 0x59, 0x54, 0x4F, 0x4B,
-	0x47, 0x43, 0x3F, 0x3B, 0x38, 0x35, 0x32, 0x2F,
-	0x2C, 0x2A, 0x28, 0x25, 0x23, 0x21, 0x1F, 0x1D,
-	0x1C, 0x1A, 0x19, 0x17, 0x16, 0x15, 0x13, 0x12,
-	0x11, 0x10, 0x0F, 0x0E, 0x0E, 0x0D, 0x0C, 0x0B,
-	0x0B, 0x0A, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07,
-	0x07, 0x06, 0x06, 0x05, 0x05, 0x05, 0x04, 0x04,
-	0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02,
-	0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01,
-	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
-};
-
-} // End of namespace Kyra
diff --git a/engines/kyra/sound/drivers/pcspeaker_v1.cpp b/engines/kyra/sound/drivers/pcspeaker_v1.cpp
new file mode 100644
index 0000000..01dcbab
--- /dev/null
+++ b/engines/kyra/sound/drivers/pcspeaker_v1.cpp
@@ -0,0 +1,99 @@
+/* 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.
+ *
+ */
+
+#ifdef ENABLE_EOB
+
+#include "kyra/sound/drivers/pc_base.h"
+#include "audio/mixer.h"
+#include "audio/softsynth/pcspk.h"
+
+namespace Kyra {
+
+class PCSpeakerDriver : public PCSoundDriver {
+public:
+	PCSpeakerDriver(Audio::Mixer *mixer);
+	virtual ~PCSpeakerDriver();
+
+	virtual void initDriver() override;
+	virtual void setSoundData(uint8 *data, uint32 size) override;
+	virtual void queueTrack(int track, int volume) override;
+	virtual bool isChannelPlaying(int channel) const override;
+	virtual void stopAllChannels() override;
+	virtual int getSoundTrigger() const override { return _soundTrigger; }
+	virtual void resetSoundTrigger() override { _soundTrigger = 0; }
+
+	virtual void callback() override;
+
+	virtual void setMusicVolume(uint8 volume) override;
+	virtual void setSfxVolume(uint8 volume) override;
+
+private:
+	int _soundTrigger;
+};
+
+PCSpeakerDriver::PCSpeakerDriver(Audio::Mixer *mixer) : PCSoundDriver() {
+
+}
+
+PCSpeakerDriver::~PCSpeakerDriver() {
+
+}
+
+void PCSpeakerDriver::initDriver() {
+
+}
+
+void PCSpeakerDriver::setSoundData(uint8 *data, uint32 size) {
+
+}
+
+void PCSpeakerDriver::queueTrack(int track, int volume) {
+
+}
+
+bool PCSpeakerDriver::isChannelPlaying(int channel) const {
+	return true;
+}
+
+void PCSpeakerDriver::stopAllChannels() {
+
+}
+
+void PCSpeakerDriver::callback() {
+
+}
+
+void PCSpeakerDriver::setMusicVolume(uint8 volume) {
+
+}
+
+void PCSpeakerDriver::setSfxVolume(uint8 volume) {
+
+}
+
+PCSoundDriver *PCSoundDriver::createPCSpk(Audio::Mixer *mixer) {
+	return new PCSpeakerDriver(mixer);
+}
+
+} // End of namespace Kyra
+
+#endif
diff --git a/engines/kyra/sound/drivers/pcspeaker_v2.cpp b/engines/kyra/sound/drivers/pcspeaker_v2.cpp
new file mode 100644
index 0000000..110adde
--- /dev/null
+++ b/engines/kyra/sound/drivers/pcspeaker_v2.cpp
@@ -0,0 +1,366 @@
+/* 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 "kyra/sound/sound_intern.h"
+
+#include "audio/mixer.h"
+#include "audio/softsynth/pcspk.h"
+
+namespace Kyra {
+
+MidiDriver_PCSpeaker::MidiDriver_PCSpeaker(Audio::Mixer *mixer)
+	: MidiDriver_Emulated(mixer), _rate(mixer->getOutputRate()) {
+	_timerValue = 0;
+	memset(_channel, 0, sizeof(_channel));
+	memset(_note, 0, sizeof(_note));
+
+	for (int i = 0; i < 2; ++i)
+		_note[i].hardwareChannel = 0xFF;
+
+	_speaker = new Audio::PCSpeaker(_rate);
+	assert(_speaker);
+	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+	_countdown = 0xFFFF;
+	_hardwareChannel[0] = 0xFF;
+	_modulationFlag = false;
+}
+
+MidiDriver_PCSpeaker::~MidiDriver_PCSpeaker() {
+	_mixer->stopHandle(_mixerSoundHandle);
+	delete _speaker;
+	_speaker = 0;
+}
+
+void MidiDriver_PCSpeaker::send(uint32 data) {
+	Common::StackLock lock(_mutex);
+
+	uint8 channel = data & 0x0F;
+	uint8 param1 = (data >>  8) & 0xFF;
+	uint8 param2 = (data >> 16) & 0xFF;
+
+	uint8 flags = 0x00;
+
+	if (channel > 1)
+		return;
+
+	switch (data & 0xF0) {
+	case 0x80:	// note off
+		noteOff(channel, param1);
+		return;
+
+	case 0x90:	// note on
+		if (channel > 1)
+			return;
+
+		if (param2)
+			noteOn(channel, param1);
+		else
+			noteOff(channel, param1);
+		return;
+
+	case 0xB0:	// controller
+		switch (param1) {
+		case 0x01:	// modulation
+			_channel[channel].modulation = param2;
+			break;
+
+		case 0x40:	// hold
+			_channel[channel].hold = param2;
+			if (param2 < 0x40)
+				resetController(channel);
+			return;
+
+		case 0x70:	// voice protect
+			_channel[channel].voiceProtect = param2;
+			return;
+
+		case 0x79:	// all notes off
+			_channel[channel].hold = 0;
+			resetController(channel);
+			_channel[channel].modulation = 0;
+			_channel[channel].pitchBendLow = 0;
+			_channel[channel].pitchBendHigh = 0x40;
+			flags = 0x01;
+			break;
+
+		default:
+			return;
+		}
+		break;
+
+	case 0xE0:	// pitch bend
+		flags = 0x01;
+		_channel[channel].pitchBendLow = param1;
+		_channel[channel].pitchBendHigh = param2;
+		break;
+
+	default:
+		return;
+	}
+
+	for (int i = 0; i < 2; ++i) {
+		if (_note[i].enabled && _note[i].midiChannel == channel) {
+			_note[i].flags |= flags;
+			setupTone(i);
+		}
+	}
+}
+
+void MidiDriver_PCSpeaker::resetController(int channel) {
+	for (int i = 0; i < 2; ++i) {
+		if (_note[i].enabled && _note[i].midiChannel == channel && _note[i].processHold)
+			noteOff(channel, _note[i].note);
+	}
+}
+
+void MidiDriver_PCSpeaker::noteOn(int channel, int note) {
+	int n = 0;
+
+	while (n < 2 && _note[n].enabled)
+		++n;
+
+	if (n >= 2)
+		return;
+
+	_note[n].midiChannel = channel;
+	_note[n].note = note;
+	_note[n].enabled = true;
+	_note[n].processHold = false;
+	_note[n].hardwareFlags = 0x20;
+	_note[n].priority = 0x7FFF;
+	_note[n].flags = 0x01;
+
+	turnNoteOn(n);
+}
+
+void MidiDriver_PCSpeaker::turnNoteOn(int note) {
+	if (_hardwareChannel[0] == 0xFF) {
+		_note[note].hardwareChannel = 0;
+		++_channel[_note[note].midiChannel].noteCount;
+		_hardwareChannel[0] = _note[note].midiChannel;
+		_note[note].flags = 0x01;
+
+		setupTone(note);
+	} else {
+		overwriteNote(note);
+	}
+}
+
+void MidiDriver_PCSpeaker::overwriteNote(int note) {
+	int totalNotes = 0;
+	for (int i = 0; i < 2; ++i) {
+		if (_note[i].enabled) {
+			++totalNotes;
+			const int channel = _note[i].midiChannel;
+
+			uint16 priority = 0xFFFF;
+			if (_channel[channel].voiceProtect < 0x40)
+				priority = _note[i].priority;
+
+			if (_channel[channel].noteCount > priority)
+				priority = 0;
+			else
+				priority -= _channel[channel].noteCount;
+
+			_note[i].precedence = priority;
+		}
+	}
+
+	if (totalNotes <= 1)
+		return;
+
+	do {
+		uint16 maxValue = 0;
+		uint16 minValue = 0xFFFF;
+		int newNote = 0;
+
+		for (int i = 0; i < 2; ++i) {
+			if (_note[i].enabled) {
+				if (_note[i].hardwareChannel == 0xFF) {
+					if (_note[i].precedence >= maxValue) {
+						maxValue = _note[i].precedence;
+						newNote = i;
+					}
+				} else {
+					if (_note[i].precedence <= minValue) {
+						minValue = _note[i].precedence;
+						note = i;
+					}
+				}
+			}
+		}
+
+		if (maxValue < minValue)
+			return;
+
+		turnNoteOff(_note[note].hardwareChannel);
+		_note[note].enabled = false;
+
+		_note[newNote].hardwareChannel = _note[note].hardwareChannel;
+		++_channel[_note[newNote].midiChannel].noteCount;
+		_hardwareChannel[_note[note].hardwareChannel] = _note[newNote].midiChannel;
+		_note[newNote].flags = 0x01;
+
+		setupTone(newNote);
+	} while (--totalNotes);
+}
+
+void MidiDriver_PCSpeaker::noteOff(int channel, int note) {
+	for (int i = 0; i < 2; ++i) {
+		if (_note[i].enabled && _note[i].note == note && _note[i].midiChannel == channel) {
+			if (_channel[i].hold < 0x40) {
+				turnNoteOff(i);
+				_note[i].enabled = false;
+			} else {
+				_note[i].processHold = true;
+			}
+		}
+	}
+}
+
+void MidiDriver_PCSpeaker::turnNoteOff(int note) {
+	if (_note[note].hardwareChannel != 0xFF) {
+		_note[note].hardwareFlags &= 0xDF;
+		_note[note].flags |= 1;
+
+		setupTone(note);
+
+		--_channel[_note[note].midiChannel].noteCount;
+
+		_hardwareChannel[_note[note].hardwareChannel] = 0xFF;
+		_note[note].hardwareChannel = 0xFF;
+	}
+}
+
+void MidiDriver_PCSpeaker::setupTone(int note) {
+	if (_note[note].hardwareChannel == 0xFF)
+		return;
+
+	if (!(_note[note].flags & 0x01))
+		return;
+
+	if (!(_note[note].hardwareFlags & 0x20)) {
+		_speaker->stop();
+	} else {
+		const int midiChannel = _note[note].midiChannel;
+		uint16 pitchBend = (_channel[midiChannel].pitchBendHigh << 7) | _channel[midiChannel].pitchBendLow;
+
+		int noteValue = _note[note].note;
+
+		noteValue -= 24;
+		do {
+			noteValue += 12;
+		} while (noteValue < 0);
+
+		noteValue += 12;
+		do {
+			noteValue -= 12;
+		} while (noteValue > 95);
+
+		int16 modulation = _note[note].modulation;
+
+		int tableIndex = MAX(noteValue - 12, 0);
+		uint16 note1 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex];
+		tableIndex = MIN(noteValue + 12, 95);
+		uint16 note2 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex];
+		uint16 note3 = (_noteTable2[noteValue] << 8) | _noteTable1[noteValue];
+
+		int32 countdown = pitchBend - 0x2000;
+		countdown += modulation;
+
+		if (countdown >= 0)
+			countdown *= (note2 - note3);
+		else
+			countdown *= (note3 - note1);
+
+		countdown /= 0x2000;
+		countdown += note3;
+
+		countdown = uint16(countdown & 0xFFFF);
+		if (countdown != _countdown)
+			_countdown = countdown;
+
+		_speaker->play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / _countdown, -1);
+	}
+
+	_note[note].flags &= 0xFE;
+}
+
+void MidiDriver_PCSpeaker::generateSamples(int16 *buffer, int numSamples) {
+	Common::StackLock lock(_mutex);
+	_speaker->readBuffer(buffer, numSamples);
+}
+
+void MidiDriver_PCSpeaker::onTimer() {
+	/*Common::StackLock lock(_mutex);
+
+	_timerValue += 20;
+	if (_timerValue < 120)
+		return;
+	_timerValue -= 120;
+
+	_modulationFlag = !_modulationFlag;
+	for (int i = 0; i < 2; ++i) {
+		if (_note[i].enabled) {
+			uint16 modValue = 5 * _channel[_note[i].midiChannel].modulation;
+			if (_modulationFlag)
+				modValue = -modValue;
+			_note[i].modulation = modValue;
+			_note[i].flags |= 1;
+
+			setupTone(i);
+		}
+	}*/
+}
+
+const uint8 MidiDriver_PCSpeaker::_noteTable1[] = {
+	0x88, 0xB5, 0x4E, 0x40, 0x41, 0xCD, 0xC4, 0x3D,
+	0x43, 0x7C, 0x2A, 0xD6, 0x88, 0xB5, 0xFF, 0xD1,
+	0x20, 0xA7, 0xE2, 0x1E, 0xCE, 0xBE, 0xF2, 0x8A,
+	0x44, 0x41, 0x7F, 0xE8, 0x90, 0x63, 0x63, 0x8F,
+	0xE7, 0x5F, 0x01, 0xBD, 0xA2, 0xA0, 0xBF, 0xF4,
+	0x48, 0xB1, 0x31, 0xC7, 0x70, 0x2F, 0xFE, 0xE0,
+	0xD1, 0xD0, 0xDE, 0xFB, 0x24, 0x58, 0x98, 0xE3,
+	0x39, 0x97, 0xFF, 0x6F, 0xE8, 0x68, 0xEF, 0x7D,
+	0x11, 0xAC, 0x4C, 0xF1, 0x9C, 0x4B, 0xFF, 0xB7,
+	0x74, 0x34, 0xF7, 0xBE, 0x88, 0x56, 0x26, 0xF8,
+	0xCE, 0xA5, 0x7F, 0x5B, 0x3A, 0x1A, 0xFB, 0xDF,
+	0xC4, 0xAB, 0x93, 0x7C, 0x67, 0x52, 0x3F, 0x2D
+};
+
+const uint8 MidiDriver_PCSpeaker::_noteTable2[] = {
+	0x8E, 0x86, 0xFD, 0xF0, 0xE2, 0xD5, 0xC9, 0xBE,
+	0xB3, 0xA9, 0xA0, 0x96, 0x8E, 0x86, 0x7E, 0x77,
+	0x71, 0x6A, 0x64, 0x5F, 0x59, 0x54, 0x4F, 0x4B,
+	0x47, 0x43, 0x3F, 0x3B, 0x38, 0x35, 0x32, 0x2F,
+	0x2C, 0x2A, 0x28, 0x25, 0x23, 0x21, 0x1F, 0x1D,
+	0x1C, 0x1A, 0x19, 0x17, 0x16, 0x15, 0x13, 0x12,
+	0x11, 0x10, 0x0F, 0x0E, 0x0E, 0x0D, 0x0C, 0x0B,
+	0x0B, 0x0A, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07,
+	0x07, 0x06, 0x06, 0x05, 0x05, 0x05, 0x04, 0x04,
+	0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02,
+	0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01,
+	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
+};
+
+} // End of namespace Kyra
diff --git a/engines/kyra/sound/sound_adlib.cpp b/engines/kyra/sound/sound_adlib.cpp
deleted file mode 100644
index 2e72583..0000000
--- a/engines/kyra/sound/sound_adlib.cpp
+++ /dev/null
@@ -1,257 +0,0 @@
-/* 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 "kyra/sound/sound_intern.h"
-#include "kyra/sound/drivers/adlib.h"
-
-#include "common/system.h"
-#include "common/config-manager.h"
-
-
-namespace Kyra {
-
-// Kyra 1 sound triggers. Most noticeably, these are used towards the end of
-// the game, in the castle, to cycle between different songs. The same music is
-// used in other places throughout the game, but the player is less likely to
-// spend enough time there to notice.
-
-const int SoundAdLibPC::_kyra1SoundTriggers[] = {
-	0, 4, 5, 3
-};
-
-const int SoundAdLibPC::_kyra1NumSoundTriggers = ARRAYSIZE(SoundAdLibPC::_kyra1SoundTriggers);
-
-SoundAdLibPC::SoundAdLibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer)
-	: Sound(vm, mixer), _driver(0), _trackEntries(), _soundDataPtr(0) {
-	memset(_trackEntries, 0, sizeof(_trackEntries));
-
-	_soundTriggers = 0;
-	_numSoundTriggers = 0;
-	_sfxPlayingSound = -1;
-	_soundFileLoaded.clear();
-	_currentResourceSet = 0;
-	memset(&_resInfo, 0, sizeof(_resInfo));
-
-	switch (vm->game()) {
-	case GI_LOL:
-		_version = _vm->gameFlags().isDemo ? 3 : 4;
-		break;
-	case GI_KYRA2:
-		_version = 4;
-		break;
-	case GI_KYRA1:
-		_version = 3;
-		_soundTriggers = _kyra1SoundTriggers;
-		_numSoundTriggers = _kyra1NumSoundTriggers;
-		break;
-	case GI_EOB2:
-		_version = 2;
-		break;
-	case GI_EOB1:
-		_version = 1;
-		break;
-	default:
-		break;
-	}
-
-	_driver = new AdLibDriver(mixer, _version);
-	assert(_driver);
-}
-
-SoundAdLibPC::~SoundAdLibPC() {
-	delete _driver;
-	delete[] _soundDataPtr;
-	for (int i = 0; i < 3; i++)
-		initAudioResourceInfo(i, 0);
-}
-
-bool SoundAdLibPC::init() {
-	_driver->initDriver();
-	return true;
-}
-
-void SoundAdLibPC::process() {
-	int trigger = _driver->getSoundTrigger();
-
-	if (trigger < _numSoundTriggers) {
-		int soundId = _soundTriggers[trigger];
-
-		if (soundId)
-			playTrack(soundId);
-	} else {
-		warning("Unknown sound trigger %d", trigger);
-		// TODO: At this point, we really want to clear the trigger...
-	}
-}
-
-void SoundAdLibPC::updateVolumeSettings() {
-	bool mute = false;
-	if (ConfMan.hasKey("mute"))
-		mute = ConfMan.getBool("mute");
-
-	int newMusicVolume = mute ? 0 : ConfMan.getInt("music_volume");
-	//newMusicVolume = (newMusicVolume * 145) / Audio::Mixer::kMaxMixerVolume + 110;
-	newMusicVolume = CLIP(newMusicVolume, 0, 255);
-
-	int newSfxVolume = mute ? 0 : ConfMan.getInt("sfx_volume");
-	//newSfxVolume = (newSfxVolume * 200) / Audio::Mixer::kMaxMixerVolume + 55;
-	newSfxVolume = CLIP(newSfxVolume, 0, 255);
-
-	_driver->setMusicVolume(newMusicVolume);
-	_driver->setSfxVolume(newSfxVolume);
-}
-
-void SoundAdLibPC::playTrack(uint8 track) {
-	if (_musicEnabled) {
-		// WORKAROUND: There is a bug in the Kyra 1 "Pool of Sorrow"
-		// music which causes the channels to get progressively out of
-		// sync for each loop. To avoid that, we declare that all four
-		// of the song channels have to jump "in sync".
-
-		if (track == 4 && _soundFileLoaded.equalsIgnoreCase("KYRA1B.ADL"))
-			_driver->setSyncJumpMask(0x000F);
-		else
-			_driver->setSyncJumpMask(0);
-		play(track, 0xFF);
-	}
-}
-
-void SoundAdLibPC::haltTrack() {
-	play(0, 0);
-	play(0, 0);
-	//_vm->_system->delayMillis(3 * 60);
-}
-
-bool SoundAdLibPC::isPlaying() const {
-	return _driver->isChannelPlaying(0);
-}
-
-void SoundAdLibPC::playSoundEffect(uint8 track, uint8 volume) {
-	if (_sfxEnabled)
-		play(track, volume);
-}
-
-void SoundAdLibPC::play(uint8 track, uint8 volume) {
-	uint16 soundId = 0;
-
-	if (_version == 4)
-		soundId = READ_LE_UINT16(&_trackEntries[track<<1]);
-	else
-		soundId = _trackEntries[track];
-
-	if ((soundId == 0xFFFF && _version == 4) || (soundId == 0xFF && _version < 4) || !_soundDataPtr)
-		return;
-
-	_driver->queueTrack(soundId, volume);
-}
-
-void SoundAdLibPC::beginFadeOut() {
-	play(_version > 2 ? 1 : 15, 0xFF);
-}
-
-int SoundAdLibPC::checkTrigger() {
-	return _driver->getSoundTrigger();
-}
-
-void SoundAdLibPC::resetTrigger() {
-	_driver->resetSoundTrigger();
-}
-
-void SoundAdLibPC::initAudioResourceInfo(int set, void *info) {
-	if (set >= kMusicIntro && set <= kMusicFinale) {
-		delete _resInfo[set];
-		_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : 0;
-	}
-}
-
-void SoundAdLibPC::selectAudioResourceSet(int set) {
-	if (set >= kMusicIntro && set <= kMusicFinale) {
-		if (_resInfo[set])
-			_currentResourceSet = set;
-	}
-}
-
-bool SoundAdLibPC::hasSoundFile(uint file) const {
-	if (file < res()->fileListSize)
-		return (res()->fileList[file] != 0);
-	return false;
-}
-
-void SoundAdLibPC::loadSoundFile(uint file) {
-	if (file < res()->fileListSize)
-		internalLoadFile(res()->fileList[file]);
-}
-
-void SoundAdLibPC::loadSoundFile(Common::String file) {
-	internalLoadFile(file);
-}
-
-void SoundAdLibPC::internalLoadFile(Common::String file) {
-	file += ((_version == 1) ? ".DAT" : ".ADL");
-	if (_soundFileLoaded == file)
-		return;
-
-	if (_soundDataPtr)
-		haltTrack();
-
-	uint8 *fileData = 0; uint32 fileSize = 0;
-
-	fileData = _vm->resource()->fileData(file.c_str(), &fileSize);
-	if (!fileData) {
-		warning("Couldn't find music file: '%s'", file.c_str());
-		return;
-	}
-
-	playSoundEffect(0);
-	playSoundEffect(0);
-
-	_driver->stopAllChannels();
-	_soundDataPtr = 0;
-
-	int soundDataSize = fileSize;
-	uint8 *p = fileData;
-
-	if (_version == 4) {
-		memcpy(_trackEntries, p, 500);
-		p += 500;
-		soundDataSize -= 500;
-	} else {
-		memcpy(_trackEntries, p, 120);
-		p += 120;
-		soundDataSize -= 120;
-	}
-
-	_soundDataPtr = new uint8[soundDataSize];
-	assert(_soundDataPtr);
-
-	memcpy(_soundDataPtr, p, soundDataSize);
-
-	delete[] fileData;
-	fileData = p = 0;
-	fileSize = 0;
-
-	_driver->setSoundData(_soundDataPtr, soundDataSize);
-
-	_soundFileLoaded = file;
-}
-
-} // End of namespace Kyra
diff --git a/engines/kyra/sound/sound_adlib.h b/engines/kyra/sound/sound_adlib.h
deleted file mode 100644
index 0baa00b..0000000
--- a/engines/kyra/sound/sound_adlib.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/* 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 KYRA_SOUND_ADLIB_H
-#define KYRA_SOUND_ADLIB_H
-
-#include "kyra/sound/sound.h"
-
-#include "common/mutex.h"
-
-namespace Kyra {
-class AdLibDriver;
-
-/**
- * AdLib implementation of the sound output device.
- *
- * It uses a special sound file format special to
- * Dune II, Kyrandia 1 and 2. While Dune II and
- * Kyrandia 1 are using exact the same format, the
- * one of Kyrandia 2 slightly differs.
- *
- * See AdLibDriver for more information.
- * @see AdLibDriver
- */
-class SoundAdLibPC : public Sound {
-public:
-	SoundAdLibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer);
-	~SoundAdLibPC();
-
-	virtual kType getMusicType() const { return kAdLib; }
-
-	virtual bool init();
-	virtual void process();
-
-	virtual void updateVolumeSettings();
-
-	virtual void initAudioResourceInfo(int set, void *info);
-	virtual void selectAudioResourceSet(int set);
-	virtual bool hasSoundFile(uint file) const;
-	virtual void loadSoundFile(uint file);
-	virtual void loadSoundFile(Common::String file);
-
-	virtual void playTrack(uint8 track);
-	virtual void haltTrack();
-	virtual bool isPlaying() const;
-
-	virtual void playSoundEffect(uint8 track, uint8 volume = 0xFF);
-
-	virtual void beginFadeOut();
-
-	virtual int checkTrigger();
-	virtual void resetTrigger();
-private:
-	void internalLoadFile(Common::String file);
-
-	void play(uint8 track, uint8 volume);
-
-	const SoundResourceInfo_PC *res() const {return _resInfo[_currentResourceSet]; }
-	SoundResourceInfo_PC *_resInfo[3];
-	int _currentResourceSet;
-
-	AdLibDriver *_driver;
-
-	int _version;
-	uint8 _trackEntries[500];
-	uint8 *_soundDataPtr;
-	int _sfxPlayingSound;
-
-	Common::String _soundFileLoaded;
-
-	int _numSoundTriggers;
-	const int *_soundTriggers;
-
-	static const int _kyra1NumSoundTriggers;
-	static const int _kyra1SoundTriggers[];
-};
-
-} // End of namespace Kyra
-
-#endif
diff --git a/engines/kyra/sound/sound_intern.h b/engines/kyra/sound/sound_intern.h
index fc8c5a7..628635e 100644
--- a/engines/kyra/sound/sound_intern.h
+++ b/engines/kyra/sound/sound_intern.h
@@ -25,7 +25,7 @@
 
 
 #include "kyra/sound/sound.h"
-#include "kyra/sound/sound_adlib.h"
+#include "kyra/sound/sound_pc_v1.h"
 
 #include "audio/midiparser.h"
 #include "audio/softsynth/emumidi.h"
diff --git a/engines/kyra/sound/sound_midi.cpp b/engines/kyra/sound/sound_midi.cpp
deleted file mode 100644
index b1a681d..0000000
--- a/engines/kyra/sound/sound_midi.cpp
+++ /dev/null
@@ -1,414 +0,0 @@
-/* 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 "kyra/sound/drivers/midi.h"
-
-#include "kyra/resource/resource.h"
-
-#include "common/system.h"
-#include "common/config-manager.h"
-#include "common/translation.h"
-
-#include "gui/message.h"
-
-namespace Kyra {
-
-SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver, kType type) : Sound(vm, mixer) {
-	_driver = driver;
-	_output = 0;
-
-	_musicFile = _sfxFile = 0;
-	_currentResourceSet = 0;
-	memset(&_resInfo, 0, sizeof(_resInfo));
-
-	_music = MidiParser::createParser_XMIDI();
-	assert(_music);
-	for (int i = 0; i < 3; ++i) {
-		_sfx[i] = MidiParser::createParser_XMIDI();
-		assert(_sfx[i]);
-	}
-
-	_musicVolume = _sfxVolume = 0;
-	_fadeMusicOut = false;
-
-	_type = type;
-	assert(_type == kMidiMT32 || _type == kMidiGM || _type == kPCSpkr);
-
-	// Only General MIDI isn't a Roland MT-32 MIDI implemenation,
-	// even the PC Speaker driver is a Roland MT-32 based MIDI implementation.
-	// Thus we set "_nativeMT32" for all types except Gerneral MIDI to true.
-	_nativeMT32 = (_type != kMidiGM);
-
-	// KYRA1 does not include any General MIDI tracks, thus we have
-	// to overwrite the internal type with MT32 to get the correct
-	// file extension.
-	if (_vm->game() == GI_KYRA1 && _type == kMidiGM)
-		_type = kMidiMT32;
-
-	// Display a warning about possibly wrong sound when the user only has
-	// a General MIDI device, but the game is setup to use Roland MT32 MIDI.
-	// (This will only happen in The Legend of Kyrandia 1 though, all other
-	// supported games include special General MIDI tracks).
-	if (_type == kMidiMT32 && !_nativeMT32) {
-		::GUI::MessageDialog dialog(_("You appear to be using a General MIDI device,\n"
-									"but your game only supports Roland MT32 MIDI.\n"
-									"We try to map the Roland MT32 instruments to\n"
-									"General MIDI ones. It is still possible that\n"
-									"some tracks sound incorrect."));
-		dialog.runModal();
-	}
-}
-
-SoundMidiPC::~SoundMidiPC() {
-	Common::StackLock lock(_mutex);
-	_output->setTimerCallback(0, 0);
-
-	delete _music;
-	for (int i = 0; i < 3; ++i)
-		delete _sfx[i];
-
-	delete _output; // This automatically frees _driver (!)
-
-	if (_musicFile != _sfxFile)
-		delete[] _sfxFile;
-
-	delete[] _musicFile;
-
-	for (int i = 0; i < 3; i++)
-		initAudioResourceInfo(i, 0);
-}
-
-bool SoundMidiPC::init() {
-	_output = new MidiOutput(_vm->_system, _driver, _nativeMT32, (_type != kMidiGM));
-	assert(_output);
-
-	updateVolumeSettings();
-
-	_music->setMidiDriver(_output);
-	_music->setTempo(_output->getBaseTempo());
-	_music->setTimerRate(_output->getBaseTempo());
-
-	for (int i = 0; i < 3; ++i) {
-		_sfx[i]->setMidiDriver(_output);
-		_sfx[i]->setTempo(_output->getBaseTempo());
-		_sfx[i]->setTimerRate(_output->getBaseTempo());
-	}
-
-	_output->setTimerCallback(this, SoundMidiPC::onTimer);
-
-	if (_nativeMT32 && _type == kMidiMT32) {
-		const char *midiFile = 0;
-		const char *pakFile = 0;
-		if (_vm->game() == GI_KYRA1) {
-			midiFile = "INTRO";
-		} else if (_vm->game() == GI_KYRA2) {
-			midiFile = "HOF_SYX";
-			pakFile = "AUDIO.PAK";
-		} else if (_vm->game() == GI_LOL) {
-			midiFile = "LOREINTR";
-
-			if (_vm->gameFlags().isDemo) {
-				if (_vm->gameFlags().useAltShapeHeader) {
-					// Intro demo
-					pakFile = "INTROVOC.PAK";
-				} else {
-					// Kyra2 SEQ player based demo
-					pakFile = "GENERAL.PAK";
-					midiFile = "LOLSYSEX";
-				}
-			} else {
-				if (_vm->gameFlags().isTalkie)
-					pakFile = "ENG/STARTUP.PAK";
-				else
-					pakFile = "INTROVOC.PAK";
-			}
-		}
-
-		if (!midiFile)
-			return true;
-
-		if (pakFile)
-			_vm->resource()->loadPakFile(pakFile);
-
-		loadSoundFile(midiFile);
-		playTrack(0);
-
-		Common::Event event;
-		while (isPlaying() && !_vm->shouldQuit()) {
-			_vm->_system->updateScreen();
-			_vm->_eventMan->pollEvent(event);
-			_vm->_system->delayMillis(10);
-		}
-
-		if (pakFile)
-			_vm->resource()->unloadPakFile(pakFile);
-	}
-
-	return true;
-}
-
-void SoundMidiPC::updateVolumeSettings() {
-	Common::StackLock lock(_mutex);
-
-	if (!_output)
-		return;
-
-	bool mute = false;
-	if (ConfMan.hasKey("mute"))
-		mute = ConfMan.getBool("mute");
-
-	const int newMusVol = (mute ? 0 : ConfMan.getInt("music_volume"));
-	_sfxVolume = (mute ? 0 : ConfMan.getInt("sfx_volume"));
-
-	_output->setSourceVolume(0, newMusVol, newMusVol != _musicVolume);
-	_musicVolume = newMusVol;
-
-	for (int i = 1; i < 4; ++i)
-		_output->setSourceVolume(i, _sfxVolume, false);
-}
-
-void SoundMidiPC::initAudioResourceInfo(int set, void *info) {
-	if (set >= kMusicIntro && set <= kMusicFinale) {
-		delete _resInfo[set];
-		_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : 0;
-	}
-}
-
-void SoundMidiPC::selectAudioResourceSet(int set) {
-	if (set >= kMusicIntro && set <= kMusicFinale) {
-		if (_resInfo[set])
-			_currentResourceSet = set;
-	}
-}
-
-bool SoundMidiPC::hasSoundFile(uint file) const {
-	if (file < res()->fileListSize)
-		return (res()->fileList[file] != 0);
-	return false;
-}
-
-void SoundMidiPC::loadSoundFile(uint file) {
-	if (file < res()->fileListSize)
-		loadSoundFile(res()->fileList[file]);
-}
-
-void SoundMidiPC::loadSoundFile(Common::String file) {
-	Common::StackLock lock(_mutex);
-	file = getFileName(file);
-
-	if (_mFileName == file)
-		return;
-
-	if (!_vm->resource()->exists(file.c_str()))
-		return;
-
-	// When loading a new file we stop all notes
-	// still running on our own, just to prevent
-	// glitches
-	for (int i = 0; i < 16; ++i)
-		_output->stopNotesOnChannel(i);
-
-	delete[] _musicFile;
-	uint32 fileSize = 0;
-	_musicFile = _vm->resource()->fileData(file.c_str(), &fileSize);
-	_mFileName = file;
-
-	_output->setSoundSource(0);
-	_music->loadMusic(_musicFile, fileSize);
-	_music->stopPlaying();
-
-	// Since KYRA1 uses the same file for SFX and Music
-	// we setup sfx to play from music file as well
-	if (_vm->game() == GI_KYRA1) {
-		for (int i = 0; i < 3; ++i) {
-			_output->setSoundSource(i+1);
-			_sfx[i]->loadMusic(_musicFile, fileSize);
-			_sfx[i]->stopPlaying();
-		}
-	}
-}
-
-void SoundMidiPC::loadSfxFile(Common::String file) {
-	Common::StackLock lock(_mutex);
-
-	// Kyrandia 1 doesn't use a special sfx file
-	if (_vm->game() == GI_KYRA1)
-		return;
-
-	file = getFileName(file);
-
-	if (_sFileName == file)
-		return;
-
-	if (!_vm->resource()->exists(file.c_str()))
-		return;
-
-	delete[] _sfxFile;
-
-	uint32 fileSize = 0;
-	_sfxFile = _vm->resource()->fileData(file.c_str(), &fileSize);
-	_sFileName = file;
-
-	for (int i = 0; i < 3; ++i) {
-		_output->setSoundSource(i+1);
-		_sfx[i]->loadMusic(_sfxFile, fileSize);
-		_sfx[i]->stopPlaying();
-	}
-}
-
-void SoundMidiPC::playTrack(uint8 track) {
-	if (!_musicEnabled)
-		return;
-
-	haltTrack();
-
-	// The following two lines are meant as a fix for bug #6314.
-	// It is on purpose that they are outside the mutex lock.
-	_output->allSoundsOff();
-	_vm->delay(250);
-
-	Common::StackLock lock(_mutex);
-	_fadeMusicOut = false;
-	
-	_output->setSourceVolume(0, _musicVolume, true);
-
-	_output->initSource(0);
-	_output->setSourceVolume(0, _musicVolume, true);
-	_music->setTrack(track);
-}
-
-void SoundMidiPC::haltTrack() {
-	Common::StackLock lock(_mutex);
-
-	_output->setSoundSource(0);
-	_music->stopPlaying();
-	_output->deinitSource(0);
-}
-
-bool SoundMidiPC::isPlaying() const {
-	Common::StackLock lock(_mutex);
-
-	return _music->isPlaying();
-}
-
-void SoundMidiPC::playSoundEffect(uint8 track, uint8) {
-	if (!_sfxEnabled)
-		return;
-
-	Common::StackLock lock(_mutex);
-	for (int i = 0; i < 3; ++i) {
-		if (!_sfx[i]->isPlaying()) {
-			_output->initSource(i+1);
-			_sfx[i]->setTrack(track);
-			return;
-		}
-	}
-}
-
-void SoundMidiPC::stopAllSoundEffects() {
-	Common::StackLock lock(_mutex);
-
-	for (int i = 0; i < 3; ++i) {
-		_output->setSoundSource(i+1);
-		_sfx[i]->stopPlaying();
-		_output->deinitSource(i+1);
-	}
-}
-
-void SoundMidiPC::beginFadeOut() {
-	Common::StackLock lock(_mutex);
-
-	_fadeMusicOut = true;
-	_fadeStartTime = _vm->_system->getMillis();
-}
-
-void SoundMidiPC::pause(bool paused) {
-	Common::StackLock lock(_mutex);
-
-	if (paused) {
-		_music->setMidiDriver(0);
-		for (int i = 0; i < 3; i++)
-			_sfx[i]->setMidiDriver(0);
-		for (int i = 0; i < 16; i++)
-			_output->stopNotesOnChannel(i);
-	} else {
-		_music->setMidiDriver(_output);
-		for (int i = 0; i < 3; ++i)
-			_sfx[i]->setMidiDriver(_output);
-		// Possible TODO (IMHO unnecessary): restore notes and/or update _fadeStartTime
-	}
-}
-
-void SoundMidiPC::onTimer(void *data) {
-	SoundMidiPC *midi = (SoundMidiPC *)data;
-
-	Common::StackLock lock(midi->_mutex);
-
-	if (midi->_fadeMusicOut) {
-		static const uint32 musicFadeTime = 1 * 1000;
-
-		if (midi->_fadeStartTime + musicFadeTime > midi->_vm->_system->getMillis()) {
-			int volume = (byte)((musicFadeTime - (midi->_vm->_system->getMillis() - midi->_fadeStartTime)) * midi->_musicVolume / musicFadeTime);
-			midi->_output->setSourceVolume(0, volume, true);
-		} else {
-			for (int i = 0; i < 16; ++i)
-				midi->_output->stopNotesOnChannel(i);
-			for (int i = 0; i < 4; ++i)
-				midi->_output->deinitSource(i);
-
-			midi->_output->setSoundSource(0);
-			midi->_music->stopPlaying();
-
-			for (int i = 0; i < 3; ++i) {
-				midi->_output->setSoundSource(i+1);
-				midi->_sfx[i]->stopPlaying();
-			}
-
-			midi->_fadeMusicOut = false;
-		}
-	}
-
-	midi->_output->setSoundSource(0);
-	midi->_music->onTimer();
-
-	for (int i = 0; i < 3; ++i) {
-		midi->_output->setSoundSource(i+1);
-		midi->_sfx[i]->onTimer();
-	}
-}
-
-Common::String SoundMidiPC::getFileName(const Common::String &str) {
-	Common::String file = str;
-	if (_type == kMidiMT32)
-		file += ".XMI";
-	else if (_type == kMidiGM)
-		file += ".C55";
-	else if (_type == kPCSpkr)
-		file += ".PCS";
-
-	if (_vm->resource()->exists(file.c_str()))
-		return file;
-
-	return str + ".XMI";
-}
-
-} // End of namespace Kyra
diff --git a/engines/kyra/sound/sound_pc_midi.cpp b/engines/kyra/sound/sound_pc_midi.cpp
new file mode 100644
index 0000000..d15315b
--- /dev/null
+++ b/engines/kyra/sound/sound_pc_midi.cpp
@@ -0,0 +1,414 @@
+/* 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 "kyra/sound/drivers/midi.h"
+
+#include "kyra/resource/resource.h"
+
+#include "common/system.h"
+#include "common/config-manager.h"
+#include "common/translation.h"
+
+#include "gui/message.h"
+
+namespace Kyra {
+
+SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver, kType type) : Sound(vm, mixer) {
+	_driver = driver;
+	_output = 0;
+
+	_musicFile = _sfxFile = 0;
+	_currentResourceSet = 0;
+	memset(&_resInfo, 0, sizeof(_resInfo));
+
+	_music = MidiParser::createParser_XMIDI();
+	assert(_music);
+	for (int i = 0; i < 3; ++i) {
+		_sfx[i] = MidiParser::createParser_XMIDI();
+		assert(_sfx[i]);
+	}
+
+	_musicVolume = _sfxVolume = 0;
+	_fadeMusicOut = false;
+
+	_type = type;
+	assert(_type == kMidiMT32 || _type == kMidiGM || _type == kPCSpkr);
+
+	// Only General MIDI isn't a Roland MT-32 MIDI implemenation,
+	// even the PC Speaker driver is a Roland MT-32 based MIDI implementation.
+	// Thus we set "_nativeMT32" for all types except Gerneral MIDI to true.
+	_nativeMT32 = (_type != kMidiGM);
+
+	// KYRA1 does not include any General MIDI tracks, thus we have
+	// to overwrite the internal type with MT32 to get the correct
+	// file extension.
+	if (_vm->game() == GI_KYRA1 && _type == kMidiGM)
+		_type = kMidiMT32;
+
+	// Display a warning about possibly wrong sound when the user only has
+	// a General MIDI device, but the game is setup to use Roland MT32 MIDI.
+	// (This will only happen in The Legend of Kyrandia 1 though, all other
+	// supported games include special General MIDI tracks).
+	if (_type == kMidiMT32 && !_nativeMT32) {
+		::GUI::MessageDialog dialog(_("You appear to be using a General MIDI device,\n"
+									"but your game only supports Roland MT32 MIDI.\n"
+									"We try to map the Roland MT32 instruments to\n"
+									"General MIDI ones. It is still possible that\n"
+									"some tracks sound incorrect."));
+		dialog.runModal();
+	}
+}
+
+SoundMidiPC::~SoundMidiPC() {
+	Common::StackLock lock(_mutex);
+	_output->setTimerCallback(0, 0);
+
+	delete _music;
+	for (int i = 0; i < 3; ++i)
+		delete _sfx[i];
+
+	delete _output; // This automatically frees _driver (!)
+
+	if (_musicFile != _sfxFile)
+		delete[] _sfxFile;
+
+	delete[] _musicFile;
+
+	for (int i = 0; i < 3; i++)
+		initAudioResourceInfo(i, 0);
+}
+
+bool SoundMidiPC::init() {
+	_output = new MidiOutput(_vm->_system, _driver, _nativeMT32, (_type != kMidiGM));
+	assert(_output);
+
+	updateVolumeSettings();
+
+	_music->setMidiDriver(_output);
+	_music->setTempo(_output->getBaseTempo());
+	_music->setTimerRate(_output->getBaseTempo());
+
+	for (int i = 0; i < 3; ++i) {
+		_sfx[i]->setMidiDriver(_output);
+		_sfx[i]->setTempo(_output->getBaseTempo());
+		_sfx[i]->setTimerRate(_output->getBaseTempo());
+	}
+
+	_output->setTimerCallback(this, SoundMidiPC::onTimer);
+
+	if (_nativeMT32 && _type == kMidiMT32) {
+		const char *midiFile = 0;
+		const char *pakFile = 0;
+		if (_vm->game() == GI_KYRA1) {
+			midiFile = "INTRO";
+		} else if (_vm->game() == GI_KYRA2) {
+			midiFile = "HOF_SYX";
+			pakFile = "AUDIO.PAK";
+		} else if (_vm->game() == GI_LOL) {
+			midiFile = "LOREINTR";
+
+			if (_vm->gameFlags().isDemo) {
+				if (_vm->gameFlags().useAltShapeHeader) {
+					// Intro demo
+					pakFile = "INTROVOC.PAK";
+				} else {
+					// Kyra2 SEQ player based demo
+					pakFile = "GENERAL.PAK";
+					midiFile = "LOLSYSEX";
+				}
+			} else {
+				if (_vm->gameFlags().isTalkie)
+					pakFile = "ENG/STARTUP.PAK";
+				else
+					pakFile = "INTROVOC.PAK";
+			}
+		}
+
+		if (!midiFile)
+			return true;
+
+		if (pakFile)
+			_vm->resource()->loadPakFile(pakFile);
+
+		loadSoundFile(midiFile);
+		playTrack(0);
+
+		Common::Event event;
+		while (isPlaying() && !_vm->shouldQuit()) {
+			_vm->_system->updateScreen();
+			_vm->_eventMan->pollEvent(event);
+			_vm->_system->delayMillis(10);
+		}
+
+		if (pakFile)
+			_vm->resource()->unloadPakFile(pakFile);
+	}
+
+	return true;
+}
+
+void SoundMidiPC::updateVolumeSettings() {
+	Common::StackLock lock(_mutex);
+
+	if (!_output)
+		return;
+
+	bool mute = false;
+	if (ConfMan.hasKey("mute"))
+		mute = ConfMan.getBool("mute");
+
+	const int newMusVol = (mute ? 0 : ConfMan.getInt("music_volume"));
+	_sfxVolume = (mute ? 0 : ConfMan.getInt("sfx_volume"));
+
+	_output->setSourceVolume(0, newMusVol, newMusVol != _musicVolume);
+	_musicVolume = newMusVol;
+
+	for (int i = 1; i < 4; ++i)
+		_output->setSourceVolume(i, _sfxVolume, false);
+}
+
+void SoundMidiPC::initAudioResourceInfo(int set, void *info) {
+	if (set >= kMusicIntro && set <= kMusicFinale) {
+		delete _resInfo[set];
+		_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : 0;
+	}
+}
+
+void SoundMidiPC::selectAudioResourceSet(int set) {
+	if (set >= kMusicIntro && set <= kMusicFinale) {
+		if (_resInfo[set])
+			_currentResourceSet = set;
+	}
+}
+
+bool SoundMidiPC::hasSoundFile(uint file) const {
+	if (file < res()->fileListSize)
+		return (res()->fileList[file] != 0);
+	return false;
+}
+
+void SoundMidiPC::loadSoundFile(uint file) {
+	if (file < res()->fileListSize)
+		loadSoundFile(res()->fileList[file]);
+}
+
+void SoundMidiPC::loadSoundFile(Common::String file) {
+	Common::StackLock lock(_mutex);
+	file = getFileName(file);
+
+	if (_mFileName == file)
+		return;
+
+	if (!_vm->resource()->exists(file.c_str()))
+		return;
+
+	// When loading a new file we stop all notes
+	// still running on our own, just to prevent
+	// glitches
+	for (int i = 0; i < 16; ++i)
+		_output->stopNotesOnChannel(i);
+
+	delete[] _musicFile;
+	uint32 fileSize = 0;
+	_musicFile = _vm->resource()->fileData(file.c_str(), &fileSize);
+	_mFileName = file;
+
+	_output->setSoundSource(0);
+	_music->loadMusic(_musicFile, fileSize);
+	_music->stopPlaying();
+
+	// Since KYRA1 uses the same file for SFX and Music
+	// we setup sfx to play from music file as well
+	if (_vm->game() == GI_KYRA1) {
+		for (int i = 0; i < 3; ++i) {
+			_output->setSoundSource(i+1);
+			_sfx[i]->loadMusic(_musicFile, fileSize);
+			_sfx[i]->stopPlaying();
+		}
+	}
+}
+
+void SoundMidiPC::loadSfxFile(Common::String file) {
+	Common::StackLock lock(_mutex);
+
+	// Kyrandia 1 doesn't use a special sfx file
+	if (_vm->game() == GI_KYRA1)
+		return;
+
+	file = getFileName(file);
+
+	if (_sFileName == file)
+		return;
+
+	if (!_vm->resource()->exists(file.c_str()))
+		return;
+
+	delete[] _sfxFile;
+
+	uint32 fileSize = 0;
+	_sfxFile = _vm->resource()->fileData(file.c_str(), &fileSize);
+	_sFileName = file;
+
+	for (int i = 0; i < 3; ++i) {
+		_output->setSoundSource(i+1);
+		_sfx[i]->loadMusic(_sfxFile, fileSize);
+		_sfx[i]->stopPlaying();
+	}
+}
+
+void SoundMidiPC::playTrack(uint8 track) {
+	if (!_musicEnabled)
+		return;
+
+	haltTrack();
+
+	// The following two lines are meant as a fix for bug #6314.
+	// It is on purpose that they are outside the mutex lock.
+	_output->allSoundsOff();
+	_vm->delay(250);
+
+	Common::StackLock lock(_mutex);
+	_fadeMusicOut = false;
+	
+	_output->setSourceVolume(0, _musicVolume, true);
+
+	_output->initSource(0);
+	_output->setSourceVolume(0, _musicVolume, true);
+	_music->setTrack(track);
+}
+
+void SoundMidiPC::haltTrack() {
+	Common::StackLock lock(_mutex);
+
+	_output->setSoundSource(0);
+	_music->stopPlaying();
+	_output->deinitSource(0);
+}
+
+bool SoundMidiPC::isPlaying() const {
+	Common::StackLock lock(_mutex);
+
+	return _music->isPlaying();
+}
+
+void SoundMidiPC::playSoundEffect(uint8 track, uint8) {
+	if (!_sfxEnabled)
+		return;
+
+	Common::StackLock lock(_mutex);
+	for (int i = 0; i < 3; ++i) {
+		if (!_sfx[i]->isPlaying()) {
+			_output->initSource(i+1);
+			_sfx[i]->setTrack(track);
+			return;
+		}
+	}
+}
+
+void SoundMidiPC::stopAllSoundEffects() {
+	Common::StackLock lock(_mutex);
+
+	for (int i = 0; i < 3; ++i) {
+		_output->setSoundSource(i+1);
+		_sfx[i]->stopPlaying();
+		_output->deinitSource(i+1);
+	}
+}
+
+void SoundMidiPC::beginFadeOut() {
+	Common::StackLock lock(_mutex);
+
+	_fadeMusicOut = true;
+	_fadeStartTime = _vm->_system->getMillis();
+}
+
+void SoundMidiPC::pause(bool paused) {
+	Common::StackLock lock(_mutex);
+
+	if (paused) {
+		_music->setMidiDriver(0);
+		for (int i = 0; i < 3; i++)
+			_sfx[i]->setMidiDriver(0);
+		for (int i = 0; i < 16; i++)
+			_output->stopNotesOnChannel(i);
+	} else {
+		_music->setMidiDriver(_output);
+		for (int i = 0; i < 3; ++i)
+			_sfx[i]->setMidiDriver(_output);
+		// Possible TODO (IMHO unnecessary): restore notes and/or update _fadeStartTime
+	}
+}
+
+void SoundMidiPC::onTimer(void *data) {
+	SoundMidiPC *midi = (SoundMidiPC *)data;
+
+	Common::StackLock lock(midi->_mutex);
+
+	if (midi->_fadeMusicOut) {
+		static const uint32 musicFadeTime = 1 * 1000;
+
+		if (midi->_fadeStartTime + musicFadeTime > midi->_vm->_system->getMillis()) {
+			int volume = (byte)((musicFadeTime - (midi->_vm->_system->getMillis() - midi->_fadeStartTime)) * midi->_musicVolume / musicFadeTime);
+			midi->_output->setSourceVolume(0, volume, true);
+		} else {
+			for (int i = 0; i < 16; ++i)
+				midi->_output->stopNotesOnChannel(i);
+			for (int i = 0; i < 4; ++i)
+				midi->_output->deinitSource(i);
+
+			midi->_output->setSoundSource(0);
+			midi->_music->stopPlaying();
+
+			for (int i = 0; i < 3; ++i) {
+				midi->_output->setSoundSource(i+1);
+				midi->_sfx[i]->stopPlaying();
+			}
+
+			midi->_fadeMusicOut = false;
+		}
+	}
+
+	midi->_output->setSoundSource(0);
+	midi->_music->onTimer();
+
+	for (int i = 0; i < 3; ++i) {
+		midi->_output->setSoundSource(i+1);
+		midi->_sfx[i]->onTimer();
+	}
+}
+
+Common::String SoundMidiPC::getFileName(const Common::String &str) {
+	Common::String file = str;
+	if (_type == kMidiMT32)
+		file += ".XMI";
+	else if (_type == kMidiGM)
+		file += ".C55";
+	else if (_type == kPCSpkr)
+		file += ".SND";//".PCS";
+
+	if (_vm->resource()->exists(file.c_str()))
+		return file;
+
+	return str + ".XMI";
+}
+
+} // End of namespace Kyra
diff --git a/engines/kyra/sound/sound_pc_v1.cpp b/engines/kyra/sound/sound_pc_v1.cpp
new file mode 100644
index 0000000..94b489b
--- /dev/null
+++ b/engines/kyra/sound/sound_pc_v1.cpp
@@ -0,0 +1,263 @@
+/* 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 "kyra/sound/sound_intern.h"
+#include "kyra/sound/drivers/pc_base.h"
+
+#include "common/system.h"
+#include "common/config-manager.h"
+
+
+namespace Kyra {
+
+// Kyra 1 sound triggers. Most noticeably, these are used towards the end of
+// the game, in the castle, to cycle between different songs. The same music is
+// used in other places throughout the game, but the player is less likely to
+// spend enough time there to notice.
+
+const int SoundPC_v1::_kyra1SoundTriggers[] = {
+	0, 4, 5, 3
+};
+
+const int SoundPC_v1::_kyra1NumSoundTriggers = ARRAYSIZE(SoundPC_v1::_kyra1SoundTriggers);
+
+SoundPC_v1::SoundPC_v1(KyraEngine_v1 *vm, Audio::Mixer *mixer, kType type)
+	: Sound(vm, mixer), _driver(0), _trackEntries(), _soundDataPtr(0), _type(type) {
+	memset(_trackEntries, 0, sizeof(_trackEntries));
+
+	_soundTriggers = 0;
+	_numSoundTriggers = 0;
+	_sfxPlayingSound = -1;
+	_soundFileLoaded.clear();
+	_currentResourceSet = 0;
+	memset(&_resInfo, 0, sizeof(_resInfo));
+
+	switch (vm->game()) {
+	case GI_LOL:
+		_version = _vm->gameFlags().isDemo ? 3 : 4;
+		break;
+	case GI_KYRA2:
+		_version = 4;
+		break;
+	case GI_KYRA1:
+		_version = 3;
+		_soundTriggers = _kyra1SoundTriggers;
+		_numSoundTriggers = _kyra1NumSoundTriggers;
+		break;
+	case GI_EOB2:
+		_version = 2;
+		break;
+	case GI_EOB1:
+		_version = 1;
+		break;
+	default:
+		break;
+	}
+
+	// Correct the type to someting we support. NullSound is treated as a silent AdLib driver.
+	if (_type != kAdLib && _type != kPCSpkr)
+		_type = kAdLib;
+
+	_driver = (type == kAdLib) ? PCSoundDriver::createAdLib(mixer, _version) : PCSoundDriver::createPCSpk(mixer);
+	assert(_driver);
+}
+
+SoundPC_v1::~SoundPC_v1() {
+	delete _driver;
+	delete[] _soundDataPtr;
+	for (int i = 0; i < 3; i++)
+		initAudioResourceInfo(i, 0);
+}
+
+bool SoundPC_v1::init() {
+	_driver->initDriver();
+	return true;
+}
+
+void SoundPC_v1::process() {
+	int trigger = _driver->getSoundTrigger();
+
+	if (trigger < _numSoundTriggers) {
+		int soundId = _soundTriggers[trigger];
+
+		if (soundId)
+			playTrack(soundId);
+	} else {
+		warning("Unknown sound trigger %d", trigger);
+		// TODO: At this point, we really want to clear the trigger...
+	}
+}
+
+void SoundPC_v1::updateVolumeSettings() {
+	bool mute = false;
+	if (ConfMan.hasKey("mute"))
+		mute = ConfMan.getBool("mute");
+
+	int newMusicVolume = mute ? 0 : ConfMan.getInt("music_volume");
+	//newMusicVolume = (newMusicVolume * 145) / Audio::Mixer::kMaxMixerVolume + 110;
+	newMusicVolume = CLIP(newMusicVolume, 0, 255);
+
+	int newSfxVolume = mute ? 0 : ConfMan.getInt("sfx_volume");
+	//newSfxVolume = (newSfxVolume * 200) / Audio::Mixer::kMaxMixerVolume + 55;
+	newSfxVolume = CLIP(newSfxVolume, 0, 255);
+
+	_driver->setMusicVolume(newMusicVolume);
+	_driver->setSfxVolume(newSfxVolume);
+}
+
+void SoundPC_v1::playTrack(uint8 track) {
+	if (_musicEnabled) {
+		// WORKAROUND: There is a bug in the Kyra 1 "Pool of Sorrow"
+		// music which causes the channels to get progressively out of
+		// sync for each loop. To avoid that, we declare that all four
+		// of the song channels have to jump "in sync".
+
+		if (track == 4 && _soundFileLoaded.equalsIgnoreCase("KYRA1B.ADL"))
+			_driver->setSyncJumpMask(0x000F);
+		else
+			_driver->setSyncJumpMask(0);
+		play(track, 0xFF);
+	}
+}
+
+void SoundPC_v1::haltTrack() {
+	play(0, 0);
+	play(0, 0);
+	//_vm->_system->delayMillis(3 * 60);
+}
+
+bool SoundPC_v1::isPlaying() const {
+	return _driver->isChannelPlaying(0);
+}
+
+void SoundPC_v1::playSoundEffect(uint8 track, uint8 volume) {
+	if (_sfxEnabled)
+		play(track, volume);
+}
+
+void SoundPC_v1::play(uint8 track, uint8 volume) {
+	uint16 soundId = 0;
+
+	if (_version == 4)
+		soundId = READ_LE_UINT16(&_trackEntries[track<<1]);
+	else
+		soundId = _trackEntries[track];
+
+	if ((soundId == 0xFFFF && _version == 4) || (soundId == 0xFF && _version < 4) || !_soundDataPtr)
+		return;
+
+	_driver->queueTrack(soundId, volume);
+}
+
+void SoundPC_v1::beginFadeOut() {
+	play(_version > 2 ? 1 : 15, 0xFF);
+}
+
+int SoundPC_v1::checkTrigger() {
+	return _driver->getSoundTrigger();
+}
+
+void SoundPC_v1::resetTrigger() {
+	_driver->resetSoundTrigger();
+}
+
+void SoundPC_v1::initAudioResourceInfo(int set, void *info) {
+	if (set >= kMusicIntro && set <= kMusicFinale) {
+		delete _resInfo[set];
+		_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : 0;
+	}
+}
+
+void SoundPC_v1::selectAudioResourceSet(int set) {
+	if (set >= kMusicIntro && set <= kMusicFinale) {
+		if (_resInfo[set])
+			_currentResourceSet = set;
+	}
+}
+
+bool SoundPC_v1::hasSoundFile(uint file) const {
+	if (file < res()->fileListSize)
+		return (res()->fileList[file] != 0);
+	return false;
+}
+
+void SoundPC_v1::loadSoundFile(uint file) {
+	if (_version == 1 && _type == kPCSpkr)
+		file += 1;
+	if (file < res()->fileListSize)
+		internalLoadFile(res()->fileList[file]);
+}
+
+void SoundPC_v1::loadSoundFile(Common::String file) {
+	internalLoadFile(file);
+}
+
+void SoundPC_v1::internalLoadFile(Common::String file) {
+	file += ((_version == 1) ? ".DAT" : (_type == kPCSpkr ? ".SND" : ".ADL"));
+	if (_soundFileLoaded == file)
+		return;
+
+	if (_soundDataPtr)
+		haltTrack();
+
+	uint8 *fileData = 0; uint32 fileSize = 0;
+
+	fileData = _vm->resource()->fileData(file.c_str(), &fileSize);
+	if (!fileData) {
+		warning("Couldn't find music file: '%s'", file.c_str());
+		return;
+	}
+
+	playSoundEffect(0);
+	playSoundEffect(0);
+
+	_driver->stopAllChannels();
+	_soundDataPtr = 0;
+
+	int soundDataSize = fileSize;
+	uint8 *p = fileData;
+
+	if (_version == 4) {
+		memcpy(_trackEntries, p, 500);
+		p += 500;
+		soundDataSize -= 500;
+	} else {
+		memcpy(_trackEntries, p, 120);
+		p += 120;
+		soundDataSize -= 120;
+	}
+
+	_soundDataPtr = new uint8[soundDataSize];
+	assert(_soundDataPtr);
+
+	memcpy(_soundDataPtr, p, soundDataSize);
+
+	delete[] fileData;
+	fileData = p = 0;
+	fileSize = 0;
+
+	_driver->setSoundData(_soundDataPtr, soundDataSize);
+
+	_soundFileLoaded = file;
+}
+
+} // End of namespace Kyra
diff --git a/engines/kyra/sound/sound_pc_v1.h b/engines/kyra/sound/sound_pc_v1.h
new file mode 100644
index 0000000..850c1fc
--- /dev/null
+++ b/engines/kyra/sound/sound_pc_v1.h
@@ -0,0 +1,106 @@
+/* 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 KYRA_SOUND_ADLIB_H
+#define KYRA_SOUND_ADLIB_H
+
+#include "kyra/sound/sound.h"
+
+#include "common/mutex.h"
+
+namespace Kyra {
+class PCSoundDriver;
+
+/**
+ * AdLib/PC Speaker (early version) implementation of the
+ * sound output device.
+ *
+ * It uses a special sound file format special to EoB I, II,
+ * Dune II, Kyrandia 1 and 2 and LoL. EoB I has a slightly
+ * different (oldest) file format, EoB II, Dune II and
+ * Kyrandia 1 have the exact same format, Kyrandia 2  and
+ * LoL have a slightly different format.
+ *
+ * For PC Speaker this is a little different. Only the EoB
+ * games use the old driver with this data file format. The
+ * newer games use a MIDI-like driver (see pcspeaker_v2.cpp).
+ *
+ * See AdLibDriver / PCSpeakerDriver for more information.
+ * @see AdLibDriver
+ */
+class SoundPC_v1 : public Sound {
+public:
+	SoundPC_v1(KyraEngine_v1 *vm, Audio::Mixer *mixer, kType type);
+	~SoundPC_v1();
+
+	virtual kType getMusicType() const { return _type; }
+
+	virtual bool init();
+	virtual void process();
+
+	virtual void updateVolumeSettings();
+
+	virtual void initAudioResourceInfo(int set, void *info);
+	virtual void selectAudioResourceSet(int set);
+	virtual bool hasSoundFile(uint file) const;
+	virtual void loadSoundFile(uint file);
+	virtual void loadSoundFile(Common::String file);
+
+	virtual void playTrack(uint8 track);
+	virtual void haltTrack();
+	virtual bool isPlaying() const;
+
+	virtual void playSoundEffect(uint8 track, uint8 volume = 0xFF);
+
+	virtual void beginFadeOut();
+
+	virtual int checkTrigger();
+	virtual void resetTrigger();
+private:
+	void internalLoadFile(Common::String file);
+
+	void play(uint8 track, uint8 volume);
+
+	const SoundResourceInfo_PC *res() const {return _resInfo[_currentResourceSet]; }
+	SoundResourceInfo_PC *_resInfo[3];
+	int _currentResourceSet;
+
+	PCSoundDriver *_driver;
+
+	int _version;
+	kType _type;
+	uint8 _trackEntries[500];
+	uint8 *_soundDataPtr;
+	int _sfxPlayingSound;
+
+	Common::String _soundFileLoaded;
+
+	int _numSoundTriggers;
+	const int *_soundTriggers;
+
+	static const int _kyra1NumSoundTriggers;
+	static const int _kyra1SoundTriggers[];
+};
+
+} // End of namespace Kyra
+
+#endif


Commit: 0820b0617d5cf565ab65a4448a3cb17bf330cd7a
    https://github.com/scummvm/scummvm/commit/0820b0617d5cf565ab65a4448a3cb17bf330cd7a
Author: athrxx (athrxx at scummvm.org)
Date: 2020-02-01T21:09:49+01:00

Commit Message:
KYRA: (EOB) - add PC Speaker driver

Changed paths:
    engines/kyra/sequence/sequences_darkmoon.cpp
    engines/kyra/sound/drivers/adlib.cpp
    engines/kyra/sound/drivers/pc_base.h
    engines/kyra/sound/drivers/pcspeaker_v1.cpp
    engines/kyra/sound/sound.h
    engines/kyra/sound/sound_pc_v1.cpp


diff --git a/engines/kyra/sequence/sequences_darkmoon.cpp b/engines/kyra/sequence/sequences_darkmoon.cpp
index a252276..c43e7f5 100644
--- a/engines/kyra/sequence/sequences_darkmoon.cpp
+++ b/engines/kyra/sequence/sequences_darkmoon.cpp
@@ -1719,7 +1719,7 @@ void DarkmoonSequenceHelper::delay(uint32 ticks) {
 void DarkmoonSequenceHelper::waitForSongNotifier(int index, bool introUpdateAnim) {
 	if (_vm->gameFlags().platform == Common::kPlatformFMTowns)
 		index = _sndMarkersFMTowns[index - 1];
-	else if (_vm->gameFlags().platform == Common::kPlatformAmiga)
+	else if (_vm->sound()->getMusicType() != Sound::kAdLib)
 		return;
 
 	int seq = 0;
diff --git a/engines/kyra/sound/drivers/adlib.cpp b/engines/kyra/sound/drivers/adlib.cpp
index a576eb2..05fbb23 100644
--- a/engines/kyra/sound/drivers/adlib.cpp
+++ b/engines/kyra/sound/drivers/adlib.cpp
@@ -54,13 +54,13 @@ public:
 
 	virtual void initDriver() override;
 	virtual void setSoundData(uint8 *data, uint32 size) override;
-	virtual void queueTrack(int track, int volume) override;
+	virtual void startSound(int track, int volume) override;
 	virtual bool isChannelPlaying(int channel) const override;
 	virtual void stopAllChannels() override;
-	virtual int getSoundTrigger() const override { return _soundTrigger; }
-	virtual void resetSoundTrigger() override { _soundTrigger = 0; }
+	int getSoundTrigger() const { return _soundTrigger; }
+	void resetSoundTrigger() { _soundTrigger = 0; }
 
-	virtual void callback() override;
+	void callback();
 
 	virtual void setSyncJumpMask(uint16 mask) override { _syncJumpMask = mask; }
 
@@ -179,24 +179,6 @@ private:
 	// * One for programs, starting at offset 0.
 	// * One for instruments, starting at offset 500.
 
-	uint8 *getProgram(int progId) {
-		const uint16 offset = READ_LE_UINT16(_soundData + 2 * progId);
-
-		// In case an invalid offset is specified we return nullptr to
-		// indicate an error. 0xFFFF seems to indicate "this is not a valid
-		// program/instrument". However, 0 is also invalid because it points
-		// inside the offset table itself. We also ignore any offsets outside
-		// of the actual data size.
-		// The original does not contain any safety checks and will simply
-		// read outside of the valid sound data in case an invalid offset is
-		// encountered.
-		if (offset == 0 || offset >= _soundDataSize) {
-			return nullptr;
-		} else {
-			return _soundData + offset;
-		}
-	}
-
 	const uint8 *getInstrument(int instrumentId) {
 		return getProgram(_numPrograms + instrumentId);
 	}
@@ -326,9 +308,6 @@ private:
 
 	OPL::OPL *_adlib;
 
-	uint8 *_soundData;
-	uint32 _soundDataSize;
-
 	struct QueueEntry {
 		QueueEntry() : data(0), id(0), volume(0) {}
 		QueueEntry(uint8 *ptr, uint8 track, uint8 vol) : data(ptr), id(track), volume(vol) {}
@@ -390,8 +369,6 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) : PCSoundDriver() {
 		error("Failed to create OPL");
 
 	memset(_channels, 0, sizeof(_channels));
-	_soundData = 0;
-	_soundDataSize = 0;
 
 	_vibratoAndAMDepthBits = _curRegOffset = 0;
 
@@ -504,7 +481,7 @@ void AdLibDriver::setSoundData(uint8 *data, uint32 size) {
 	_soundDataSize = size;
 }
 
-void AdLibDriver::queueTrack(int track, int volume) {
+void AdLibDriver::startSound(int track, int volume) {
 	Common::StackLock lock(_mutex);
 
 	uint8 *trackData = getProgram(track);
@@ -624,7 +601,7 @@ void AdLibDriver::setupPrograms() {
 
 	if (retrySound.data) {
 		debugC(9, kDebugLevelSound, "AdLibDriver::setupPrograms(): WORKAROUND - Restarting skipped sound %d)", retrySound.id);
-		queueTrack(retrySound.id, retrySound.volume);
+		startSound(retrySound.id, retrySound.volume);
 	}
 }
 
diff --git a/engines/kyra/sound/drivers/pc_base.h b/engines/kyra/sound/drivers/pc_base.h
index b13b4a2..ce85950 100644
--- a/engines/kyra/sound/drivers/pc_base.h
+++ b/engines/kyra/sound/drivers/pc_base.h
@@ -34,28 +34,50 @@ namespace Kyra {
 
 class PCSoundDriver {
 public:
-	PCSoundDriver() {}
+	PCSoundDriver() : _soundData(0), _soundDataSize(0) {}
 	virtual ~PCSoundDriver() {}
 
 	virtual void initDriver() = 0;
 	virtual void setSoundData(uint8 *data, uint32 size) = 0;
-	virtual void queueTrack(int track, int volume) = 0;
+	virtual void startSound(int track, int volume) = 0;
 	virtual bool isChannelPlaying(int channel) const = 0;
 	virtual void stopAllChannels() = 0;
-	virtual int getSoundTrigger() const = 0;
-	virtual void resetSoundTrigger() = 0;
 
-	virtual void callback() = 0;
-
-	// AdLiB specific
-	virtual void setSyncJumpMask(uint16) {}
+	virtual int getSoundTrigger() const { return 0; }
+	virtual void resetSoundTrigger() {}
 
 	virtual void setMusicVolume(uint8 volume) = 0;
 	virtual void setSfxVolume(uint8 volume) = 0;
 
+	// AdLiB (Kyra 1) specific
+	virtual void setSyncJumpMask(uint16) {}
+
+protected:
+	uint8 *getProgram(int progId) {
+		const uint16 offset = READ_LE_UINT16(_soundData + 2 * progId);
+
+		// In case an invalid offset is specified we return nullptr to
+		// indicate an error. 0xFFFF seems to indicate "this is not a valid
+		// program/instrument". However, 0 is also invalid because it points
+		// inside the offset table itself. We also ignore any offsets outside
+		// of the actual data size.
+		// The original does not contain any safety checks and will simply
+		// read outside of the valid sound data in case an invalid offset is
+		// encountered.
+		if (offset == 0 || offset >= _soundDataSize) {
+			return nullptr;
+		} else {
+			return _soundData + offset;
+		}
+	}
+
+	uint8 *_soundData;
+	uint32 _soundDataSize;
+
+public:
 	static PCSoundDriver *createAdLib(Audio::Mixer *mixer, int version);
 #ifdef ENABLE_EOB
-	static PCSoundDriver *createPCSpk(Audio::Mixer *mixer);
+	static PCSoundDriver *createPCSpk(Audio::Mixer *mixer, bool pcJRMode);
 #endif
 };
 
diff --git a/engines/kyra/sound/drivers/pcspeaker_v1.cpp b/engines/kyra/sound/drivers/pcspeaker_v1.cpp
index 01dcbab..2d0a296 100644
--- a/engines/kyra/sound/drivers/pcspeaker_v1.cpp
+++ b/engines/kyra/sound/drivers/pcspeaker_v1.cpp
@@ -23,75 +23,371 @@
 #ifdef ENABLE_EOB
 
 #include "kyra/sound/drivers/pc_base.h"
-#include "audio/mixer.h"
-#include "audio/softsynth/pcspk.h"
+#include "audio/audiostream.h"
+#include "common/mutex.h"
 
 namespace Kyra {
 
-class PCSpeakerDriver : public PCSoundDriver {
+class PCSpeakerDriver : public PCSoundDriver, public Audio::AudioStream {
 public:
-	PCSpeakerDriver(Audio::Mixer *mixer);
+	PCSpeakerDriver(Audio::Mixer *mixer, bool pcJRMode);
 	virtual ~PCSpeakerDriver();
 
 	virtual void initDriver() override;
 	virtual void setSoundData(uint8 *data, uint32 size) override;
-	virtual void queueTrack(int track, int volume) override;
+	virtual void startSound(int id, int) override;
 	virtual bool isChannelPlaying(int channel) const override;
 	virtual void stopAllChannels() override;
-	virtual int getSoundTrigger() const override { return _soundTrigger; }
-	virtual void resetSoundTrigger() override { _soundTrigger = 0; }
-
-	virtual void callback() override;
 
 	virtual void setMusicVolume(uint8 volume) override;
-	virtual void setSfxVolume(uint8 volume) override;
+	virtual void setSfxVolume(uint8) override {}
+
+	void update();
+
+	// AudioStream interface
+	virtual int readBuffer(int16 *buffer, const int numSamples) override;
+	virtual bool isStereo() const override { return false; }
+	virtual int getRate() const override { return _outputRate; }
+	virtual bool endOfData() const override { return false; }
 
 private:
-	int _soundTrigger;
+	void noteOn(int chan, uint16 period);
+	void chanOff(int chan);
+	void generateSamples(int16 *buffer, int numSamples);
+
+	struct Channel {
+		Channel(uint8 attnDB) : curSample(32767.0 / pow(2.0, (double)attnDB / 6.0)),
+			dataPtr(0), timer(0), timerScale(0), repeatCounter1(0), repeatCounter2(0), period(-1), samplesLeft(0) {}
+		const uint8 *dataPtr;
+		int16 timer;
+		uint8 timerScale;
+		uint8 repeatCounter1;
+		uint8 repeatCounter2;
+		int32 period;
+		int32 curSample;
+		uint32 samplesLeft;
+	};
+
+	Channel **_channels;
+	int _numChannels;
+
+	const uint8 *_newTrackData;
+	const uint8 *_trackData;
+
+	Common::Mutex _mutex;
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _handle;
+
+	uint _outputRate;
+	int _samplesUpdateIntv;
+	int _samplesUpdateIntvRem;
+	int _samplesUpdateTmr;
+	int _samplesUpdateTmrRem;
+
+	int _masterVolume;
+	bool _ready;
+
+	const int _clock;
+	const int _updateRate;
+	const bool _pcJR;
+	const int _periodDiv;
+	const int _levelAdjust;
+	const uint16 * const _periodsTable;
+
+	static const uint16 _periodsPCSpk[96];
+	static const uint16 _periodsPCjr[96];
 };
 
-PCSpeakerDriver::PCSpeakerDriver(Audio::Mixer *mixer) : PCSoundDriver() {
+PCSpeakerDriver::PCSpeakerDriver(Audio::Mixer *mixer, bool pcJRMode) : PCSoundDriver(), _mixer(mixer), _samplesUpdateIntv(0), _samplesUpdateIntvRem(0),
+	_outputRate(0), _samplesUpdateTmr(0), _samplesUpdateTmrRem(0), _newTrackData(0), _trackData(0), _pcJR(pcJRMode), _numChannels(pcJRMode ? 3 : 1), _channels(0),
+		_clock(pcJRMode ? 111860 : 1193180), _updateRate(292), _masterVolume(63), _periodsTable(pcJRMode ? _periodsPCjr : _periodsPCSpk), _periodDiv(pcJRMode ? 2 : 2),
+	_levelAdjust(pcJRMode ? 1 : 0), _ready(false) {
+	_outputRate = _mixer->getOutputRate();
+	_samplesUpdateIntv = _outputRate / _updateRate;
+	_samplesUpdateIntvRem = _outputRate % _updateRate;
 
+	_channels = new Channel*[_numChannels];
+	assert(_channels);
+	for (int i = 0; i < _numChannels; ++i) {
+		_channels[i] = new Channel(i * 10);
+		assert(_channels[i]);
+	}
 }
 
 PCSpeakerDriver::~PCSpeakerDriver() {
+	_ready = false;
+	_mixer->stopHandle(_handle);
 
+	if (_channels) {
+		for (int i = 0; i < _numChannels; ++i)
+			delete _channels[i];
+		delete[] _channels;
+	}
 }
 
 void PCSpeakerDriver::initDriver() {
-
+	if (_ready)
+		return;
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+	_ready = true;
 }
 
 void PCSpeakerDriver::setSoundData(uint8 *data, uint32 size) {
+	Common::StackLock lock(_mutex);
+	if (!_ready)
+		return;
 
-}
+	if (_soundData) {
+		delete[] _soundData;
+		_soundData = 0;
+	}
 
-void PCSpeakerDriver::queueTrack(int track, int volume) {
+	_soundData = data;
+	_soundDataSize = size;
+}
 
+void PCSpeakerDriver::startSound(int id, int) {
+	Common::StackLock lock(_mutex);
+	if (!_ready)
+		return;
+	_newTrackData = getProgram(id & 0x7F);
 }
 
 bool PCSpeakerDriver::isChannelPlaying(int channel) const {
-	return true;
+	Common::StackLock lock(_mutex);
+	if (!_ready)
+		return false;
+	return _trackData;
 }
 
 void PCSpeakerDriver::stopAllChannels() {
+	Common::StackLock lock(_mutex);
+	if (!_ready)
+		return;
+	for (int i = 0; i < _numChannels; ++i)
+		chanOff(i);
+	_trackData = 0;
+}
 
+void PCSpeakerDriver::setMusicVolume(uint8 volume) {
+	Common::StackLock lock(_mutex);
+	_masterVolume = volume >> 2;
 }
 
-void PCSpeakerDriver::callback() {
+void PCSpeakerDriver::update() {
+	Common::StackLock lock(_mutex);
+	if (!_ready)
+		return;
+
+	if (_newTrackData) {
+		_trackData = _newTrackData;
+		_newTrackData = 0;
+
+		for (int i = _numChannels - 1; i >= 0; --i) {
+			_channels[i]->dataPtr = _trackData;
+			_channels[i]->timer = i * 35;
+			_channels[i]->timerScale = 1;
+		}
+	}
+
+	for (int i = _numChannels - 1; i >= 0; --i) {
+		const uint8 *pos = _channels[i]->dataPtr;
+		if (!pos)
+			continue;
+
+		for (bool runloop = true; runloop; ) {
+			if (--_channels[i]->timer > -1)
+				break;
+			_channels[i]->timer = 0;
+
+			int8 cmd = (int8)*pos++;
+			if (cmd >= 0) {
+				if (cmd > 95)
+					cmd = 0;
+
+				noteOn(i, _periodsTable[cmd]);
+				uint8 nextTimer = 1 + *pos++;
+				_channels[i]->timer = _channels[i]->timerScale * nextTimer;
+
+			} else {
+				switch (cmd) {
+				case -23: {
+					uint16 ts = _channels[i]->timerScale + *pos++;
+					_channels[i]->timerScale = (uint8)MIN<uint16>(ts, 0xFF);
+				} break;
+				
+				case -24: {
+					int16 ts = _channels[i]->timerScale - *pos++;
+					_channels[i]->timerScale = (uint8)MAX<int16>(ts, 1);
+				} break;
+				
+				case -26: {
+					uint16 prd = _clock / READ_LE_UINT16(pos);
+					if (_pcJR && prd >= 0x400)
+						prd = 0x3FF;
+					pos += 2;
+					noteOn(i, prd);
+					uint8 nextTimer = 1 + *pos++;
+					_channels[i]->timer = _channels[i]->timerScale * nextTimer;
+				} break;
+				
+				case -30: {
+					_channels[i]->timerScale = *pos++;
+					if (!_channels[i]->timerScale)
+						_channels[i]->timerScale = 1;
+				} break;
+
+				case -46: {
+					if (--_channels[i]->repeatCounter2)
+						pos -= *pos;
+					else
+						pos += 2;
+				} break;
 
+				case -47: {
+					_channels[i]->repeatCounter2 = *pos++;
+					if (!_channels[i]->repeatCounter2)
+						_channels[i]->repeatCounter2 = 1;
+				} break;
+
+				case -50: {
+					if (--_channels[i]->repeatCounter1)
+						pos -= *pos;
+					else
+						pos += 2;
+				} break;
+
+				case -51: {
+					_channels[i]->repeatCounter1 = *pos++;
+					if (!_channels[i]->repeatCounter1)
+						_channels[i]->repeatCounter1 = 1;
+				} break;
+
+				default:
+					chanOff(i);
+					pos = 0;
+					runloop = false;
+				}
+			}
+		}
+
+		_channels[i]->dataPtr = pos;
+	}
 }
 
-void PCSpeakerDriver::setMusicVolume(uint8 volume) {
+int PCSpeakerDriver::readBuffer(int16 *buffer, const int numSamples) {
+	Common::StackLock lock(_mutex);
+	if (!_ready)
+		return 0;
+
+	int render = 0;
 
+	for (int samplesLeft = numSamples; samplesLeft; samplesLeft -= render) {
+		if (_samplesUpdateTmr <= 0) {
+			_samplesUpdateTmr += _samplesUpdateIntv;
+			update();
+		}
+
+		_samplesUpdateTmrRem += _samplesUpdateIntvRem;
+		while (_samplesUpdateTmrRem >= _updateRate) {
+			_samplesUpdateTmr++;
+			_samplesUpdateTmrRem -= _updateRate;
+		}
+
+		render = MIN<int>(_samplesUpdateTmr, samplesLeft);
+		_samplesUpdateTmr -= render;
+
+		generateSamples(buffer, render);
+		buffer += render;
+	}
+	
+	return numSamples;
 }
 
-void PCSpeakerDriver::setSfxVolume(uint8 volume) {
+void PCSpeakerDriver::noteOn(int chan, uint16 period) {
+	if (chan >= _numChannels)
+		return;
+
+	if (period == 0) {
+		chanOff(chan);
+		return;
+	}
+
+	uint32 p = (_outputRate << 10) / ((_clock << 10) / period);
+	if (_channels[chan]->period == -1 || _channels[chan]->samplesLeft == 0)
+		_channels[chan]->samplesLeft = p / _periodDiv;
+	_channels[chan]->period = p & 0xFFFF;
+}
 
+void PCSpeakerDriver::chanOff(int chan) {
+	if (chan >= _numChannels)
+		return;
+	_channels[chan]->period = -1;
 }
 
-PCSoundDriver *PCSoundDriver::createPCSpk(Audio::Mixer *mixer) {
-	return new PCSpeakerDriver(mixer);
+void PCSpeakerDriver::generateSamples(int16 *buffer, int numSamples) {
+	int render = 0;
+	for (int samplesLeft = numSamples; samplesLeft; samplesLeft -= render) {
+		render = samplesLeft;
+
+		for (int i = _numChannels - 1; i >= 0; --i)
+			if (_channels[i]->period != -1)
+				render = MIN<int>(render, _channels[i]->samplesLeft);
+
+		int32 smp = 0;
+		for (int i = _numChannels - 1; i >= 0; --i)
+			if (_channels[i]->period != -1)
+				smp += _channels[i]->curSample;
+		smp = (smp * _masterVolume) >> (8 + _levelAdjust);
+
+		Common::fill<int16*, int16>(buffer, &buffer[render], smp);
+		buffer += render;
+
+		for (int i = _numChannels - 1; i >= 0; --i) {
+			if (_channels[i]->period == -1)
+				continue;
+
+			_channels[i]->samplesLeft -= render;
+			if (_channels[i]->samplesLeft == 0) {
+				_channels[i]->samplesLeft = _channels[i]->period / _periodDiv;
+				_channels[i]->curSample = ~_channels[i]->curSample;
+			}
+		}
+	}
+}
+
+const uint16 PCSpeakerDriver::_periodsPCSpk[96] = {
+	0x0000, 0xfdff, 0xefa2, 0xe241, 0xd582, 0xc998, 0xbe3d, 0xb38a,
+	0xa97c, 0x9ff2, 0x96fc, 0x8e89, 0x8683, 0x7ef7, 0x77d9, 0x7121,
+	0x6ac7, 0x64c6, 0x5f1f, 0x59ca, 0x54be, 0x4ffd, 0x4b7e, 0x4742,
+	0x4342, 0x3f7b, 0x3bdb, 0x388f, 0x3562, 0x3263, 0x2f8f, 0x2ce4,
+	0x2a5f, 0x27fe, 0x25c0, 0x23a1, 0x21a1, 0x1fbe, 0x1df6, 0x1c48,
+	0x1ab1, 0x1932, 0x17c8, 0x1672, 0x1530, 0x13ff, 0x12e0, 0x11d1,
+	0x10d1, 0x0fdf, 0x0efb, 0x0e24, 0x0d59, 0x0c99, 0x0be4, 0x0b39,
+	0x0a98, 0x0a00, 0x0970, 0x08e8, 0x0868, 0x07f0, 0x077e, 0x0712,
+	0x06ac, 0x064c, 0x05f2, 0x059d, 0x054c, 0x0500, 0x04b8, 0x0474,
+	0x0434, 0x03f8, 0x03bf, 0x0382, 0x0356, 0x0326, 0x02f9, 0x02ce,
+	0x02a6, 0x0280, 0x025c, 0x023a, 0x021a, 0x01fc, 0x01df, 0x01c4,
+	0x01ab, 0x0193, 0x017c, 0x0167, 0x0153, 0x0140, 0x012e, 0x011d
+};
+
+const uint16 PCSpeakerDriver::_periodsPCjr[96] = {
+	0x0000, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
+	0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
+	0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
+	0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
+	0x03f9, 0x03c0, 0x038a, 0x0357, 0x0327, 0x02fa, 0x02cf, 0x02a7,
+	0x0281, 0x025d, 0x023b, 0x021b, 0x01fc, 0x01e0, 0x01c5, 0x01ac,
+	0x0194, 0x017d, 0x0168, 0x0153, 0x0140, 0x012e, 0x011d, 0x010d,
+	0x00fe, 0x00f0, 0x00e2, 0x00d6, 0x00ca, 0x00be, 0x00b4, 0x00aa,
+	0x00a0, 0x0097, 0x008f, 0x0087, 0x007f, 0x0078, 0x0071, 0x006b,
+	0x0065, 0x005f, 0x005a, 0x0054, 0x0050, 0x004c, 0x0047, 0x0043,
+	0x0040, 0x003c, 0x0039, 0x0035, 0x0032, 0x0030, 0x002d, 0x002a,
+	0x0028, 0x0026, 0x0024, 0x0022, 0x0020, 0x001e, 0x001c, 0x001b
+};
+
+PCSoundDriver *PCSoundDriver::createPCSpk(Audio::Mixer *mixer, bool pcJRMode) {
+	return new PCSpeakerDriver(mixer, pcJRMode);
 }
 
 } // End of namespace Kyra
diff --git a/engines/kyra/sound/sound.h b/engines/kyra/sound/sound.h
index 531496b..e5a063b 100644
--- a/engines/kyra/sound/sound.h
+++ b/engines/kyra/sound/sound.h
@@ -101,6 +101,7 @@ public:
 		kTowns,
 		kPC98,
 		kPCSpkr,
+		kPCjr,
 		kAmiga
 	};
 
diff --git a/engines/kyra/sound/sound_pc_v1.cpp b/engines/kyra/sound/sound_pc_v1.cpp
index 94b489b..4d52993 100644
--- a/engines/kyra/sound/sound_pc_v1.cpp
+++ b/engines/kyra/sound/sound_pc_v1.cpp
@@ -74,10 +74,10 @@ SoundPC_v1::SoundPC_v1(KyraEngine_v1 *vm, Audio::Mixer *mixer, kType type)
 	}
 
 	// Correct the type to someting we support. NullSound is treated as a silent AdLib driver.
-	if (_type != kAdLib && _type != kPCSpkr)
+	if (_type != kAdLib && _type != kPCSpkr && _type != kPCjr)
 		_type = kAdLib;
 
-	_driver = (type == kAdLib) ? PCSoundDriver::createAdLib(mixer, _version) : PCSoundDriver::createPCSpk(mixer);
+	_driver = (type == kAdLib) ? PCSoundDriver::createAdLib(mixer, _version) : PCSoundDriver::createPCSpk(mixer, _type == kPCjr);
 	assert(_driver);
 }
 
@@ -165,7 +165,7 @@ void SoundPC_v1::play(uint8 track, uint8 volume) {
 	if ((soundId == 0xFFFF && _version == 4) || (soundId == 0xFF && _version < 4) || !_soundDataPtr)
 		return;
 
-	_driver->queueTrack(soundId, volume);
+	_driver->startSound(soundId, volume);
 }
 
 void SoundPC_v1::beginFadeOut() {
@@ -201,7 +201,7 @@ bool SoundPC_v1::hasSoundFile(uint file) const {
 }
 
 void SoundPC_v1::loadSoundFile(uint file) {
-	if (_version == 1 && _type == kPCSpkr)
+	if (_version == 1 && (_type == kPCSpkr || _type == kPCjr))
 		file += 1;
 	if (file < res()->fileListSize)
 		internalLoadFile(res()->fileList[file]);


Commit: 2c91759a05c267b5530c61a0fd0d03bdce99b4be
    https://github.com/scummvm/scummvm/commit/2c91759a05c267b5530c61a0fd0d03bdce99b4be
Author: athrxx (athrxx at scummvm.org)
Date: 2020-02-01T21:09:49+01:00

Commit Message:
KYRA: (LOK) - add missing delay (bug #11330)

Changed paths:
    engines/kyra/sequence/sequences_lok.cpp


diff --git a/engines/kyra/sequence/sequences_lok.cpp b/engines/kyra/sequence/sequences_lok.cpp
index 0d3b5fc..e6e6457 100644
--- a/engines/kyra/sequence/sequences_lok.cpp
+++ b/engines/kyra/sequence/sequences_lok.cpp
@@ -1120,9 +1120,12 @@ int KyraEngine_LoK::seq_playEnd() {
 				_finalA->displayFrame(i, 0, 8, 8, 0, 0, 0);
 				_screen->updateScreen();
 			}
-			delete _finalA;
 
+			nextTime = _system->getMillis() + 300 * _tickLength;
+			delete _finalA;
 			_finalA = 0;
+			delayUntil(nextTime);
+
 			seq_playEnding();
 			return 1;
 		}


Commit: 0239ed5e963da9ce6f824386b87ef1144d3c1d9d
    https://github.com/scummvm/scummvm/commit/0239ed5e963da9ce6f824386b87ef1144d3c1d9d
Author: athrxx (athrxx at scummvm.org)
Date: 2020-02-01T21:09:50+01:00

Commit Message:
KYRA: (HOF) - fix bug #11331

This reverts the change from 1d5fd780 which was clearly wrong and caused this new bug (no idea what I was thinking there).  To prevent the revival of bug #3721 I now added the proper code for a fix after tracking the whole bug with the DOSBox debugger.

Changed paths:
    engines/kyra/engine/kyra_hof.cpp
    engines/kyra/engine/kyra_hof.h
    engines/kyra/engine/kyra_v1.cpp
    engines/kyra/kyra_v1.h
    engines/kyra/script/script_hof.cpp
    engines/kyra/script/script_v1.cpp


diff --git a/engines/kyra/engine/kyra_hof.cpp b/engines/kyra/engine/kyra_hof.cpp
index 43849d5..11d82f2 100644
--- a/engines/kyra/engine/kyra_hof.cpp
+++ b/engines/kyra/engine/kyra_hof.cpp
@@ -1420,10 +1420,10 @@ void KyraEngine_HoF::snd_playVoiceFile(int id) {
 	sprintf(vocFile, "%07d", id);
 	if (_sound->isVoicePresent(vocFile)) {
 		// Unlike the original I have added a timeout here. I have chosen a size that makes sure that it
-		// won't get triggered in any of the bug #11309 or bug #3721 situations, but still avoids infinite
-		// hangups if something goes wrong.
+		// won't get triggered in bug #11309 or similiar situations, but still avoids infinite hangups
+		// if something goes wrong.
 		uint32 timeout = _system->getMillis() + 5000;
-		while (_sound->voiceIsPlaying() && _system->getMillis() < timeout && !skipFlag())
+		while (snd_voiceIsPlaying() && _system->getMillis() < timeout && !skipFlag() && !shouldQuit())
 			delay(10);
 		_chatEndTime += (_system->getMillis() + 5000 - timeout);
 		if (_system->getMillis() >= timeout && !skipFlag())
diff --git a/engines/kyra/engine/kyra_hof.h b/engines/kyra/engine/kyra_hof.h
index 04ee2b6..60e2d1f 100644
--- a/engines/kyra/engine/kyra_hof.h
+++ b/engines/kyra/engine/kyra_hof.h
@@ -522,6 +522,7 @@ protected:
 	int o2_wipeDownMouseItem(EMCState *script);
 	int o2_getElapsedSecs(EMCState *script);
 	int o2_getTimerDelay(EMCState *script);
+	int o2_playCompleteSoundEffect(EMCState *script);
 	int o2_delaySecs(EMCState *script);
 	int o2_setTimerDelay(EMCState *script);
 	int o2_setScaleTableItem(EMCState *script);
diff --git a/engines/kyra/engine/kyra_v1.cpp b/engines/kyra/engine/kyra_v1.cpp
index b83aa3b..97cb69e 100644
--- a/engines/kyra/engine/kyra_v1.cpp
+++ b/engines/kyra/engine/kyra_v1.cpp
@@ -55,6 +55,7 @@ KyraEngine_v1::KyraEngine_v1(OSystem *system, const GameFlags &flags)
 	_trackMapSize = 0;
 	_lastMusicCommand = -1;
 	_curSfxFile = _curMusicTheme = -1;
+	_preventScriptSfx = false;
 
 	_gameToLoad = -1;
 
diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h
index 03c77b0..1f15f0e 100644
--- a/engines/kyra/kyra_v1.h
+++ b/engines/kyra/kyra_v1.h
@@ -365,6 +365,8 @@ protected:
 	const int8 *_trackMap;
 	int _trackMapSize;
 
+	bool _preventScriptSfx;
+
 	virtual int convertVolumeToMixer(int value);
 	virtual int convertVolumeFromMixer(int value);
 
diff --git a/engines/kyra/script/script_hof.cpp b/engines/kyra/script/script_hof.cpp
index ebc10c2..74c0638 100644
--- a/engines/kyra/script/script_hof.cpp
+++ b/engines/kyra/script/script_hof.cpp
@@ -445,6 +445,23 @@ int KyraEngine_HoF::o2_getTimerDelay(EMCState *script) {
 	return _timer->getDelay(stackPos(0));
 }
 
+int KyraEngine_HoF::o2_playCompleteSoundEffect(EMCState *script) {
+	debugC(3, kDebugLevelScriptFuncs, "KyraEngine_HoF::o2_playCompleteSoundEffect(%p) (%d)", (const void *)script, stackPos(0));
+	snd_playSoundEffect(stackPos(0));
+	// The following code is derived from HOFCD MAINWIN.EXE. MAINDOS.EXE has the "normal" o1_playSoundEffect() opcode here (the
+	// way we had implemented it before this fix). This was actually the cause of bug #3721. I have verified with the DOSBox
+	// debugger that it really runs the MAINWIN.EXE code and in particular this opcode with the wait loop.
+	// This loop waits for complete silence on all 4 pcm sound channels. Meanwhile it prevents any scripts from starting more
+	// sound effects while the loop is active (actually, only opcode 0x59 gets blocked). The whole thing looks a bit like a last
+	// minute hack....
+	while (_sound->voiceIsPlaying() && !skipFlag() && !shouldQuit()) {
+		_preventScriptSfx = true;
+		delay(10, true);
+		_preventScriptSfx = false;
+	}
+	return 0;
+}
+
 int KyraEngine_HoF::o2_delaySecs(EMCState *script) {
 	debugC(3, kDebugLevelScriptFuncs, "KyraEngine_HoF::o2_delaySecs(%p) (%d)", (const void *)script, stackPos(0));
 	delay(stackPos(0) * 1000, true);
@@ -1548,7 +1565,7 @@ void KyraEngine_HoF::setupOpcodeTable() {
 	Opcode(o2_getElapsedSecs);
 	// 0x34
 	Opcode(o2_getTimerDelay);
-	Opcode(o1_playSoundEffect);
+	Opcode(o2_playCompleteSoundEffect);
 	Opcode(o2_delaySecs);
 	Opcode(o2_delay);
 	// 0x38
diff --git a/engines/kyra/script/script_v1.cpp b/engines/kyra/script/script_v1.cpp
index 356460d..b053358 100644
--- a/engines/kyra/script/script_v1.cpp
+++ b/engines/kyra/script/script_v1.cpp
@@ -118,7 +118,8 @@ int KyraEngine_v1::o1_blockOutWalkableRegion(EMCState *script) {
 
 int KyraEngine_v1::o1_playSoundEffect(EMCState *script) {
 	debugC(3, kDebugLevelScriptFuncs, "KyraEngine_v1::o1_playSoundEffect(%p) (%d)", (const void *)script, stackPos(0));
-	snd_playSoundEffect(stackPos(0));
+	if (!_preventScriptSfx)
+		snd_playSoundEffect(stackPos(0));
 	return 0;
 }
 




More information about the Scummvm-git-logs mailing list