[Scummvm-git-logs] scummvm master -> 6694cdcd7389f7e5452733c8bfcfda9585223d3d

athrxx athrxx at scummvm.org
Sun Jul 14 22:11:01 CEST 2019


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

Summary:
9afdde2601 SCUMM: add Amiga iMuse sound driver
0899ecc987 SCUMM: hook up Amiga MI2 + INDY4 to new sound driver
d1b64aab0c SCUMM: (iMuse/Amiga) - improve accuracy
01f99f1a0a SCUMM: imuse driver directory cleanup
4ee4d2d9af SCUMM: (FM-Towns Audio) remove TODO
19643175a8 SCUMM: limit 'Unrecognized base tag' warning to valid cases
6694cdcd73 SCUMM: update news (Amiga iMuse support)


Commit: 9afdde2601b64f724c2e5c4d4b43ba04709a27f1
    https://github.com/scummvm/scummvm/commit/9afdde2601b64f724c2e5c4d4b43ba04709a27f1
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T20:56:27+02:00

Commit Message:
SCUMM: add Amiga iMuse sound driver

(applies to MI2 and INDY4)

Changed paths:
  A engines/scumm/imuse/drivers/amiga.cpp
  A engines/scumm/imuse/drivers/amiga.h
    audio/mididrv.h
    engines/scumm/POTFILES
    engines/scumm/imuse/imuse.cpp
    engines/scumm/module.mk


diff --git a/audio/mididrv.h b/audio/mididrv.h
index 1eaf415..7fffb99 100644
--- a/audio/mididrv.h
+++ b/audio/mididrv.h
@@ -274,6 +274,7 @@ public:
 	virtual void volume(byte value) { controlChange(7, value); }
 	virtual void panPosition(byte value) { controlChange(10, value); }
 	virtual void pitchBendFactor(byte value) = 0;
+	virtual void transpose(int8 value) {}
 	virtual void detune(byte value) { controlChange(17, value); }
 	virtual void priority(byte value) { }
 	virtual void sustain(bool value) { controlChange(64, value ? 1 : 0); }
diff --git a/engines/scumm/POTFILES b/engines/scumm/POTFILES
index 039aa16..bdd18b6 100644
--- a/engines/scumm/POTFILES
+++ b/engines/scumm/POTFILES
@@ -5,4 +5,4 @@ engines/scumm/input.cpp
 engines/scumm/scumm.cpp
 engines/scumm/players/player_v3m.cpp
 engines/scumm/players/player_v5m.cpp
-
+engines/scumm/imuse/drivers/amiga.cpp
diff --git a/engines/scumm/imuse/drivers/amiga.cpp b/engines/scumm/imuse/drivers/amiga.cpp
new file mode 100644
index 0000000..4942fd5
--- /dev/null
+++ b/engines/scumm/imuse/drivers/amiga.cpp
@@ -0,0 +1,875 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/scumm/imuse/drivers/amiga.h"
+#include "audio/mixer.h"
+#include "common/file.h"
+#include "common/translation.h"
+#include "gui/error.h"
+
+namespace Scumm {
+
+struct Instrument_Amiga {
+	struct Samples {
+		uint16 rate;
+		uint16 baseNote;
+		int16 noteRangeMin;
+		int16 noteRangeMax;
+		int16 sustainLevel;
+		uint16 type;
+		uint32 numSamples;
+		uint32 dr_offset;
+		uint32 dr_numSamples;
+		int16 levelFadeDelayAT;
+		int16 levelFadeDelayRL;
+		int16 levelFadeTriggerRL;
+		int16 levelFadeDelayDC;
+		const int8 *data;
+	};
+	Samples samples[8];
+	int numBlocks;
+};
+
+class SoundChannel_Amiga {
+public:
+	SoundChannel_Amiga(IMuseDriver_Amiga *driver, int id, Instrument_Amiga *instruments);
+	~SoundChannel_Amiga();
+
+	static SoundChannel_Amiga *allocate(int prio);
+	void connect(IMusePart_Amiga *part);
+	void disconnect();
+
+	void noteOn(byte note, byte velocity, byte program, int8 transpose, int16 pitchBend);
+	void ctrl_volume(uint8 volume);
+	void ctrl_sustain(bool sustainToggle);
+	void transposePitchBend(int8 transpose, int16 pitchBend);
+
+	void updateLevel();
+	void updateEnvelope();
+
+	uint8 note() const { return _note; }
+	SoundChannel_Amiga *next() const { return _next; }
+
+private:
+	void keyOn(const int8 *data1, uint16 data1Size, const int8 *data2, uint16 data2Size, uint16 period);
+	void keyOff();
+	void setRepeatData(const int8 *data, uint16 size);
+	void setVelocity(uint8 velo, int delay);
+	void setVolume(uint8 volume);
+
+	uint16 calculatePeriod(int16 tone, uint8 baseNote, uint16 rate);
+
+	void createVolumeTable();
+
+	SoundChannel_Amiga *_prev, *_next;
+	IMusePart_Amiga *_assign;
+
+	uint8 _id;
+	uint8 _note;
+	int8 _transpose;
+	bool _sustain;
+
+	IMuseDriver_Amiga *_driver;
+
+	static uint8 _allocCurPos;
+	static SoundChannel_Amiga *_channels[4];
+
+	enum EnvelopeState {
+		kReady = 0,
+		kRelease = 1,
+		kDecay = 2,
+		kAttack = 3,
+		kRestart = 4
+	};
+
+	struct IOUnit {
+		IOUnit() : program(0), block(0), volume(63), currentLevel(0), fadeTargetLevel(0), fadeLevelDelta(0), fadeLevelMod(0), levelFadeTriggerDC(0), fadeLevelTicks(0),
+			fadeLevelTicker(0), fadeLevelDuration(0), releaseData(0), releaseDataSize(0), repeatData(0), repeatDataSize(0), envelopeState(kReady) {}
+		uint8 program;
+		uint8 block;
+		uint8 volume;
+		uint8 currentLevel;
+		uint8 fadeTargetLevel;
+		uint8 fadeLevelDelta;
+		uint16 fadeLevelTicks;
+		int8 fadeLevelMod;
+		bool levelFadeTriggerDC;
+		uint32 fadeLevelTicker;
+		uint32 fadeLevelDuration;
+		const int8 *releaseData;
+		uint16 releaseDataSize;
+		const int8 *repeatData;
+		uint16 repeatDataSize;
+		uint8 envelopeState;
+	};
+
+	IOUnit _ioUnit;
+
+	const Instrument_Amiga *_instruments;
+
+	static const int8 _muteData[16];
+	static const uint8 *_volTable;
+};
+
+class IMusePart_Amiga : public MidiChannel {
+public:
+	IMusePart_Amiga(IMuseDriver_Amiga *driver, int id);
+	~IMusePart_Amiga() {}
+
+	MidiDriver *device() { return _driver; }
+	byte getNumber() { return _id; }
+	bool allocate();
+	void release();
+
+	void send(uint32 b);
+
+	void noteOff(byte note);
+	void noteOn(byte note, byte velocity);
+	void controlChange(byte control, byte value);
+	void programChange(byte program);
+	void pitchBend(int16 bend);
+	void pitchBendFactor(byte value);
+	void transpose(int8 value);
+
+	void priority(byte value);
+	void sysEx_customInstrument(uint32 type, const byte *instr) {}
+
+	int getPriority() const { return _priority; }
+	SoundChannel_Amiga *getChannel() const { return _out; }
+	void setChannel(SoundChannel_Amiga *chan) { _out = chan; }
+
+private:
+	void controlModulationWheel(byte value);
+	void controlVolume(byte value);
+	void controlSustain(byte value);
+
+	uint8 _priority;
+	uint8 _program;
+	int8 _modulation;
+	int8 _transpose;
+	int16 _pitchBend;
+	uint8 _pitchBendSensitivity;
+	uint16 _volume;
+	bool _sustain;
+	bool _allocated;
+	const uint8 _id;
+	SoundChannel_Amiga *_out;
+	IMuseDriver_Amiga *_driver;
+};
+
+SoundChannel_Amiga::SoundChannel_Amiga(IMuseDriver_Amiga *driver, int id, Instrument_Amiga *instruments) : _driver(driver), _id(id), _instruments(instruments), 
+	_assign(0), _next(0), _prev(0), _sustain(false), _note(0) {
+	assert(id > -1 && id < 4);
+	_channels[id] = this;
+	createVolumeTable();
+}
+
+SoundChannel_Amiga::~SoundChannel_Amiga() {
+	_channels[_id] = 0;
+
+	// delete volume table only if this is the last remaining SoundChannel_Amiga object
+	for (int i = 0; i < 4; ++i) {
+		if (_channels[i])
+			return;
+	}
+
+	delete[] _volTable;
+	_volTable = 0;
+}
+ 
+SoundChannel_Amiga *SoundChannel_Amiga::allocate(int prio) {
+	SoundChannel_Amiga *res = 0;
+
+	for (int i = 0; i < 4; i++) {
+		if (++_allocCurPos == 4)
+			_allocCurPos = 0;
+
+		SoundChannel_Amiga *temp = _channels[_allocCurPos];
+		if (!temp->_assign)
+			return temp;
+
+		if (temp->_next)
+			continue;
+
+		if (prio >= temp->_assign->getPriority()) {
+			res = temp;
+			prio = temp->_assign->getPriority();
+		}
+	}
+
+	if (res)
+		res->disconnect();
+
+	return res;
+}
+
+void SoundChannel_Amiga::connect(IMusePart_Amiga *part) {
+	if (!part)
+		return;
+
+	_assign = part;
+	_next = part->getChannel();
+	_prev = 0;
+	part->setChannel(this);
+	if (_next)
+		_next->_prev = this;
+}
+
+void SoundChannel_Amiga::disconnect() {
+	keyOff();
+
+	SoundChannel_Amiga *p = _prev;
+	SoundChannel_Amiga *n = _next;
+
+	if (n)
+		n->_prev = p;
+	if (p)
+		p->_next = n;
+	else
+		_assign->setChannel(n);
+	_assign = 0;
+}
+
+void SoundChannel_Amiga::noteOn(byte note, byte volume, byte program, int8 transpose, int16 pitchBend) {
+	if (program > 128)
+		program = 128;
+
+	if (program != 128 && !_instruments[program].samples[0].data)
+		program = 128;
+
+	_note = note;
+	_sustain = false;
+
+	_ioUnit.block = 0;
+	_ioUnit.program = program;
+	const Instrument_Amiga::Samples *s = &_instruments[program].samples[_ioUnit.block];
+	int16 pnote = note + transpose + (pitchBend >> 7);
+
+	if (_instruments[program].numBlocks > 1) {
+		for (int i = 0; i < _instruments[program].numBlocks; ++i) {
+			if (pnote >= _instruments[program].samples[i].noteRangeMin && pnote <= _instruments[program].samples[i].noteRangeMax) {
+				_ioUnit.block = i;
+				s = &_instruments[program].samples[_ioUnit.block];
+				break;
+			}
+		}
+	}
+
+	_driver->disableChannel(_id);
+	setVelocity(0, 0);
+	setVolume(volume);
+	
+	if (s->type > 1)
+		return;
+	
+	uint16 period = calculatePeriod(pitchBend + ((_note + transpose) << 7), s->baseNote, s->rate);
+
+	if (s->type == 1) {
+		keyOn(s->data, s->numSamples, 0, 0, period);
+		setRepeatData(0, 0);
+	} else {
+		if (s->dr_numSamples) {
+			keyOn(s->data, s->dr_numSamples, s->data + s->dr_offset, s->dr_numSamples - s->dr_offset, period);
+			setRepeatData(s->data + s->dr_numSamples, s->numSamples - s->dr_numSamples);
+		} else {
+			keyOn(s->data, s->numSamples, s->data + s->dr_offset, s->numSamples - s->dr_offset, period);
+			setRepeatData(0, 0);
+		}
+	}
+}
+
+void SoundChannel_Amiga::ctrl_volume(uint8 volume) {
+	setVolume(volume);
+}
+
+void SoundChannel_Amiga::ctrl_sustain(bool sustainToggle) {
+	if (_sustain && !sustainToggle)
+		disconnect();
+	else if (sustainToggle)
+		_sustain = true;
+}
+
+void SoundChannel_Amiga::transposePitchBend(int8 transpose, int16 pitchBend) {
+	//_transpose = transpose;
+	const Instrument_Amiga::Samples *s = &_instruments[_ioUnit.program].samples[_ioUnit.block];
+	_driver->setChannelPeriod(_id, calculatePeriod(((_note + transpose) << 7) + pitchBend, s->baseNote, s->rate));
+}
+
+void SoundChannel_Amiga::updateLevel() {
+	if (!_ioUnit.fadeLevelMod)
+		return;
+
+	_ioUnit.fadeLevelDuration += _ioUnit.fadeLevelDelta;
+	if (_ioUnit.fadeLevelDuration <= _ioUnit.fadeLevelTicker)
+		return;
+
+	while (_ioUnit.fadeLevelDuration > _ioUnit.fadeLevelTicker && _ioUnit.currentLevel != _ioUnit.fadeTargetLevel) {
+		_ioUnit.fadeLevelTicker += _ioUnit.fadeLevelTicks;
+		_ioUnit.currentLevel += _ioUnit.fadeLevelMod;
+	}
+
+	_driver->setChannelVolume(_id, _volTable[(_ioUnit.volume << 5) + _ioUnit.currentLevel]);
+
+	if (_ioUnit.currentLevel != _ioUnit.fadeTargetLevel)
+		return;
+
+	_ioUnit.fadeLevelMod = 0;
+	if (!_ioUnit.levelFadeTriggerDC)
+		return;
+
+	const Instrument_Amiga::Samples *s = &_instruments[_ioUnit.program].samples[_ioUnit.block];
+	setVelocity(s->sustainLevel >> 1, s->levelFadeDelayDC);
+}
+
+void SoundChannel_Amiga::updateEnvelope() {
+	if (_ioUnit.envelopeState == kReady)
+		return;
+
+	uint8 envCur = _ioUnit.envelopeState--;
+	if (envCur == kAttack) {
+		const Instrument_Amiga::Samples *s = &_instruments[_ioUnit.program].samples[_ioUnit.block];
+		_driver->enableChannel(_id);
+		if (s->levelFadeDelayDC) {
+			setVelocity(31, s->levelFadeDelayAT);
+			if (s->levelFadeDelayAT)
+				_ioUnit.levelFadeTriggerDC = true;
+			else
+				setVelocity(s->sustainLevel >> 1, s->levelFadeDelayDC);
+		} else {
+			setVelocity(s->sustainLevel >> 1, s->levelFadeDelayAT);
+		}
+	}
+
+	if (envCur == kRelease) {
+		_driver->setChannelSampleStart(_id, _ioUnit.releaseData);
+		_driver->setChannelSampleLen(_id, _ioUnit.releaseDataSize);
+	}
+}
+
+void SoundChannel_Amiga::keyOn(const int8 *attackData, uint16 attackDataSize, const int8 *releaseData, uint16 releaseDataSize, uint16 period) {
+	_driver->setChannelSampleStart(_id, attackData);
+	_driver->setChannelSampleLen(_id, attackDataSize >> 1);
+	_driver->setChannelPeriod(_id, period);
+
+	if (releaseData) {
+		_ioUnit.releaseData = releaseData;
+		_ioUnit.releaseDataSize = releaseDataSize >> 1;
+	} else {
+		_ioUnit.releaseData = _muteData;
+		_ioUnit.releaseDataSize = ARRAYSIZE(_muteData) >> 1;
+	}
+
+	_ioUnit.envelopeState = kRestart;
+}
+
+void SoundChannel_Amiga::keyOff() {
+	_ioUnit.levelFadeTriggerDC = 0;
+	if (_ioUnit.repeatData) {
+		_driver->setChannelSampleStart(_id, _ioUnit.repeatData);
+		_driver->setChannelSampleLen(_id, _ioUnit.repeatDataSize);
+		_ioUnit.releaseData = _muteData;
+		_ioUnit.releaseDataSize = ARRAYSIZE(_muteData) >> 1;
+		_ioUnit.envelopeState = kDecay;
+	} else {
+		_ioUnit.envelopeState = kReady;
+	}
+
+	if (_instruments[_ioUnit.program].samples[_ioUnit.block].levelFadeTriggerRL)
+		setVelocity(0, _instruments[_ioUnit.program].samples[_ioUnit.block].levelFadeDelayRL);
+}
+
+void SoundChannel_Amiga::setRepeatData(const int8 *data, uint16 size) {
+	_ioUnit.repeatData = data;
+	_ioUnit.repeatDataSize = size >> 1;
+}
+
+void SoundChannel_Amiga::setVelocity(uint8 velo, int delay) {
+	_ioUnit.levelFadeTriggerDC = 0;
+
+	if (delay) {
+		_ioUnit.fadeTargetLevel = velo;
+		_ioUnit.fadeLevelDelta = ABS(_ioUnit.currentLevel - velo);
+		_ioUnit.fadeLevelTicks = (delay << 10) / 5500;
+		_ioUnit.fadeLevelMod = (_ioUnit.currentLevel >= velo) ? -1 : 1;
+		_ioUnit.fadeLevelTicker = _ioUnit.fadeLevelDuration = 0;
+	} else {
+		_driver->setChannelVolume(_id, _volTable[(_ioUnit.volume << 5) + velo]);
+		_ioUnit.currentLevel = _ioUnit.fadeTargetLevel = velo;
+		_ioUnit.fadeLevelMod = 0;		
+	}
+}
+
+void SoundChannel_Amiga::setVolume(uint8 volume) {
+	volume >>= 1;
+	_ioUnit.volume = volume;
+	_driver->setChannelVolume(_id, _volTable[(volume << 5) + _ioUnit.currentLevel]);
+}
+
+uint16 SoundChannel_Amiga::calculatePeriod(int16 tone, uint8 baseNote, uint16 rate) {
+	static const uint32 octavePeriods[13] = { 0x4000, 0x43CE, 0x47D7, 0x4C1B, 0x50A2, 0x556D, 0x5A82, 0x5FE4, 0x6598, 0x6BA2, 0x7209, 0x78D0, 0x8000 };
+
+	int16 frq_coarse = tone >> 7;
+	uint8 frq_fine = tone & 0x7F;
+	int16 octTrans = baseNote;
+	rate <<= 3;
+
+	for (int16 octTransHi = baseNote + 12; octTransHi <= frq_coarse; octTransHi += 12) {
+		rate >>= 1;
+		octTrans = octTransHi;
+	}
+
+	while (octTrans > frq_coarse) {
+		rate += rate;
+		octTrans -= 12;
+	}
+
+	uint32 res = (((octavePeriods[11 - (frq_coarse - octTrans)] * rate) >> 18) * frq_fine + ((octavePeriods[12 - (frq_coarse - octTrans)] * rate) >> 18) * (0x80 - frq_fine)) >> 7;
+
+	if (!res)
+		return 124;
+
+	while (res < 124)
+		res += res;
+
+	if (res > 65535)
+		res = 65535;
+
+	return res & 0xFFFF;
+}
+
+void SoundChannel_Amiga::createVolumeTable() {
+	if (_volTable)
+		return;
+
+	uint8 *volTbl = new uint8[2048];
+	for (int a = 0; a < 64; ++a) {
+		volTbl[a << 5] = 0;
+		for (int b = 1; b < 32; ++b)
+			volTbl[(a << 5) + b] = (a * (b + 1)) >> 5;
+	}
+	_volTable = volTbl;
+}
+
+uint8 SoundChannel_Amiga::_allocCurPos = 0;
+
+const uint8 *SoundChannel_Amiga::_volTable = 0;
+
+SoundChannel_Amiga *SoundChannel_Amiga::_channels[4] = { 0, 0, 0, 0 };
+
+const int8 SoundChannel_Amiga::_muteData[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+IMusePart_Amiga::IMusePart_Amiga(IMuseDriver_Amiga *driver, int id) : _driver(driver), _id(id), _allocated(false), _out(0), _priority(0), _program(0),
+	_pitchBend(0), _pitchBendSensitivity(2), _volume(0), _modulation(0), _transpose(0), _sustain(false) {
+}
+
+bool IMusePart_Amiga::allocate() {
+	if (_allocated)
+		return false;
+
+	_allocated = true;
+
+	while (_out)
+		_out->disconnect();
+
+	return true;
+}
+
+void IMusePart_Amiga::release() {
+	_allocated = false;
+
+	while (_out)
+		_out->disconnect();
+}
+
+void IMusePart_Amiga::send(uint32 b) {
+	_driver->send(b | _id);
+}
+
+void IMusePart_Amiga::noteOff(byte note) {
+	for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next()) {
+		if (note == cur->note()) {
+			if (_sustain)
+				cur->ctrl_sustain(true);
+			else
+				cur->disconnect();
+		}
+	}
+}
+
+void IMusePart_Amiga::noteOn(byte note, byte velocity) {
+	if (!velocity) {
+		noteOff(note);
+		return;
+	}
+
+	SoundChannel_Amiga *chan = SoundChannel_Amiga::allocate(_priority);
+	if (!chan)
+		return;
+
+	chan->connect(this);
+	// The velocity parameter is ignored here.
+	chan->noteOn(note, _volume, _program, _transpose, (_pitchBend * _pitchBendSensitivity) >> 6);
+}
+
+void IMusePart_Amiga::controlChange(byte control, byte value) {
+	switch (control) {
+	case 1:
+		controlModulationWheel(value);
+		break;
+	case 7:
+		controlVolume(value);
+		break;
+	case 10:
+		// The original driver has no support for this.
+		break;
+	case 64:
+		controlSustain(value);
+		break;
+	case 123:
+		while (_out)
+			_out->disconnect();
+		break;
+	default:
+		break;
+	}
+}
+
+void IMusePart_Amiga::programChange(byte program) {
+	_program = program;
+}
+
+void IMusePart_Amiga::pitchBend(int16 bend) {
+	_pitchBend = bend;
+	for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+		cur->transposePitchBend(_transpose, (_pitchBend * _pitchBendSensitivity) >> 6);
+}
+
+void IMusePart_Amiga::pitchBendFactor(byte value) {
+	_pitchBendSensitivity = value;
+	for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+		cur->transposePitchBend(_transpose, (_pitchBend * _pitchBendSensitivity) >> 6);
+}
+
+void IMusePart_Amiga::transpose(int8 value) {
+	_transpose = value << 1;
+	for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+		cur->transposePitchBend(_transpose, (_pitchBend * _pitchBendSensitivity) >> 6);
+}
+
+void IMusePart_Amiga::priority(byte value) {
+	_priority = value;
+}
+
+void IMusePart_Amiga::controlModulationWheel(byte value) {
+	_modulation = (int8)value;
+}
+
+void IMusePart_Amiga::controlVolume(byte value) {
+	_volume = value;
+	for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+		cur->ctrl_volume(_volume);
+}
+
+void IMusePart_Amiga::controlSustain(byte value) {
+	_sustain = value;
+	if (!value) {
+		for (SoundChannel_Amiga *cur = _out; cur; cur = cur->next())
+			cur->ctrl_sustain(false);
+	}
+}
+
+IMuseDriver_Amiga::IMuseDriver_Amiga(Audio::Mixer *mixer) : Paula(true, mixer->getOutputRate(), (mixer->getOutputRate() * 1000) / 181818), _mixer(mixer), _isOpen(false), _soundHandle(),
+	_numParts(24), _baseTempo(5500), _internalTempo(5500), _timerProc(0), _timerProcPara(0), _parts(0), _chan(0), _instruments(0), _missingFiles(0), _ticker(0) {
+	setAudioFilter(true);
+
+	_instruments = new Instrument_Amiga[129];
+	memset(_instruments, 0, sizeof(Instrument_Amiga) * 129);
+	loadInstrument(128);
+
+	_parts = new IMusePart_Amiga*[_numParts];
+	for (int i = 0; i < _numParts; i++)
+		_parts[i] = new IMusePart_Amiga(this, i);
+
+	_chan = new SoundChannel_Amiga*[4];
+	for (int i = 0; i < 4; i++)
+		_chan[i] = new SoundChannel_Amiga(this, i, _instruments);
+}
+
+IMuseDriver_Amiga::~IMuseDriver_Amiga() {
+	close();
+
+	Common::StackLock lock(_mutex);
+
+	if (_chan) {
+		for (int i = 0; i < 4; i++)
+			delete _chan[i];
+		delete[] _chan;
+	}
+	_chan = 0;
+
+	if (_parts) {
+		for (int i = 0; i < _numParts; i++)
+			delete _parts[i];
+		delete[] _parts;
+	}
+	_parts = 0;
+
+	delete[] _instruments;
+}
+
+int IMuseDriver_Amiga::open() {
+	if (_isOpen)
+		return MERR_ALREADY_OPEN;
+
+	// Load all instruments at once. The original will load the programs that are necessary for the currently playing
+	// sounds into a fixed 100000 bytes buffer. The approach here needs more memory (approx. 480 KB for MI2), but we
+	// can easily afford this and it saves me the trouble of implementing a loader into the imuse code. The original
+	// loader is quite unpleasant, since it scans the whole imuse midi track for program change events and collects
+	// the program numbers for each such event in a buffer. Afterwards these instruments will get loaded.
+	for (int i = 0; i < 128; ++i)
+		loadInstrument(i);
+
+	// Actually not all of the .IMS files are required to play. Many of these contain copies of the same instruments.
+	// Each floppy disk contains one of the .IMS files. This would reduce the number of necessary floppy disk changes
+	// when playing from the floppy disks. Obviously we don't need the redundancy files. The error dialog will display
+	// only the required files. These are different for MI2 and INDY4.
+	if (_missingFiles) {
+		Common::String message = _("This AMIGA version is missing (at least) the following file(s):\n\n");
+		for (int i = 0; i < 11; ++i) {
+			if (_missingFiles & (1 << i))
+				message += Common::String::format("AMIGA%d.IMS\n", i + 1);
+		}
+		message += _("\nPlease copy these file(s) into the game data directory.\n\n");
+		::GUI::displayErrorDialog(message.c_str());
+		return MERR_DEVICE_NOT_AVAILABLE;
+	}
+
+	startPaula();
+	_mixer->playStream(Audio::Mixer::kPlainSoundType,
+		&_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+	_isOpen = true;
+
+	return 0;
+}
+
+void IMuseDriver_Amiga::close() {
+	if (!_isOpen)
+		return;
+
+	_isOpen = false;
+
+	Common::StackLock lock(_mutex);
+
+	stopPaula();
+	setTimerCallback(0, 0);
+	_mixer->stopHandle(_soundHandle);
+
+	unloadInstruments();
+
+	g_system->delayMillis(20);
+}
+
+void IMuseDriver_Amiga::send(uint32 b) {
+	if (!_isOpen)
+		return;
+
+	byte param2 = (b >> 16) & 0xFF;
+	byte param1 = (b >> 8) & 0xFF;
+	byte cmd = b & 0xF0;
+
+	IMusePart_Amiga *p = _parts[b & 0x0F];
+
+	switch (cmd) {
+	case 0x80:
+		p->noteOff(param1);
+		break;
+	case 0x90:
+		p->noteOn(param1, param2);
+		break;
+	case 0xB0:
+		p->controlChange(param1, param2);
+		break;
+	case 0xC0:
+		p->programChange(param1);
+		break;
+	case 0xE0:
+		p->pitchBend((param1 | (param2 << 7)) - 0x2000);
+		break;
+	case 0xF0:
+		warning("IMuseDriver_Amiga: Receiving SysEx command on a send() call");
+		break;
+
+	default:
+		break;
+	}
+}
+
+void IMuseDriver_Amiga::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+	_timerProc = timer_proc;
+	_timerProcPara = timer_param;
+}
+
+uint32 IMuseDriver_Amiga::getBaseTempo() {
+	return _baseTempo;
+}
+
+MidiChannel *IMuseDriver_Amiga::allocateChannel() {
+	if (!_isOpen)
+		return 0;
+
+	for (int i = 0; i < _numParts; ++i) {
+		if (_parts[i]->allocate())
+			return _parts[i];
+	}
+
+	return 0;
+}
+
+MidiChannel *IMuseDriver_Amiga::getPercussionChannel() {
+	return 0;
+}
+
+void IMuseDriver_Amiga::interrupt() {
+	if (!_isOpen)
+		return;
+
+	for (_ticker += _internalTempo; _ticker >= _baseTempo; _ticker -= _baseTempo) {	
+		updateParser();
+		updateSounds();
+	}
+}
+
+void IMuseDriver_Amiga::updateParser() {
+	if (_timerProc)
+		_timerProc(_timerProcPara);
+}
+
+void IMuseDriver_Amiga::updateSounds() {
+	for (int i = 0; i < 4; i++)
+		_chan[i]->updateLevel();
+	for (int i = 0; i < 4; i++)
+		_chan[i]->updateEnvelope();
+}
+
+void IMuseDriver_Amiga::loadInstrument(int program) {
+	Common::StackLock lock(_mutex);
+
+	if (program == 128) {
+		// The hard-coded default instrument definitions and sample data are the same in MI2 and INDY4.
+		static const int8 defaultData[16] = { 0, 49, 90, 117, 127, 117, 90, 49, 0, -49, -90, -117, -127, -117, -90, -49 };
+		static Instrument_Amiga::Samples defaultSamples = { 428, 60, 0, 127, 33, 0, /*0, 0,*/16, 0, 0, 5, 300, 5, 100, defaultData };
+		_instruments[128].numBlocks = 1;
+		memcpy(&_instruments[128].samples[0], &defaultSamples, sizeof(Instrument_Amiga::Samples));
+	}
+
+	if (program > 127)
+		return;
+
+	Common::File ims;
+	int32 header[10];
+	uint32 offset = 0;
+	memset(header, 0, sizeof(header));
+
+	for (int i = 0; i < 8; ++i) {
+		if (_instruments[program].samples[i].data) {
+			delete[] _instruments[program].samples[i].data;
+			_instruments[program].samples[i].data = 0;
+		}
+	}
+
+	for (int fileNo = 1; fileNo != -1 && !ims.isOpen(); ) {
+		if (!ims.open(Common::String::format("amiga%d.ims", fileNo))) {
+			_missingFiles |= (1 << (fileNo - 1));
+			return;
+		}
+
+		ims.seek(16 + (program << 2), SEEK_SET);
+		offset = ims.readUint32BE();
+		if (offset & 0x40000000) {
+			offset &= ~0x40000000;
+			ims.seek(16 + (offset << 2), SEEK_SET);
+			offset = ims.readUint32BE();
+		}
+
+		if (offset & 0x80000000) {
+			offset &= ~0x80000000;
+			ims.close();
+			fileNo = offset ? offset : -1;
+		} else {
+			ims.seek(552 + offset, SEEK_SET);
+			for (int i = 0; i < 10; ++i)
+				header[i] = ims.readSint32BE();
+		}
+	}
+
+	if (!ims.isOpen())
+		return;
+
+	for (int block = 0; block < 8; ++block) {
+		int size = 0;
+
+		if (header[block] != -1)
+			size = (block != 7 && header[block + 1] != -1 ? header[block + 1] : header[9]) - header[block];
+
+		if (size <= 0)
+			break;
+			
+		size -= 38;
+		Instrument_Amiga::Samples *s = &_instruments[program].samples[block];
+		ims.seek(594 + offset + header[block], SEEK_SET);
+		int8 *buf = new int8[size];
+		
+		s->rate = ims.readUint16BE();
+		s->baseNote = ims.readUint16BE();
+		s->noteRangeMin = ims.readSint16BE();
+		s->noteRangeMax = ims.readSint16BE();
+		s->sustainLevel = ims.readSint16BE();
+		s->type = ims.readUint16BE();
+		ims.skip(8);
+		s->numSamples = size;
+		s->dr_offset = ims.readUint32BE();
+		s->dr_numSamples = ims.readUint32BE();
+		s->levelFadeDelayAT = ims.readSint16BE();
+		s->levelFadeDelayRL = ims.readSint16BE();
+		s->levelFadeTriggerRL = ims.readSint16BE();
+		s->levelFadeDelayDC = ims.readSint16BE();
+		ims.read(buf, size);
+		s->data = buf;
+		_instruments[program].numBlocks = block + 1;
+	}
+
+	ims.close();
+}
+
+void IMuseDriver_Amiga::unloadInstruments() {
+	Common::StackLock lock(_mutex);
+	for (int prg = 0; prg < 128; ++prg) {
+		for (int block = 0; block < 8; ++block) {
+			if (_instruments[prg].samples[block].data)
+				delete[] _instruments[prg].samples[block].data;
+		}
+	}
+	memset(_instruments, 0, sizeof(Instrument_Amiga) * 128);
+}
+
+}
diff --git a/engines/scumm/imuse/drivers/amiga.h b/engines/scumm/imuse/drivers/amiga.h
new file mode 100644
index 0000000..77c7507
--- /dev/null
+++ b/engines/scumm/imuse/drivers/amiga.h
@@ -0,0 +1,85 @@
+/* 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 IMUSE_DRV_AMIGA_H
+#define IMUSE_DRV_AMIGA_H
+
+#include "audio/mididrv.h"
+#include "audio/mods/paula.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+class IMusePart_Amiga;
+class SoundChannel_Amiga;
+struct Instrument_Amiga;
+
+class IMuseDriver_Amiga : public MidiDriver, public Audio::Paula {
+friend class SoundChannel_Amiga;
+public:
+	IMuseDriver_Amiga(Audio::Mixer *mixer);
+	~IMuseDriver_Amiga();
+
+	int open();
+	bool isOpen() const { return _isOpen; }
+	void close();
+
+	void send(uint32 b);
+
+	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
+
+	uint32 getBaseTempo();
+	MidiChannel *allocateChannel();
+	MidiChannel *getPercussionChannel();
+
+	void interrupt();
+
+private:
+	void updateParser();
+	void updateSounds();
+
+	void loadInstrument(int program);
+	void unloadInstruments();
+
+	IMusePart_Amiga **_parts;
+	SoundChannel_Amiga **_chan;
+
+	Common::TimerManager::TimerProc _timerProc;
+	void *_timerProcPara;
+
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundHandle;
+	
+	int32 _ticker;
+	bool _isOpen;
+
+	Instrument_Amiga *_instruments;
+	uint16 _missingFiles;
+
+	const int32 _baseTempo;
+	const int32 _internalTempo;
+	const uint8 _numParts;
+};
+
+}
+
+#endif
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index f5526ab..5fd5d21 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -157,7 +157,9 @@ bool IMuseInternal::isMT32(int sound) {
 	case MKTAG('S', 'P', 'K', ' '):
 		return false;
 
-	case MKTAG('A', 'M', 'I', ' '):
+	case MKTAG('A', 'M', 'I', ' '): // Amiga
+		return false;
+
 	case MKTAG('R', 'O', 'L', ' '):
 		return true;
 
@@ -199,7 +201,9 @@ bool IMuseInternal::isMIDI(int sound) {
 	case MKTAG('S', 'P', 'K', ' '):
 		return false;
 
-	case MKTAG('A', 'M', 'I', ' '):
+	case MKTAG('A', 'M', 'I', ' '): // Amiga
+		return false;
+
 	case MKTAG('R', 'O', 'L', ' '):
 		return true;
 
@@ -236,7 +240,9 @@ bool IMuseInternal::supportsPercussion(int sound) {
 	case MKTAG('S', 'P', 'K', ' '):
 		return false;
 
-	case MKTAG('A', 'M', 'I', ' '):
+	case MKTAG('A', 'M', 'I', ' '): // Amiga
+		return false;
+
 	case MKTAG('R', 'O', 'L', ' '):
 		return true;
 
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index f5ba4ba..bcc1eba 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -32,6 +32,7 @@ MODULE_OBJS := \
 	imuse/pcspk.o \
 	imuse/sysex_samnmax.o \
 	imuse/sysex_scumm.o \
+	imuse/drivers/amiga.o \
 	input.o \
 	midiparser_ro.o \
 	object.o \


Commit: 0899ecc98760bba79bccc2026bcfa44542d46d8f
    https://github.com/scummvm/scummvm/commit/0899ecc98760bba79bccc2026bcfa44542d46d8f
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T20:56:27+02:00

Commit Message:
SCUMM: hook up Amiga MI2 + INDY4 to new sound driver

Changed paths:
    engines/scumm/detection_tables.h
    engines/scumm/imuse/imuse.cpp
    engines/scumm/scumm-md5.h
    engines/scumm/scumm.cpp


diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index ce8eecf..3dae5e1 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -247,13 +247,15 @@ static const GameSettings gameVariantsTable[] = {
 	{"monkey", "SEGA",         0, GID_MONKEY,     5, 0, MDT_NONE,                         GF_AUDIOTRACKS, Common::kPlatformSegaCD, GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI)},
 	{"monkey", "SE Talkie",    0, GID_MONKEY,     5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, GF_AUDIOTRACKS, UNK, GUIO0()},
 
-	{"monkey2",  "", 0, GID_MONKEY2,  5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO1(GUIO_NOSPEECH)},
+	{"monkey2", "", 0, GID_MONKEY2,  5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO1(GUIO_NOSPEECH)},
+	{"monkey2", "Amiga", 0, GID_MONKEY2,  5, 0, MDT_AMIGA, 0, Common::kPlatformAmiga, GUIO2(GUIO_NOSPEECH, GUIO_MIDIAMIGA)},
 	{"monkey2", "FM-TOWNS", 0, GID_MONKEY2,  5, 0, MDT_PCSPK | MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO5(GUIO_NOSPEECH, GUIO_MIDITOWNS, GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_NOASPECT)},
 	{"monkey2", "SE Talkie",0, GID_MONKEY2,  5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO0()},
 
 	{"atlantis", "", 0, GID_INDY4,    5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO0()},
 	{"atlantis", "Steam", "steam", GID_INDY4,    5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO0()},
 	{"atlantis", "Floppy", 0, GID_INDY4,    5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO1(GUIO_NOSPEECH)},
+	{"atlantis", "Amiga", 0, GID_INDY4,  5, 0, MDT_AMIGA, 0, Common::kPlatformAmiga, GUIO2(GUIO_NOSPEECH, GUIO_MIDIAMIGA)},
 	{"atlantis", "FM-TOWNS", 0, GID_INDY4,    5, 0, MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO4(GUIO_MIDITOWNS, GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_NOASPECT)},
 
 	{"tentacle", "", 0, GID_TENTACLE, 6, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_GM, GF_USE_KEY, UNK, GUIO0()},
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index 5fd5d21..ad921e4 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -201,8 +201,8 @@ bool IMuseInternal::isMIDI(int sound) {
 	case MKTAG('S', 'P', 'K', ' '):
 		return false;
 
-	case MKTAG('A', 'M', 'I', ' '): // Amiga
-		return false;
+	case MKTAG('A', 'M', 'I', ' '): // Amiga (return true, since the driver is initalized as native midi)
+		return true;
 
 	case MKTAG('R', 'O', 'L', ' '):
 		return true;
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 101676d..8a09bbb 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -68,10 +68,10 @@ static const MD5Table md5table[] = {
 	{ "10d8e66cd11049ce64815ebb9fd76eb3", "spyozon", "", "", -1, Common::FR_FRA, Common::kPlatformUnknown },
 	{ "111b36172bdc9bfe498e135878c03d38", "pajama", "HE 101", "", 66878, Common::NL_NLD, Common::kPlatformWii },
 	{ "114acdc2659a273c220f86ee9edb24c1", "maniac", "V2", "V2", -1, Common::FR_FRA, Common::kPlatformDOS },
-	{ "11ddf1fde76e3156eb3a38da213f484e", "monkey2", "", "", -1, Common::IT_ITA, Common::kPlatformAmiga },
+	{ "11ddf1fde76e3156eb3a38da213f484e", "monkey2", "Amiga", "", -1, Common::IT_ITA, Common::kPlatformAmiga },
 	{ "11e6e244078ff09b0f3832e35420e0a7", "catalog", "", "Demo", -1, Common::EN_ANY, Common::kPlatformWindows },
 	{ "12cdc256eae5a461bcc9a49975999841", "atlantis", "Floppy", "Demo", -1, Common::EN_ANY, Common::kPlatformDOS },
-	{ "132bff65e6367c09cc69318ce1b59333", "monkey2", "", "", 11155, Common::EN_ANY, Common::kPlatformAmiga },
+	{ "132bff65e6367c09cc69318ce1b59333", "monkey2", "Amiga", "", 11155, Common::EN_ANY, Common::kPlatformAmiga },
 	{ "1387d16aa620dc1c2d1fd87f8a9e7a09", "puttcircus", "", "Demo", -1, Common::FR_FRA, Common::kPlatformWindows },
 	{ "13d2a86a7290813a1c386490447d72db", "fbear", "HE 62", "", -1, Common::EN_ANY, Common::kPlatform3DO },
 	{ "145bd3373574feb668cc2eea2ec6cf86", "balloon", "HE 80", "", -1, Common::RU_RUS, Common::kPlatformWindows },
@@ -81,7 +81,7 @@ static const MD5Table md5table[] = {
 	{ "157367c3c21e0d03a0cba44361b4cf65", "indy3", "No AdLib", "EGA", -1, Common::EN_ANY, Common::kPlatformAtariST },
 	{ "15878e3bee2e1e759184abee98589eaa", "spyfox", "HE 100", "", -1, Common::EN_ANY, Common::kPlatformIOS },
 	{ "15e03ffbfeddb9c2aebc13dcb2a4a8f4", "monkey", "VGA", "VGA", 8357, Common::EN_ANY, Common::kPlatformDOS },
-	{ "15f588e887e857e8c56fe6ade4956168", "atlantis", "Floppy", "Floppy", -1, Common::ES_ESP, Common::kPlatformAmiga },
+	{ "15f588e887e857e8c56fe6ade4956168", "atlantis", "Amiga", "Floppy", -1, Common::ES_ESP, Common::kPlatformAmiga },
 	{ "16542a7342a918bfe4ba512007d36c47", "FreddisFunShop", "HE 99L", "", -1, Common::EN_USA, Common::kPlatformUnknown },
 	{ "166553538ff320c69edafeee29525419", "samnmax", "", "CD", 199195304, Common::EN_ANY, Common::kPlatformMacintosh },
 	{ "16effd200aa6b8abe9c569c3e578814d", "freddi4", "HE 99", "Demo", -1, Common::NL_NLD, Common::kPlatformUnknown },
@@ -115,7 +115,7 @@ static const MD5Table md5table[] = {
 	{ "2012f854d83d9cc6f73b2b544cd8bbf8", "water", "HE 80", "", -1, Common::RU_RUS, Common::kPlatformWindows },
 	{ "20176076d708bf14407bcc9bdcd7a418", "pajama3", "", "", -1, Common::RU_RUS, Common::kPlatformWindows },
 	{ "204453e33456c4faa26e276229fe5b76", "spyfox2", "", "Demo", 14689, Common::DE_DEU, Common::kPlatformWindows },
-	{ "20da6fce37805423966aaa8f3c2426aa", "atlantis", "Floppy", "Floppy", -1, Common::FR_FRA, Common::kPlatformAmiga },
+	{ "20da6fce37805423966aaa8f3c2426aa", "atlantis", "Amiga", "Floppy", -1, Common::FR_FRA, Common::kPlatformAmiga },
 	{ "2108d83dcf09f8adb4bc524669c8cf51", "PuttTime", "HE 99", "Updated", -1, Common::EN_USA, Common::kPlatformUnknown },
 	{ "21a6592322f92550f144f68a8a4e685e", "dig", "", "", -1, Common::FR_FRA, Common::kPlatformMacintosh },
 	{ "21abe302e1b1e2b66d6f5c12e241ebfd", "freddicove", "unenc", "Unencrypted", -1, Common::RU_RUS, Common::kPlatformWindows },
@@ -182,7 +182,7 @@ static const MD5Table md5table[] = {
 	{ "399b217b0c8d65d0398076da486363a9", "indy3", "VGA", "VGA", 6295, Common::DE_DEU, Common::kPlatformDOS },
 	{ "39cb9dec16fa16f38d79acd80effb059", "loom", "EGA", "EGA", -1, Common::UNK_LANG, Common::kPlatformAmiga },
 	{ "39fd6db10d0222d817025c4d3346e3b4", "farm", "HE 72", "Demo", -1, Common::EN_ANY, Common::kPlatformMacintosh },
-	{ "3a03dab514e4038df192d8a8de469788", "atlantis", "Floppy", "Floppy", -1, Common::EN_ANY, Common::kPlatformAmiga },
+	{ "3a03dab514e4038df192d8a8de469788", "atlantis", "Amiga", "Floppy", -1, Common::EN_ANY, Common::kPlatformAmiga },
 	{ "3a0c35f3c147b98a2bdf8d400cfc4ab5", "indy3", "FM-TOWNS", "", -1, Common::JA_JPN, Common::kPlatformFMTowns },
 	{ "3a3e592b074f595489f7f11e150c398d", "puttzoo", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows },
 	{ "3a5d13675e9a23aedac0bac7730f0ac1", "samnmax", "", "CD", 228446581, Common::FR_FRA, Common::kPlatformMacintosh },
@@ -243,7 +243,7 @@ static const MD5Table md5table[] = {
 	{ "4f04b321a95d4315ce6d65f8e1dd0368", "maze", "HE 80", "", -1, Common::EN_USA, Common::kPlatformUnknown },
 	{ "4f138ac6f9b2ac5a41bc68b2c3296064", "freddi4", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformWindows },
 	{ "4f1d6f8b38343dba405472538b5037ed", "fbear", "HE 62", "", 7717, Common::EN_ANY, Common::kPlatformDOS },
-	{ "4f267a901719623de7dde83e47d5b474", "atlantis", "Floppy", "Floppy", -1, Common::DE_DEU, Common::kPlatformAmiga },
+	{ "4f267a901719623de7dde83e47d5b474", "atlantis", "Amiga", "Floppy", -1, Common::DE_DEU, Common::kPlatformAmiga },
 	{ "4f580a021eee026f3b4589e17d130d78", "freddi4", "", "", 44190, Common::UNK_LANG, Common::kPlatformUnknown },
 	{ "4fa6870d9bc8c313b65d54b1da5a1891", "pajama", "", "", -1, Common::NL_NLD, Common::kPlatformUnknown },
 	{ "4fbbe9f64b8bc547503a379a301183ce", "tentacle", "", "CD", -1, Common::IT_ITA, Common::kPlatformUnknown },
@@ -265,7 +265,7 @@ static const MD5Table md5table[] = {
 	{ "56b5922751be7ffd771b38dda56b028b", "freddi", "HE 100", "", 34837, Common::NL_NLD, Common::kPlatformWii },
 	{ "56e8c37a0a08c3a7076f82417461a877", "indy3", "EGA", "EGA", -1, Common::EN_ANY, Common::kPlatformDOS },
 	{ "5719fc8a13b4638b78d9d8d12f091f94", "puttrace", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformWindows },
-	{ "5798972220cd458be2626d54c80f71d7", "atlantis", "Floppy", "Floppy", -1, Common::IT_ITA, Common::kPlatformAmiga },
+	{ "5798972220cd458be2626d54c80f71d7", "atlantis", "Amiga", "Floppy", -1, Common::IT_ITA, Common::kPlatformAmiga },
 	{ "57a17febe2183f521250e55d55b83e60", "PuttTime", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformWindows },
 	{ "57a5cfec9ef231a007043cc1917e8988", "freddi", "HE 100", "", -1, Common::EN_ANY, Common::kPlatformWii },
 	{ "57b0d89af79befe1cabce3bece869e7f", "tentacle", "Floppy", "Floppy", -1, Common::DE_DEU, Common::kPlatformDOS },
@@ -336,7 +336,7 @@ static const MD5Table md5table[] = {
 	{ "6dead580b0ff14d5f7b33b4219f04159", "samnmax", "", "Demo", 16556335, Common::EN_ANY, Common::kPlatformMacintosh },
 	{ "6df20c50c1ab19799de9be7ae7716881", "fbear", "HE 62", "Demo", -1, Common::EN_ANY, Common::kPlatformMacintosh },
 	{ "6e959d65358eedf9b68b81e304b97fa4", "tentacle", "", "CD", 7932, Common::DE_DEU, Common::kPlatformUnknown },
-	{ "6ea966b4d660c870b9ee790d1fbfc535", "monkey2", "", "", -1, Common::ES_ESP, Common::kPlatformAmiga },
+	{ "6ea966b4d660c870b9ee790d1fbfc535", "monkey2", "Amiga", "", -1, Common::ES_ESP, Common::kPlatformAmiga },
 	{ "6f0be328c64d689bb606d22a389e1b0f", "loom", "No AdLib", "EGA", 5748, Common::EN_ANY, Common::kPlatformMacintosh },
 	{ "6f4d64ce1b44470536cae2eb6fe8aefd", "spyfox", "HE 100", "", 49742, Common::FR_FRA, Common::kPlatformWii },
 	{ "6f6ef668c608c7f534fea6e6d3878dde", "indy3", "EGA", "EGA", -1, Common::DE_DEU, Common::kPlatformDOS },
@@ -550,7 +550,7 @@ static const MD5Table md5table[] = {
 	{ "c225bec1b6c0798a2b8c89ac226dc793", "pajama", "HE 101", "", -1, Common::EN_ANY, Common::kPlatformWii },
 	{ "c24c490373aeb48fbd54caa8e7ae376d", "loom", "No AdLib", "EGA", -1, Common::DE_DEU, Common::kPlatformAtariST },
 	{ "c25755b08a8d0d47695e05f1e2111bfc", "freddi4", "", "Demo", -1, Common::EN_USA, Common::kPlatformUnknown },
-	{ "c30ef068add4277104243c31ce46c12b", "monkey2", "", "", -1, Common::FR_FRA, Common::kPlatformAmiga },
+	{ "c30ef068add4277104243c31ce46c12b", "monkey2", "Amiga", "", -1, Common::FR_FRA, Common::kPlatformAmiga },
 	{ "c3196c5349e53e387aaff1533d95e53a", "samnmax", "Floppy", "Demo", -1, Common::EN_ANY, Common::kPlatformDOS },
 	{ "c3b22fa4654bb580b20325ebf4174841", "puttzoo", "", "", -1, Common::NL_NLD, Common::kPlatformWindows },
 	{ "c3df37df9d3b481b45f75283a9907c47", "loom", "EGA", "EGA", -1, Common::IT_ITA, Common::kPlatformDOS },
@@ -621,7 +621,7 @@ static const MD5Table md5table[] = {
 	{ "d9d0dd93d16ab4dec55cabc2b86bbd17", "samnmax", "", "Demo", 6478, Common::EN_ANY, Common::kPlatformDOS },
 	{ "da09e666fc8f5b78d7b0ac65d1a3b56e", "monkey2", "FM-TOWNS", "", 11135, Common::EN_ANY, Common::kPlatformFMTowns },
 	{ "da6269b18fcb08189c0aa9c95533cce2", "monkey", "CD", "CD", 8955, Common::IT_ITA, Common::kPlatformDOS },
-	{ "da669b20271b85182e9c17a2a37ea02e", "monkey2", "", "", -1, Common::DE_DEU, Common::kPlatformAmiga },
+	{ "da669b20271b85182e9c17a2a37ea02e", "monkey2", "Amiga", "", -1, Common::DE_DEU, Common::kPlatformAmiga },
 	{ "db21a6e338fe3b70c2723b6530865bf2", "PuttTime", "HE 85", "", -1, Common::FR_FRA, Common::kPlatformUnknown },
 	{ "db74136c20557eca6ed3411bff39f7a1", "puttcircus", "", "", -1, Common::EN_GRB, Common::kPlatformWindows },
 	{ "dbf4d59d70b826733f379f998354d350", "BluesBirthday", "", "Demo", -1, Common::EN_ANY, Common::kPlatformUnknown },
@@ -643,7 +643,7 @@ static const MD5Table md5table[] = {
 	{ "e41de1c2a15abbcdbf9977e2d7e8a340", "freddi2", "HE 100", "Updated", -1, Common::RU_RUS, Common::kPlatformWindows },
 	{ "e441af0b65983c1a4b3a52f9c7b15484", "spyfox", "HE 100", "", 49742, Common::EN_USA, Common::kPlatformWii },
 	{ "e44ea295a3f8fe4f41983080dab1e9ce", "freddi", "HE 90", "Updated", -1, Common::FR_FRA, Common::kPlatformMacintosh },
-	{ "e534d29afb3c6e0ee9dc3d53c5956714", "atlantis", "Floppy", "Floppy", -1, Common::DE_DEU, Common::kPlatformAmiga },
+	{ "e534d29afb3c6e0ee9dc3d53c5956714", "atlantis", "Amiga", "Floppy", -1, Common::DE_DEU, Common::kPlatformAmiga },
 	{ "e5563c8358443c4352fcddf7402a5e0a", "pajama2", "HE 98.5", "", -1, Common::FR_FRA, Common::kPlatformWindows },
 	{ "e5c112140ad6574997de033a8e2a2964", "readtime", "", "", -1, Common::EN_ANY, Common::kPlatformUnknown },
 	{ "e62056ba675ad65d8854ab3c5ad4b3c0", "spyfox2", "", "Mini Game", 14689, Common::EN_GRB, Common::kPlatformWindows },
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 1102890..f6e0f9e 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -78,6 +78,7 @@
 #include "scumm/verbs.h"
 #include "scumm/imuse/pcspk.h"
 #include "scumm/imuse/mac_m68k.h"
+#include "scumm/imuse/drivers/amiga.h"
 
 #include "backends/audiocd/audiocd.h"
 
@@ -1837,6 +1838,9 @@ void ScummEngine::setupMusic(int midi) {
 	case MT_NULL:
 		_sound->_musicType = MDT_NONE;
 		break;
+	case MT_AMIGA:
+		_sound->_musicType = MDT_AMIGA;
+		break;
 	case MT_PCSPK:
 		_sound->_musicType = MDT_PCSPK;
 		break;
@@ -1985,6 +1989,10 @@ void ScummEngine::setupMusic(int midi) {
 			_native_mt32 = false;
 			// Ignore non-native drivers. This also ignores the multi MIDI setting.
 			useOnlyNative = true;
+		} else if (_sound->_musicType == MDT_AMIGA) {
+			nativeMidiDriver = new IMuseDriver_Amiga(_mixer);
+			_native_mt32 = false;
+			useOnlyNative = true;
 		} else if (_sound->_musicType != MDT_ADLIB && _sound->_musicType != MDT_TOWNS && _sound->_musicType != MDT_PCSPK) {
 			nativeMidiDriver = MidiDriver::createMidi(dev);
 		}


Commit: d1b64aab0cd1477f6d56c9ced8b84886d66de4d8
    https://github.com/scummvm/scummvm/commit/d1b64aab0cd1477f6d56c9ced8b84886d66de4d8
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:29+02:00

Commit Message:
SCUMM: (iMuse/Amiga) - improve accuracy

This fixes the issue that some rhythm instruments didn't receive correct notes. The changes have been limited to the Amiga versions.

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


diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index ad921e4..37a0465 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -45,6 +45,7 @@ namespace Scumm {
 IMuseInternal::IMuseInternal() :
 	_native_mt32(false),
 	_enable_gs(false),
+	_isAmiga(false),
 	_midi_adlib(NULL),
 	_midi_native(NULL),
 	_sysex(NULL),
@@ -481,6 +482,10 @@ uint32 IMuseInternal::property(int prop, uint32 value) {
 		}
 		break;
 
+	case IMuse::PROP_AMIGA:
+		_isAmiga = (value > 0);
+		break;
+
 	case IMuse::PROP_LIMIT_PLAYERS:
 		if (value > 0 && value <= ARRAYSIZE(_players))
 			_player_limit = (int)value;
diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h
index a30d3db..7b2fb81 100644
--- a/engines/scumm/imuse/imuse.h
+++ b/engines/scumm/imuse/imuse.h
@@ -53,6 +53,7 @@ public:
 		PROP_TEMPO_BASE,
 		PROP_NATIVE_MT32,
 		PROP_GS,
+		PROP_AMIGA,
 		PROP_LIMIT_PLAYERS,
 		PROP_RECYCLE_PLAYERS,
 		PROP_GAME_ID,
diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h
index 7aff68a..4b9b750 100644
--- a/engines/scumm/imuse/imuse_internal.h
+++ b/engines/scumm/imuse/imuse_internal.h
@@ -372,6 +372,7 @@ struct Part : public Common::Serializable {
 
 private:
 	void sendPitchBend();
+	void sendTranspose();
 	void sendPanPosition(uint8 value);
 	void sendEffectLevel(uint8 value);
 };
@@ -399,6 +400,7 @@ class IMuseInternal : public IMuse {
 protected:
 	bool _native_mt32;
 	bool _enable_gs;
+	bool _isAmiga;
 	MidiDriver *_midi_adlib;
 	MidiDriver *_midi_native;
 	TimerCallbackInfo _timer_info_adlib;
diff --git a/engines/scumm/imuse/imuse_part.cpp b/engines/scumm/imuse/imuse_part.cpp
index 33492a1..1614076 100644
--- a/engines/scumm/imuse/imuse_part.cpp
+++ b/engines/scumm/imuse/imuse_part.cpp
@@ -145,8 +145,17 @@ void Part::set_pan(int8 pan) {
 
 void Part::set_transpose(int8 transpose) {
 	_transpose = transpose;
-	_transpose_eff = (_transpose == -128) ? 0 : transpose_clamp(_transpose + _player->getTranspose(), -24, 24);
-	sendPitchBend();
+	
+	if (_se->_isAmiga) {
+		// The Amiga version does a check like this. While this is probably a bug (a signed int8 can never be 128),
+		// the playback depends on this being implemented exactly like in the original driver. I found this bug with
+		// the WinUAE debugger. I don't know whether this is an Amiga only thing...
+		_transpose_eff = /*(_transpose == 128) ? 0 : */transpose_clamp(_transpose + _player->getTranspose(), -12, 12);
+		sendTranspose();
+	} else {
+		_transpose_eff = (_transpose == -128) ? 0 : transpose_clamp(_transpose + _player->getTranspose(), -24, 24);
+		sendPitchBend();
+	}	
 }
 
 void Part::sustain(bool value) {
@@ -332,6 +341,7 @@ void Part::sendAll() {
 		return;
 
 	_mc->pitchBendFactor(_pitchbend_factor);
+	sendTranspose();
 	sendPitchBend();
 	_mc->volume(_vol_eff);
 	_mc->sustain(_pedal);
@@ -358,7 +368,24 @@ void Part::sendPitchBend() {
 	// so we'll do the scaling ourselves.
 	if (_player->_se->isNativeMT32())
 		bend = bend * _pitchbend_factor / 12;
-	_mc->pitchBend(clamp(bend + (_detune_eff * 64 / 12) + (_transpose_eff * 8192 / 12), -8192, 8191));
+	
+	// We send the transpose value separately for Amiga (which is more like the original handles this).
+	// Some rhythm instruments depend on this. 
+	int8 transpose = _se->_isAmiga ? 0 : _transpose_eff;
+	_mc->pitchBend(clamp(bend + (_detune_eff * 64 / 12) + (transpose * 8192 / 12), -8192, 8191));
+}
+
+void Part::sendTranspose() {
+	if (!_mc)
+		return;
+
+	// See comment above. The transpose function was never implemented into our other drivers,
+	// since this seems to have been handled via pitchBend() instead. The original drivers do have
+	// such functions.
+	if (!_se->_isAmiga)
+		return;
+	
+	_mc->transpose(_transpose_eff);
 }
 
 void Part::programChange(byte value) {
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index f6e0f9e..64f45c1 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -2039,6 +2039,8 @@ void ScummEngine::setupMusic(int midi) {
 			}
 			if (_sound->_musicType == MDT_PCSPK)
 				_imuse->property(IMuse::PROP_PC_SPEAKER, 1);
+			if (_sound->_musicType == MDT_AMIGA)
+				_imuse->property(IMuse::PROP_AMIGA, 1);
 		}
 	}
 }


Commit: 01f99f1a0a39a5e09f9eafc9e03ece4a8470bf1c
    https://github.com/scummvm/scummvm/commit/01f99f1a0a39a5e09f9eafc9e03ece4a8470bf1c
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:43+02:00

Commit Message:
SCUMM: imuse driver directory cleanup

- move mac, pc speaker and fm-towns ims sound drivers into separate directory

(AdLib and MT32/GM drivers are still too entangled with common code to be moved so easily, especially MT32/GM. It would require lots of changes to the common code and possibly to all engines using the MidiDriver class. So I leave that for now.)

Changed paths:
  A engines/scumm/imuse/drivers/fmtowns.cpp
  A engines/scumm/imuse/drivers/fmtowns.h
  A engines/scumm/imuse/drivers/mac_m68k.cpp
  A engines/scumm/imuse/drivers/mac_m68k.h
  A engines/scumm/imuse/drivers/pcspk.cpp
  A engines/scumm/imuse/drivers/pcspk.h
  R audio/softsynth/fmtowns_pc98/towns_midi.cpp
  R audio/softsynth/fmtowns_pc98/towns_midi.h
  R engines/scumm/imuse/mac_m68k.cpp
  R engines/scumm/imuse/mac_m68k.h
  R engines/scumm/imuse/pcspk.cpp
  R engines/scumm/imuse/pcspk.h
    audio/module.mk
    audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
    engines/scumm/module.mk
    engines/scumm/players/player_towns.h
    engines/scumm/scumm.cpp


diff --git a/audio/module.mk b/audio/module.mk
index 686c70b..c2d764a 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -49,7 +49,6 @@ MODULE_OBJS := \
 	softsynth/fmtowns_pc98/pc98_audio.o \
 	softsynth/fmtowns_pc98/towns_audio.o \
 	softsynth/fmtowns_pc98/towns_euphony.o \
-	softsynth/fmtowns_pc98/towns_midi.o \
 	softsynth/fmtowns_pc98/towns_pc98_driver.o \
 	softsynth/fmtowns_pc98/towns_pc98_fmsynth.o \
 	softsynth/fmtowns_pc98/towns_pc98_plugins.o \
diff --git a/audio/softsynth/fmtowns_pc98/towns_midi.cpp b/audio/softsynth/fmtowns_pc98/towns_midi.cpp
deleted file mode 100644
index c02b047..0000000
--- a/audio/softsynth/fmtowns_pc98/towns_midi.cpp
+++ /dev/null
@@ -1,1029 +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 "audio/softsynth/fmtowns_pc98/towns_midi.h"
-#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
-#include "common/textconsole.h"
-#include "common/system.h"
-
-class TownsMidiOutputChannel {
-friend class TownsMidiInputChannel;
-public:
-	TownsMidiOutputChannel(MidiDriver_TOWNS *driver, int chanId);
-	~TownsMidiOutputChannel();
-
-	void noteOn(uint8 msb, uint16 lsb);
-	void noteOnPitchBend(uint8 msb, uint16 lsb);
-	void setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara);
-	void setupEffects(int index, uint8 flags, const uint8 *effectData);
-	void setModWheel(uint8 value);
-
-	void connect(TownsMidiInputChannel *chan);
-	void disconnect();
-
-	bool update();
-
-	enum CheckPriorityStatus {
-		kDisconnected = -2,
-		kHighPriority = -1
-	};
-
-	int checkPriority(int pri);
-
-private:
-	struct EffectEnvelope {
-		uint8 state;
-		int32 currentLevel;
-		int32 duration;
-		int32 maxLevel;
-		int32 startLevel;
-		uint8 loop;
-		uint8 stateTargetLevels[4];
-		uint8 stateModWheelLevels[4];
-		int8 modWheelSensitivity;
-		int8 modWheelState;
-		int8 modWheelLast;
-		uint16 numSteps;
-		uint32 stepCounter;
-		int32 incrPerStep;
-		int8 dir;
-		uint32 incrPerStepRem;
-		uint32 incrCountRem;
-	} *_effectEnvelopes;
-
-	struct EffectDef {
-		int32 phase;
-		uint8 type;
-		uint8 useModWheel;
-		uint8 loopRefresh;
-		EffectEnvelope *s;
-	} *_effectDefs;
-
-	void startEffect(EffectEnvelope *s, const uint8 *effectData);
-	void updateEffectGenerator(EffectEnvelope *s, EffectDef *d);
-	int advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d);
-	void initNextEnvelopeState(EffectEnvelope *s);
-	int16 getEffectStartLevel(uint8 type);
-	int getEffectModLevel(int lvl, int mod);
-
-	void keyOn();
-	void keyOff();
-	void keyOnSetFreq(uint16 frq);
-	void out(uint8 reg, uint8 val);
-
-	TownsMidiInputChannel *_in;
-	TownsMidiOutputChannel *_prev;
-	TownsMidiOutputChannel *_next;
-	uint8 _adjustModTl;
-	uint8 _chan;
-	uint8 _note;
-	uint8 _operator2Tl;
-	uint8 _operator1Tl;
-	uint8 _sustainNoteOff;
-	int16 _duration;
-
-	uint16 _freq;
-	int16 _freqAdjust;
-
-	MidiDriver_TOWNS *_driver;
-
-	static const uint8 _chanMap[];
-	static const uint8 _chanMap2[];
-	static const uint8 _effectDefaults[];
-	static const uint16 _effectEnvStepTable[];
-	static const uint8 _freqMSB[];
-	static const uint16 _freqLSB[];
-};
-
-class TownsMidiInputChannel : public MidiChannel {
-friend class TownsMidiOutputChannel;
-public:
-	TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex);
-	~TownsMidiInputChannel();
-
-	MidiDriver *device() { return _driver; }
-	byte getNumber() { return _chanIndex; }
-	bool allocate();
-	void release();
-
-	void send(uint32 b);
-
-	void noteOff(byte note);
-	void noteOn(byte note, byte velocity);
-	void programChange(byte program);
-	void pitchBend(int16 bend);
-	void controlChange(byte control, byte value);
-	void pitchBendFactor(byte value);
-	void priority(byte value);
-	void sysEx_customInstrument(uint32 type, const byte *instr);
-
-private:
-	void controlModulationWheel(byte value);
-	void controlVolume(byte value);
-	void controlPanPos(byte value);
-	void controlSustain(byte value);
-
-	void releasePedal();
-
-	TownsMidiOutputChannel *_out;
-
-	uint8 *_instrument;
-	uint8 _chanIndex;
-	uint8 _priority;
-	uint8 _tl;
-	int8 _transpose;
-	int8 _detune;
-	int8 _modWheel;
-	uint8 _sustain;
-	uint8 _pitchBendFactor;
-	int16 _pitchBend;
-	uint16 _freqLSB;
-
-	bool _allocated;
-
-	MidiDriver_TOWNS *_driver;
-
-	static const uint8 _programAdjustLevel[];
-};
-
-class TownsMidiChanState {
-public:
-	TownsMidiChanState();
-	~TownsMidiChanState() {}
-	uint8 get(uint8 type);
-
-	uint8 unk1;
-	uint8 mulAmsFms;
-	uint8 tl;
-	uint8 attDec;
-	uint8 sus;
-	uint8 fgAlg;
-	uint8 unk2;
-};
-
-TownsMidiChanState::TownsMidiChanState() {
-	unk1 = mulAmsFms = tl =	attDec = sus = fgAlg = unk2 = 0;
-}
-
-uint8 TownsMidiChanState::get(uint8 type) {
-	switch (type) {
-	case 0:
-		return unk1;
-	case 1:
-		return mulAmsFms;
-	case 2:
-		return tl;
-	case 3:
-		return attDec;
-	case 4:
-		return sus;
-	case 5:
-		return fgAlg;
-	case 6:
-		return unk2;
-	default:
-		break;
-	}
-	return 0;
-}
-
-TownsMidiOutputChannel::TownsMidiOutputChannel(MidiDriver_TOWNS *driver, int chanIndex) : _driver(driver), _chan(chanIndex),
-	_in(0), _prev(0), _next(0), _adjustModTl(0), _operator2Tl(0), _note(0), _operator1Tl(0), _sustainNoteOff(0), _duration(0), _freq(0), _freqAdjust(0) {
-	_effectEnvelopes = new EffectEnvelope[2];
-	_effectDefs = new EffectDef[2];
-
-	memset(_effectEnvelopes, 0, 2 * sizeof(EffectEnvelope));
-	memset(_effectDefs, 0, 2 * sizeof(EffectDef));
-	_effectDefs[0].s = &_effectEnvelopes[1];
-	_effectDefs[1].s = &_effectEnvelopes[0];
-}
-
-TownsMidiOutputChannel::~TownsMidiOutputChannel() {
-	delete[] _effectEnvelopes;
-	delete[] _effectDefs;
-}
-
-void TownsMidiOutputChannel::noteOn(uint8 msb, uint16 lsb) {
-	_freq = (msb << 7) + lsb;
-	_freqAdjust = 0;
-	keyOnSetFreq(_freq);
-}
-
-void TownsMidiOutputChannel::noteOnPitchBend(uint8 msb, uint16 lsb) {
-	_freq = (msb << 7) + lsb;
-	keyOnSetFreq(_freq + _freqAdjust);
-}
-
-void TownsMidiOutputChannel::setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara) {
-	// This driver uses only 2 operators and 2 algorithms (algorithm 5 and 7),
-	// since it is just a modified AdLib driver. It also uses AdLib programs.
-	// There are no FM-TOWNS specific programs. This is the reason for the low quality of the FM-TOWNS
-	// music (unsuitable data is just forced into the wrong audio device).
-
-	static const uint8 mul[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 };
-	uint8 chan = _chanMap[_chan];
-
-	uint8 mulAmsFms1 = _driver->_chanState[chan].mulAmsFms = data[0];
-	uint8 tl1 = _driver->_chanState[chan].tl = (data[1] | 0x3f) - mLevelPara;
-	uint8 attDec1 = _driver->_chanState[chan].attDec = ~data[2];
-	uint8 sus1 = _driver->_chanState[chan].sus = ~data[3];
-	_driver->_chanState[chan].unk2 = data[4];
-	chan += 3;
-
-	out(0x30, mul[mulAmsFms1 & 0x0f]);
-	out(0x40, (tl1 & 0x3f) + 15);
-	out(0x50, ((attDec1 >> 4) << 1) | ((attDec1 >> 4) & 1));
-	out(0x60, ((attDec1 << 1) | (attDec1 & 1)) & 0x1f);
-	out(0x70, (mulAmsFms1 & 0x20) ^ 0x20 ? (((sus1 & 0x0f) << 1) | 1) : 0);
-	out(0x80, sus1);
-
-	uint8 mulAmsFms2 = _driver->_chanState[chan].mulAmsFms = data[5];
-	uint8 tl2 = _driver->_chanState[chan].tl = (data[6] | 0x3f) - tLevelPara;
-	uint8 attDec2 = _driver->_chanState[chan].attDec = ~data[7];
-	uint8 sus2 = _driver->_chanState[chan].sus = ~data[8];
-	_driver->_chanState[chan].unk2 = data[9];
-
-	uint8 mul2 = mul[mulAmsFms2 & 0x0f];
-	tl2 = (tl2 & 0x3f) + 15;
-	uint8 ar2 = ((attDec2 >> 4) << 1) | ((attDec2 >> 4) & 1);
-	uint8 dec2 = ((attDec2 << 1) | (attDec2 & 1)) & 0x1f;
-	uint8 sus2r = (mulAmsFms2 & 0x20) ^ 0x20 ? (((sus2 & 0x0f) << 1) | 1) : 0;
-
-	for (int i = 4; i < 16; i += 4) {
-		out(0x30 + i, mul2);
-		out(0x40 + i, tl2);
-		out(0x50 + i, ar2);
-		out(0x60 + i, dec2);
-		out(0x70 + i, sus2r);
-		out(0x80 + i, sus2);
-	}
-
-	_driver->_chanState[chan].fgAlg = data[10];
-
-	uint8 alg = 5 + 2 * (data[10] & 1);
-	uint8 fb = 4 * (data[10] & 0x0e);
-	out(0xb0, fb | alg);
-	uint8 t = mulAmsFms1 | mulAmsFms2;
-	out(0xb4, (0xc0 | ((t & 0x80) >> 3) | ((t & 0x40) >> 5)));
-}
-
-void TownsMidiOutputChannel::setupEffects(int index, uint8 flags, const uint8 *effectData) {
-	uint16 effectMaxLevel[] = { 0x2FF, 0x1F, 0x07, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3E, 0x1F };
-	uint8 effectType[] = { 0x1D, 0x1C, 0x1B, 0x00, 0x03, 0x04, 0x07, 0x08, 0x0D, 0x10, 0x11, 0x14, 0x15, 0x1e, 0x1f, 0x00 };
-
-	EffectEnvelope *s = &_effectEnvelopes[index];
-	EffectDef *d = &_effectDefs[index];
-
-	d->phase = 0;
-	d->useModWheel = flags & 0x40;
-	s->loop = flags & 0x20;
-	d->loopRefresh = flags & 0x10;
-	d->type = effectType[flags & 0x0f];
-	s->maxLevel = effectMaxLevel[flags & 0x0f];
-	s->modWheelSensitivity = 31;
-	s->modWheelState = d->useModWheel ? _in->_modWheel >> 2 : 31;
-
-	switch (d->type) {
-	case 0:
-		s->startLevel = _operator2Tl;
-		break;
-	case 13:
-		s->startLevel = _operator1Tl;
-		break;
-	case 30:
-		s->startLevel = 31;
-		d->s->modWheelState = 0;
-		break;
-	case 31:
-		s->startLevel = 0;
-		d->s->modWheelSensitivity = 0;
-		break;
-	default:
-		s->startLevel = getEffectStartLevel(d->type);
-		break;
-	}
-
-	startEffect(s, effectData);
-}
-
-void TownsMidiOutputChannel::setModWheel(uint8 value) {
-	if (_effectEnvelopes[0].state != kEnvReady && _effectDefs[0].type)
-		_effectEnvelopes[0].modWheelState = value >> 2;
-
-	if (_effectEnvelopes[1].state != kEnvReady && _effectDefs[1].type)
-		_effectEnvelopes[1].modWheelState = value >> 2;
-}
-
-void TownsMidiOutputChannel::connect(TownsMidiInputChannel *chan) {
-	if (!chan)
-		return;
-
-	_in = chan;
-	_next = chan->_out;
-	_prev = 0;
-	chan->_out = this;
-	if (_next)
-		_next->_prev = this;
-}
-
-void TownsMidiOutputChannel::disconnect() {
-	keyOff();
-
-	TownsMidiOutputChannel *p = _prev;
-	TownsMidiOutputChannel *n = _next;
-
-	if (n)
-		n->_prev = p;
-	if (p)
-		p->_next = n;
-	else
-		_in->_out = n;
-	_in = 0;
-}
-
-bool TownsMidiOutputChannel::update() {
-	if (!_in)
-		return false;
-
-	if (_duration) {
-		_duration -= 17;
-		if (_duration <= 0) {
-			disconnect();
-			return true;
-		}
-	}
-
-	for (int i = 0; i < 2; i++) {
-		if (_effectEnvelopes[i].state != kEnvReady)
-			updateEffectGenerator(&_effectEnvelopes[i], &_effectDefs[i]);
-	}
-
-	return false;
-}
-
-int TownsMidiOutputChannel::checkPriority(int pri) {
-	if (!_in)
-		return kDisconnected;
-
-	if (!_next && pri >= _in->_priority)
-		return _in->_priority;
-
-	return kHighPriority;
-}
-
-void TownsMidiOutputChannel::startEffect(EffectEnvelope *s, const uint8 *effectData) {
-	s->state = kEnvAttacking;
-	s->currentLevel = 0;
-	s->modWheelLast = 31;
-	s->duration = effectData[0] * 63;
-	s->stateTargetLevels[0] = effectData[1];
-	s->stateTargetLevels[1] = effectData[3];
-	s->stateTargetLevels[2] = effectData[5];
-	s->stateTargetLevels[3] = effectData[6];
-	s->stateModWheelLevels[0] = effectData[2];
-	s->stateModWheelLevels[1] = effectData[4];
-	s->stateModWheelLevels[2] = 0;
-	s->stateModWheelLevels[3] = effectData[7];
-	initNextEnvelopeState(s);
-}
-
-void TownsMidiOutputChannel::updateEffectGenerator(EffectEnvelope *s, EffectDef *d) {
-	uint8 f = advanceEffectEnvelope(s, d);
-
-	if (f & 1) {
-		switch (d->type) {
-		case 0:
-			_operator2Tl = s->startLevel + d->phase;
-			break;
-		case 13:
-			_operator1Tl = s->startLevel + d->phase;
-			break;
-		case 30:
-			d->s->modWheelState = d->phase;
-			break;
-		case 31:
-			d->s->modWheelSensitivity = d->phase;
-			break;
-		default:
-			break;
-		}
-	}
-
-	if (f & 2) {
-		if (d->loopRefresh)
-			keyOn();
-	}
-}
-
-int TownsMidiOutputChannel::advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d) {
-	if (s->duration) {
-		s->duration -= 17;
-		if (s->duration <= 0) {
-			s->state = kEnvReady;
-			return 0;
-		}
-	}
-
-	int32 t = s->currentLevel + s->incrPerStep;
-
-	s->incrCountRem += s->incrPerStepRem;
-	if (s->incrCountRem >= s->numSteps) {
-		s->incrCountRem -= s->numSteps;
-		t += s->dir;
-	}
-
-	int retFlags = 0;
-
-	if (t != s->currentLevel || (s->modWheelState != s->modWheelLast)) {
-		s->currentLevel = t;
-		s->modWheelLast = s->modWheelState;
-		t = getEffectModLevel(t, s->modWheelState);
-		if (t != d->phase) {
-			d->phase = t;
-			retFlags |= 1;
-		}
-	}
-
-	if (--s->stepCounter)
-		return retFlags;
-
-	if (++s->state > kEnvReleasing) {
-		if (!s->loop) {
-			s->state = kEnvReady;
-			return retFlags;
-		}
-		s->state = kEnvAttacking;
-		retFlags |= 2;
-	}
-
-	initNextEnvelopeState(s);
-
-	return retFlags;
-}
-
-void TownsMidiOutputChannel::initNextEnvelopeState(EffectEnvelope *s) {
-	uint8 v = s->stateTargetLevels[s->state - 1];
-	int32 e = _effectEnvStepTable[_driver->_operatorLevelTable[((v & 0x7f) << 5) + s->modWheelSensitivity]];
-
-	if (v & 0x80)
-		e = _driver->randomValue(e);
-
-	if (!e)
-		e = 1;
-
-	s->numSteps = s->stepCounter = e;
-	int32 d = 0;
-
-	if (s->state != kEnvSustaining) {
-		v = s->stateModWheelLevels[s->state - 1];
-		e = getEffectModLevel(s->maxLevel, (v & 0x7f) - 31);
-
-		if (v & 0x80)
-			e = _driver->randomValue(e);
-
-		if (e + s->startLevel > s->maxLevel) {
-			e = s->maxLevel - s->startLevel;
-		} else {
-			if (e + s->startLevel < 0)
-				e = -s->startLevel;
-		}
-
-		d = e - s->currentLevel;
-	}
-
-	s->incrPerStep = d / s->numSteps;
-	s->dir = (d < 0) ? -1 : 1;
-	d *= s->dir;
-	s->incrPerStepRem = d % s->numSteps;
-	s->incrCountRem = 0;
-}
-
-int16 TownsMidiOutputChannel::getEffectStartLevel(uint8 type) {
-	uint8 chan = (type < 13) ? _chanMap2[_chan] : ((type < 26) ? _chanMap[_chan] : _chan);
-
-	if (type == 28)
-		return 15;
-	else if (type == 29)
-		return 383;
-	else if (type > 29)
-		return 0;
-	else if (type > 12)
-		type -= 13;
-
-	const uint8 *def = &_effectDefaults[type << 2];
-	uint8 res = (_driver->_chanState[chan].get(def[0] >> 5) & def[2]) >> def[1];
-	if (def[3])
-		res = def[3] - res;
-
-	return res;
-}
-
-int TownsMidiOutputChannel::getEffectModLevel(int lvl, int mod) {
-	if (mod == 0)
-		return 0;
-
-	if (mod == 31)
-		return lvl;
-
-	if (lvl > 63 || lvl < -63)
-		return ((lvl + 1) * mod) >> 5;
-
-	if (mod < 0) {
-		if (lvl < 0)
-			return _driver->_operatorLevelTable[((-lvl) << 5) - mod];
-		else
-			return -_driver->_operatorLevelTable[(lvl << 5) - mod];
-	} else {
-		if (lvl < 0)
-			return -_driver->_operatorLevelTable[((-lvl) << 5) + mod];
-		else
-			return _driver->_operatorLevelTable[(lvl << 5) + mod];
-	}
-
-	return 0;
-}
-
-void TownsMidiOutputChannel::keyOn() {
-	out(0x28, 0x30);
-}
-
-void TownsMidiOutputChannel::keyOff() {
-	out(0x28, 0);
-}
-
-void TownsMidiOutputChannel::keyOnSetFreq(uint16 frq) {
-	uint16 note = (frq << 1) >> 8;
-	frq = (_freqMSB[note] << 11) | _freqLSB[note];
-	out(0xa4, frq >> 8);
-	out(0xa0, frq & 0xff);
-	//out(0x28, 0x00);
-	out(0x28, 0x30);
-}
-
-void TownsMidiOutputChannel::out(uint8 reg, uint8 val) {
-	static const uint8 chanRegOffs[] = { 0, 1, 2, 0, 1, 2 };
-	static const uint8 keyValOffs[] = { 0, 1, 2, 4, 5, 6 };
-
-	if (reg == 0x28)
-		val = (val & 0xf0) | keyValOffs[_chan];
-	if (reg < 0x30)
-		_driver->_intf->callback(17, 0, reg, val);
-	else
-		_driver->_intf->callback(17, _chan / 3, (reg & ~3) | chanRegOffs[_chan], val);
-}
-
-const uint8 TownsMidiOutputChannel::_chanMap[] = {
-	0, 1, 2, 8, 9, 10
-};
-
-const uint8 TownsMidiOutputChannel::_chanMap2[] = {
-	3, 4, 5, 11, 12, 13
-};
-
-const uint8 TownsMidiOutputChannel::_effectDefaults[] = {
-	0x40, 0x00, 0x3F, 0x3F, 0xE0, 0x02, 0x00, 0x00, 0x40, 0x06, 0xC0, 0x00,
-	0x20, 0x00, 0x0F, 0x00, 0x60, 0x04, 0xF0, 0x0F, 0x60, 0x00, 0x0F, 0x0F,
-	0x80, 0x04, 0xF0, 0x0F, 0x80, 0x00, 0x0F, 0x0F, 0xE0, 0x00, 0x03, 0x00,
-	0x20, 0x07, 0x80, 0x00, 0x20, 0x06, 0x40, 0x00, 0x20, 0x05, 0x20, 0x00,
-	0x20, 0x04, 0x10, 0x00, 0xC0, 0x00, 0x01, 0x00, 0xC0, 0x01, 0x0E, 0x00
-};
-
-const uint16 TownsMidiOutputChannel::_effectEnvStepTable[] = {
-	0x0001, 0x0002, 0x0004, 0x0005, 0x0006, 0x0007,	0x0008, 0x0009,
-	0x000A, 0x000C, 0x000E, 0x0010,	0x0012, 0x0015, 0x0018, 0x001E,
-	0x0024, 0x0032,	0x0040, 0x0052, 0x0064, 0x0088, 0x00A0, 0x00C0,
-	0x00F0, 0x0114, 0x0154, 0x01CC, 0x0258, 0x035C,	0x04B0, 0x0640
-};
-
-const uint8 TownsMidiOutputChannel::_freqMSB[] = {
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
-	0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
-	0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
-	0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
-	0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
-	0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
-	0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
-	0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
-	0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x80, 0x81, 0x83, 0x85,
-	0x87, 0x88, 0x8A, 0x8C, 0x8E, 0x8F, 0x91, 0x93, 0x95, 0x96, 0x98, 0x9A,
-	0x9C, 0x9E, 0x9F, 0xA1, 0xA3, 0xA5, 0xA6, 0xA8, 0xAA, 0xAC, 0xAD, 0xAF,
-	0xB1, 0xB3, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBD, 0xBF, 0xC1, 0xC3, 0xC4,
-	0xC6, 0xC8, 0xCA, 0xCB, 0xCD, 0xCF, 0xD1, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA,
-	0xDB, 0xDD, 0xDF, 0xE1, 0xE2, 0xE4, 0xE6, 0xE8, 0xE9, 0xEB, 0xED, 0xEF
-};
-
-const uint16 TownsMidiOutputChannel::_freqLSB[] = {
-	0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6,
-	0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x0301, 0x032F,
-	0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
-	0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
-	0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
-	0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
-	0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
-	0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
-	0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
-	0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
-	0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
-	0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
-	0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
-	0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
-	0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
-	0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B
-};
-
-TownsMidiInputChannel::TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex) : MidiChannel(), _driver(driver), _out(0), _chanIndex(chanIndex),
-	_priority(0), _tl(0), _transpose(0), _pitchBendFactor(0), _pitchBend(0), _sustain(0), _freqLSB(0), _detune(0), _modWheel(0), _allocated(false) {
-	_instrument = new uint8[30];
-	memset(_instrument, 0, 30);
-}
-
-TownsMidiInputChannel::~TownsMidiInputChannel() {
-	delete[] _instrument;
-}
-
-bool TownsMidiInputChannel::allocate() {
-	if (_allocated)
-		return false;
-	_allocated = true;
-	return true;
-}
-
-void TownsMidiInputChannel::release() {
-	_allocated = false;
-}
-
-void TownsMidiInputChannel::send(uint32 b) {
-	_driver->send(b | _chanIndex);
-}
-
-void TownsMidiInputChannel::noteOff(byte note) {
-	if (!_out)
-		return;
-
-	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
-		if (oc->_note != note)
-			continue;
-
-		if (_sustain)
-			oc->_sustainNoteOff = 1;
-		else
-			oc->disconnect();
-	}
-}
-
-void TownsMidiInputChannel::noteOn(byte note, byte velocity) {
-	TownsMidiOutputChannel *oc = _driver->allocateOutputChannel(_priority);
-
-	if (!oc)
-		return;
-
-	oc->connect(this);
-
-	oc->_adjustModTl = _instrument[10] & 1;
-	oc->_note = note;
-	oc->_sustainNoteOff = 0;
-	oc->_duration = _instrument[29] * 63;
-
-	oc->_operator1Tl = (_instrument[1] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[4] >> 2)];
-	if (oc->_operator1Tl > 63)
-		oc->_operator1Tl = 63;
-
-	oc->_operator2Tl = (_instrument[6] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[9] >> 2)];
-	if (oc->_operator2Tl > 63)
-		oc->_operator2Tl = 63;
-
-	oc->setupProgram(_instrument, oc->_adjustModTl == 1 ? _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator1Tl << 5)]] : oc->_operator1Tl, _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator2Tl << 5)]]);
-	oc->noteOn(note + _transpose, _freqLSB);
-
-	if (_instrument[11] & 0x80)
-		oc->setupEffects(0, _instrument[11], &_instrument[12]);
-	else
-		oc->_effectEnvelopes[0].state = kEnvReady;
-
-	if (_instrument[20] & 0x80)
-		oc->setupEffects(1, _instrument[20], &_instrument[21]);
-	else
-		oc->_effectEnvelopes[1].state = kEnvReady;
-}
-
-void TownsMidiInputChannel::programChange(byte program) {
-	// Not implemented (The loading and assignment of programs
-	// is handled externally by the SCUMM engine. The programs
-	// get sent via sysEx_customInstrument.)
-}
-
-void TownsMidiInputChannel::pitchBend(int16 bend) {
-	_pitchBend = bend;
-	_freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
-	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
-		oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
-}
-
-void TownsMidiInputChannel::controlChange(byte control, byte value) {
-	switch (control) {
-	case 1:
-		controlModulationWheel(value);
-		break;
-	case 7:
-		controlVolume(value);
-		break;
-	case 10:
-		controlPanPos(value);
-		break;
-	case 64:
-		controlSustain(value);
-		break;
-	case 123:
-		while (_out)
-			_out->disconnect();
-		break;
-	default:
-		break;
-	}
-}
-
-void TownsMidiInputChannel::pitchBendFactor(byte value) {
-	_pitchBendFactor = value;
-	_freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
-	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
-		oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
-}
-
-void TownsMidiInputChannel::priority(byte value) {
-	_priority = value;
-}
-
-void TownsMidiInputChannel::sysEx_customInstrument(uint32 type, const byte *instr) {
-	memcpy(_instrument, instr, 30);
-}
-
-void TownsMidiInputChannel::controlModulationWheel(byte value) {
-	_modWheel = value;
-	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
-		oc->setModWheel(value);
-}
-
-void TownsMidiInputChannel::controlVolume(byte value) {
-	/* This is all done inside the imuse code
-	uint16 v1 = _ctrlVolume + 1;
-	uint16 v2 = value;
-	if (_chanIndex != 16) {
-		_ctrlVolume = value;
-		v2 = _player->getEffectiveVolume();
-	}
-	_tl = (v1 * v2) >> 7;*/
-
-	_tl = value;
-}
-
-void TownsMidiInputChannel::controlPanPos(byte value) {
-	// not implemented
-}
-
-void TownsMidiInputChannel::controlSustain(byte value) {
-	_sustain = value;
-	if (!value)
-		releasePedal();
-}
-
-void TownsMidiInputChannel::releasePedal() {
-	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
-		if (oc->_sustainNoteOff)
-			oc->disconnect();
-	}
-}
-
-const uint8 TownsMidiInputChannel::_programAdjustLevel[] = {
-	0x00, 0x04, 0x07, 0x0B, 0x0D, 0x10, 0x12, 0x14,
-	0x16, 0x18, 0x1A, 0x1B, 0x1D, 0x1E, 0x1F, 0x21,
-	0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
-	0x2A, 0x2B, 0x2C, 0x2C, 0x2D, 0x2E, 0x2F, 0x2F,
-	0x30, 0x31, 0x31, 0x32, 0x33, 0x33, 0x34, 0x35,
-	0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x38, 0x39,
-	0x39, 0x3A, 0x3A, 0x3B, 0x3B, 0x3C, 0x3C, 0x3C,
-	0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F
-};
-
-MidiDriver_TOWNS::MidiDriver_TOWNS(Audio::Mixer *mixer) : _timerProc(0), _timerProcPara(0), _channels(0), _out(0),
-	_baseTempo(10080), _chanState(0), _operatorLevelTable(0), _tickCounter(0), _rand(1), _allocCurPos(0), _isOpen(false) {
-	// We set exteral mutex handling to true to avoid lockups in SCUMM which has its own mutex.
-	_intf = new TownsAudioInterface(mixer, this, true);
-
-	_channels = new TownsMidiInputChannel*[32];
-	for (int i = 0; i < 32; i++)
-		_channels[i] = new TownsMidiInputChannel(this, i > 8 ? (i + 1) : i);
-
-	_out = new TownsMidiOutputChannel*[6];
-	for (int i = 0; i < 6; i++)
-		_out[i] = new TownsMidiOutputChannel(this, i);
-
-	_chanState = new TownsMidiChanState[32];
-
-	_operatorLevelTable = new uint8[2048];
-	for (int i = 0; i < 64; i++) {
-		for (int ii = 0; ii < 32; ii++)
-			_operatorLevelTable[(i << 5) + ii] = ((i * (ii + 1)) >> 5) & 0xff;
-	}
-	for (int i = 0; i < 64; i++)
-		_operatorLevelTable[i << 5] = 0;
-}
-
-MidiDriver_TOWNS::~MidiDriver_TOWNS() {
-	close();
-	delete _intf;
-
-	if (_channels) {
-		for (int i = 0; i < 32; i++)
-			delete _channels[i];
-		delete[] _channels;
-	}
-	_channels = 0;
-
-	if (_out) {
-		for (int i = 0; i < 6; i++)
-			delete _out[i];
-		delete[] _out;
-	}
-	_out = 0;
-
-	delete[] _chanState;
-	_chanState = 0;
-	delete[] _operatorLevelTable;
-	_operatorLevelTable = 0;
-}
-
-int MidiDriver_TOWNS::open() {
-	if (_isOpen)
-		return MERR_ALREADY_OPEN;
-
-	if (!_intf->init())
-		return MERR_CANNOT_CONNECT;
-
-	_intf->callback(0);
-
-	_intf->callback(21, 255, 1);
-	_intf->callback(21, 0, 1);
-	_intf->callback(22, 255, 221);
-
-	_intf->callback(33, 8);
-	_intf->setSoundEffectChanMask(~0x3f);
-
-	_allocCurPos = 0;
-
-	_isOpen = true;
-
-	return 0;
-}
-
-void MidiDriver_TOWNS::close() {
-	if (!_isOpen)
-		return;
-
-	_isOpen = false;
-
-	setTimerCallback(0, 0);
-	g_system->delayMillis(20);
-}
-
-void MidiDriver_TOWNS::send(uint32 b) {
-	if (!_isOpen)
-		return;
-
-	byte param2 = (b >> 16) & 0xFF;
-	byte param1 = (b >> 8) & 0xFF;
-	byte cmd = b & 0xF0;
-
-	TownsMidiInputChannel *c = _channels[b & 0x0F];
-
-	switch (cmd) {
-	case 0x80:
-		c->noteOff(param1);
-		break;
-	case 0x90:
-		if (param2)
-			c->noteOn(param1, param2);
-		else
-			c->noteOff(param1);
-		break;
-	case 0xB0:
-		c->controlChange(param1, param2);
-		break;
-	case 0xC0:
-		c->programChange(param1);
-		break;
-	case 0xE0:
-		c->pitchBend((param1 | (param2 << 7)) - 0x2000);
-		break;
-	case 0xF0:
-		warning("MidiDriver_TOWNS: Receiving SysEx command on a send() call");
-		break;
-
-	default:
-		break;
-	}
-}
-
-void MidiDriver_TOWNS::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
-	_timerProc = timer_proc;
-	_timerProcPara = timer_param;
-}
-
-uint32 MidiDriver_TOWNS::getBaseTempo() {
-	return _baseTempo;
-}
-
-MidiChannel *MidiDriver_TOWNS::allocateChannel() {
-	if (!_isOpen)
-		return 0;
-
-	for (int i = 0; i < 32; ++i) {
-		TownsMidiInputChannel *chan = _channels[i];
-		if (chan->allocate())
-			return chan;
-	}
-
-	return 0;
-}
-
-MidiChannel *MidiDriver_TOWNS::getPercussionChannel() {
-	return 0;
-}
-
-void MidiDriver_TOWNS::timerCallback(int timerId) {
-	if (!_isOpen)
-		return;
-
-	switch (timerId) {
-	case 1:
-		updateParser();
-		updateOutputChannels();
-		break;
-	default:
-		break;
-	}
-}
-
-void MidiDriver_TOWNS::updateParser() {
-	if (_timerProc)
-		_timerProc(_timerProcPara);
-}
-
-void MidiDriver_TOWNS::updateOutputChannels() {
-	_tickCounter += _baseTempo;
-	while (_tickCounter >= 16667) {
-		_tickCounter -= 16667;
-		for (int i = 0; i < 6; i++) {
-			if (_out[i]->update())
-				return;
-		}
-	}
-}
-
-TownsMidiOutputChannel *MidiDriver_TOWNS::allocateOutputChannel(uint8 pri) {
-	TownsMidiOutputChannel *res = 0;
-
-	for (int i = 0; i < 6; i++) {
-		if (++_allocCurPos == 6)
-			_allocCurPos = 0;
-
-		int s = _out[_allocCurPos]->checkPriority(pri);
-		if (s == TownsMidiOutputChannel::kDisconnected)
-			return _out[_allocCurPos];
-
-		if (s != TownsMidiOutputChannel::kHighPriority) {
-			pri = s;
-			res = _out[_allocCurPos];
-		}
-	}
-
-	if (res)
-		res->disconnect();
-
-	return res;
-}
-
-int MidiDriver_TOWNS::randomValue(int para) {
-	_rand = (_rand & 1) ? (_rand >> 1) ^ 0xb8 : (_rand >> 1);
-	return (_rand * para) >> 8;
-}
diff --git a/audio/softsynth/fmtowns_pc98/towns_midi.h b/audio/softsynth/fmtowns_pc98/towns_midi.h
deleted file mode 100644
index 1143dba..0000000
--- a/audio/softsynth/fmtowns_pc98/towns_midi.h
+++ /dev/null
@@ -1,83 +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 TOWNS_MIDI_H
-#define TOWNS_MIDI_H
-
-#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
-#include "audio/mididrv.h"
-
-
-class TownsMidiOutputChannel;
-class TownsMidiInputChannel;
-class TownsMidiChanState;
-
-class MidiDriver_TOWNS : public MidiDriver, public TownsAudioInterfacePluginDriver {
-friend class TownsMidiInputChannel;
-friend class TownsMidiOutputChannel;
-public:
-	MidiDriver_TOWNS(Audio::Mixer *mixer);
-	~MidiDriver_TOWNS();
-
-	int open();
-	bool isOpen() const { return _isOpen; }
-	void close();
-
-	void send(uint32 b);
-
-	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
-
-	uint32 getBaseTempo();
-	MidiChannel *allocateChannel();
-	MidiChannel *getPercussionChannel();
-
-	void timerCallback(int timerId);
-
-private:
-	void updateParser();
-	void updateOutputChannels();
-
-	TownsMidiOutputChannel *allocateOutputChannel(uint8 pri);
-
-	int randomValue(int para);
-
-	TownsMidiInputChannel **_channels;
-	TownsMidiOutputChannel **_out;
-	TownsMidiChanState *_chanState;
-
-	Common::TimerManager::TimerProc _timerProc;
-	void *_timerProcPara;
-
-	TownsAudioInterface *_intf;
-
-	uint32 _tickCounter;
-	uint8 _allocCurPos;
-	uint8 _rand;
-
-	bool _isOpen;
-
-	uint8 *_operatorLevelTable;
-
-	const uint16 _baseTempo;
-};
-
-#endif
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp b/audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
index 194bfc4..4103484 100644
--- a/audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_plugins.cpp
@@ -20,7 +20,6 @@
  *
  */
 
-#include "audio/softsynth/fmtowns_pc98/towns_midi.h"
 #include "audio/musicplugin.h"
 #include "common/translation.h"
 #include "common/error.h"
@@ -48,8 +47,8 @@ MusicDevices TownsEmuMusicPlugin::getDevices() const {
 }
 
 Common::Error TownsEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
-	*mididriver = new MidiDriver_TOWNS(g_system->getMixer());
-	return Common::kNoError;
+	*mididriver = 0;
+	return Common::kUnknownError;
 }
 
 class PC98EmuMusicPlugin : public MusicPluginObject {
@@ -73,8 +72,8 @@ MusicDevices PC98EmuMusicPlugin::getDevices() const {
 }
 
 Common::Error PC98EmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
-	//*mididriver = /**/
-	return Common::kNoError;
+	*mididriver = 0;
+	return Common::kUnknownError;
 }
 
 //#if PLUGIN_ENABLED_DYNAMIC(TOWNS)
diff --git a/engines/scumm/imuse/drivers/fmtowns.cpp b/engines/scumm/imuse/drivers/fmtowns.cpp
new file mode 100644
index 0000000..37765e0
--- /dev/null
+++ b/engines/scumm/imuse/drivers/fmtowns.cpp
@@ -0,0 +1,1029 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/scumm/imuse/drivers/fmtowns.h"
+#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
+#include "common/textconsole.h"
+#include "common/system.h"
+
+class TownsMidiOutputChannel {
+friend class TownsMidiInputChannel;
+public:
+	TownsMidiOutputChannel(MidiDriver_TOWNS *driver, int chanId);
+	~TownsMidiOutputChannel();
+
+	void noteOn(uint8 msb, uint16 lsb);
+	void noteOnPitchBend(uint8 msb, uint16 lsb);
+	void setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara);
+	void setupEffects(int index, uint8 flags, const uint8 *effectData);
+	void setModWheel(uint8 value);
+
+	void connect(TownsMidiInputChannel *chan);
+	void disconnect();
+
+	bool update();
+
+	enum CheckPriorityStatus {
+		kDisconnected = -2,
+		kHighPriority = -1
+	};
+
+	int checkPriority(int pri);
+
+private:
+	struct EffectEnvelope {
+		uint8 state;
+		int32 currentLevel;
+		int32 duration;
+		int32 maxLevel;
+		int32 startLevel;
+		uint8 loop;
+		uint8 stateTargetLevels[4];
+		uint8 stateModWheelLevels[4];
+		int8 modWheelSensitivity;
+		int8 modWheelState;
+		int8 modWheelLast;
+		uint16 numSteps;
+		uint32 stepCounter;
+		int32 incrPerStep;
+		int8 dir;
+		uint32 incrPerStepRem;
+		uint32 incrCountRem;
+	} *_effectEnvelopes;
+
+	struct EffectDef {
+		int32 phase;
+		uint8 type;
+		uint8 useModWheel;
+		uint8 loopRefresh;
+		EffectEnvelope *s;
+	} *_effectDefs;
+
+	void startEffect(EffectEnvelope *s, const uint8 *effectData);
+	void updateEffectGenerator(EffectEnvelope *s, EffectDef *d);
+	int advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d);
+	void initNextEnvelopeState(EffectEnvelope *s);
+	int16 getEffectStartLevel(uint8 type);
+	int getEffectModLevel(int lvl, int mod);
+
+	void keyOn();
+	void keyOff();
+	void keyOnSetFreq(uint16 frq);
+	void out(uint8 reg, uint8 val);
+
+	TownsMidiInputChannel *_in;
+	TownsMidiOutputChannel *_prev;
+	TownsMidiOutputChannel *_next;
+	uint8 _adjustModTl;
+	uint8 _chan;
+	uint8 _note;
+	uint8 _operator2Tl;
+	uint8 _operator1Tl;
+	uint8 _sustainNoteOff;
+	int16 _duration;
+
+	uint16 _freq;
+	int16 _freqAdjust;
+
+	MidiDriver_TOWNS *_driver;
+
+	static const uint8 _chanMap[];
+	static const uint8 _chanMap2[];
+	static const uint8 _effectDefaults[];
+	static const uint16 _effectEnvStepTable[];
+	static const uint8 _freqMSB[];
+	static const uint16 _freqLSB[];
+};
+
+class TownsMidiInputChannel : public MidiChannel {
+friend class TownsMidiOutputChannel;
+public:
+	TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex);
+	~TownsMidiInputChannel();
+
+	MidiDriver *device() { return _driver; }
+	byte getNumber() { return _chanIndex; }
+	bool allocate();
+	void release();
+
+	void send(uint32 b);
+
+	void noteOff(byte note);
+	void noteOn(byte note, byte velocity);
+	void programChange(byte program);
+	void pitchBend(int16 bend);
+	void controlChange(byte control, byte value);
+	void pitchBendFactor(byte value);
+	void priority(byte value);
+	void sysEx_customInstrument(uint32 type, const byte *instr);
+
+private:
+	void controlModulationWheel(byte value);
+	void controlVolume(byte value);
+	void controlPanPos(byte value);
+	void controlSustain(byte value);
+
+	void releasePedal();
+
+	TownsMidiOutputChannel *_out;
+
+	uint8 *_instrument;
+	uint8 _chanIndex;
+	uint8 _priority;
+	uint8 _tl;
+	int8 _transpose;
+	int8 _detune;
+	int8 _modWheel;
+	uint8 _sustain;
+	uint8 _pitchBendFactor;
+	int16 _pitchBend;
+	uint16 _freqLSB;
+
+	bool _allocated;
+
+	MidiDriver_TOWNS *_driver;
+
+	static const uint8 _programAdjustLevel[];
+};
+
+class TownsMidiChanState {
+public:
+	TownsMidiChanState();
+	~TownsMidiChanState() {}
+	uint8 get(uint8 type);
+
+	uint8 unk1;
+	uint8 mulAmsFms;
+	uint8 tl;
+	uint8 attDec;
+	uint8 sus;
+	uint8 fgAlg;
+	uint8 unk2;
+};
+
+TownsMidiChanState::TownsMidiChanState() {
+	unk1 = mulAmsFms = tl =	attDec = sus = fgAlg = unk2 = 0;
+}
+
+uint8 TownsMidiChanState::get(uint8 type) {
+	switch (type) {
+	case 0:
+		return unk1;
+	case 1:
+		return mulAmsFms;
+	case 2:
+		return tl;
+	case 3:
+		return attDec;
+	case 4:
+		return sus;
+	case 5:
+		return fgAlg;
+	case 6:
+		return unk2;
+	default:
+		break;
+	}
+	return 0;
+}
+
+TownsMidiOutputChannel::TownsMidiOutputChannel(MidiDriver_TOWNS *driver, int chanIndex) : _driver(driver), _chan(chanIndex),
+	_in(0), _prev(0), _next(0), _adjustModTl(0), _operator2Tl(0), _note(0), _operator1Tl(0), _sustainNoteOff(0), _duration(0), _freq(0), _freqAdjust(0) {
+	_effectEnvelopes = new EffectEnvelope[2];
+	_effectDefs = new EffectDef[2];
+
+	memset(_effectEnvelopes, 0, 2 * sizeof(EffectEnvelope));
+	memset(_effectDefs, 0, 2 * sizeof(EffectDef));
+	_effectDefs[0].s = &_effectEnvelopes[1];
+	_effectDefs[1].s = &_effectEnvelopes[0];
+}
+
+TownsMidiOutputChannel::~TownsMidiOutputChannel() {
+	delete[] _effectEnvelopes;
+	delete[] _effectDefs;
+}
+
+void TownsMidiOutputChannel::noteOn(uint8 msb, uint16 lsb) {
+	_freq = (msb << 7) + lsb;
+	_freqAdjust = 0;
+	keyOnSetFreq(_freq);
+}
+
+void TownsMidiOutputChannel::noteOnPitchBend(uint8 msb, uint16 lsb) {
+	_freq = (msb << 7) + lsb;
+	keyOnSetFreq(_freq + _freqAdjust);
+}
+
+void TownsMidiOutputChannel::setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara) {
+	// This driver uses only 2 operators and 2 algorithms (algorithm 5 and 7),
+	// since it is just a modified AdLib driver. It also uses AdLib programs.
+	// There are no FM-TOWNS specific programs. This is the reason for the low quality of the FM-TOWNS
+	// music (unsuitable data is just forced into the wrong audio device).
+
+	static const uint8 mul[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 };
+	uint8 chan = _chanMap[_chan];
+
+	uint8 mulAmsFms1 = _driver->_chanState[chan].mulAmsFms = data[0];
+	uint8 tl1 = _driver->_chanState[chan].tl = (data[1] | 0x3f) - mLevelPara;
+	uint8 attDec1 = _driver->_chanState[chan].attDec = ~data[2];
+	uint8 sus1 = _driver->_chanState[chan].sus = ~data[3];
+	_driver->_chanState[chan].unk2 = data[4];
+	chan += 3;
+
+	out(0x30, mul[mulAmsFms1 & 0x0f]);
+	out(0x40, (tl1 & 0x3f) + 15);
+	out(0x50, ((attDec1 >> 4) << 1) | ((attDec1 >> 4) & 1));
+	out(0x60, ((attDec1 << 1) | (attDec1 & 1)) & 0x1f);
+	out(0x70, (mulAmsFms1 & 0x20) ^ 0x20 ? (((sus1 & 0x0f) << 1) | 1) : 0);
+	out(0x80, sus1);
+
+	uint8 mulAmsFms2 = _driver->_chanState[chan].mulAmsFms = data[5];
+	uint8 tl2 = _driver->_chanState[chan].tl = (data[6] | 0x3f) - tLevelPara;
+	uint8 attDec2 = _driver->_chanState[chan].attDec = ~data[7];
+	uint8 sus2 = _driver->_chanState[chan].sus = ~data[8];
+	_driver->_chanState[chan].unk2 = data[9];
+
+	uint8 mul2 = mul[mulAmsFms2 & 0x0f];
+	tl2 = (tl2 & 0x3f) + 15;
+	uint8 ar2 = ((attDec2 >> 4) << 1) | ((attDec2 >> 4) & 1);
+	uint8 dec2 = ((attDec2 << 1) | (attDec2 & 1)) & 0x1f;
+	uint8 sus2r = (mulAmsFms2 & 0x20) ^ 0x20 ? (((sus2 & 0x0f) << 1) | 1) : 0;
+
+	for (int i = 4; i < 16; i += 4) {
+		out(0x30 + i, mul2);
+		out(0x40 + i, tl2);
+		out(0x50 + i, ar2);
+		out(0x60 + i, dec2);
+		out(0x70 + i, sus2r);
+		out(0x80 + i, sus2);
+	}
+
+	_driver->_chanState[chan].fgAlg = data[10];
+
+	uint8 alg = 5 + 2 * (data[10] & 1);
+	uint8 fb = 4 * (data[10] & 0x0e);
+	out(0xb0, fb | alg);
+	uint8 t = mulAmsFms1 | mulAmsFms2;
+	out(0xb4, (0xc0 | ((t & 0x80) >> 3) | ((t & 0x40) >> 5)));
+}
+
+void TownsMidiOutputChannel::setupEffects(int index, uint8 flags, const uint8 *effectData) {
+	uint16 effectMaxLevel[] = { 0x2FF, 0x1F, 0x07, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3E, 0x1F };
+	uint8 effectType[] = { 0x1D, 0x1C, 0x1B, 0x00, 0x03, 0x04, 0x07, 0x08, 0x0D, 0x10, 0x11, 0x14, 0x15, 0x1e, 0x1f, 0x00 };
+
+	EffectEnvelope *s = &_effectEnvelopes[index];
+	EffectDef *d = &_effectDefs[index];
+
+	d->phase = 0;
+	d->useModWheel = flags & 0x40;
+	s->loop = flags & 0x20;
+	d->loopRefresh = flags & 0x10;
+	d->type = effectType[flags & 0x0f];
+	s->maxLevel = effectMaxLevel[flags & 0x0f];
+	s->modWheelSensitivity = 31;
+	s->modWheelState = d->useModWheel ? _in->_modWheel >> 2 : 31;
+
+	switch (d->type) {
+	case 0:
+		s->startLevel = _operator2Tl;
+		break;
+	case 13:
+		s->startLevel = _operator1Tl;
+		break;
+	case 30:
+		s->startLevel = 31;
+		d->s->modWheelState = 0;
+		break;
+	case 31:
+		s->startLevel = 0;
+		d->s->modWheelSensitivity = 0;
+		break;
+	default:
+		s->startLevel = getEffectStartLevel(d->type);
+		break;
+	}
+
+	startEffect(s, effectData);
+}
+
+void TownsMidiOutputChannel::setModWheel(uint8 value) {
+	if (_effectEnvelopes[0].state != kEnvReady && _effectDefs[0].type)
+		_effectEnvelopes[0].modWheelState = value >> 2;
+
+	if (_effectEnvelopes[1].state != kEnvReady && _effectDefs[1].type)
+		_effectEnvelopes[1].modWheelState = value >> 2;
+}
+
+void TownsMidiOutputChannel::connect(TownsMidiInputChannel *chan) {
+	if (!chan)
+		return;
+
+	_in = chan;
+	_next = chan->_out;
+	_prev = 0;
+	chan->_out = this;
+	if (_next)
+		_next->_prev = this;
+}
+
+void TownsMidiOutputChannel::disconnect() {
+	keyOff();
+
+	TownsMidiOutputChannel *p = _prev;
+	TownsMidiOutputChannel *n = _next;
+
+	if (n)
+		n->_prev = p;
+	if (p)
+		p->_next = n;
+	else
+		_in->_out = n;
+	_in = 0;
+}
+
+bool TownsMidiOutputChannel::update() {
+	if (!_in)
+		return false;
+
+	if (_duration) {
+		_duration -= 17;
+		if (_duration <= 0) {
+			disconnect();
+			return true;
+		}
+	}
+
+	for (int i = 0; i < 2; i++) {
+		if (_effectEnvelopes[i].state != kEnvReady)
+			updateEffectGenerator(&_effectEnvelopes[i], &_effectDefs[i]);
+	}
+
+	return false;
+}
+
+int TownsMidiOutputChannel::checkPriority(int pri) {
+	if (!_in)
+		return kDisconnected;
+
+	if (!_next && pri >= _in->_priority)
+		return _in->_priority;
+
+	return kHighPriority;
+}
+
+void TownsMidiOutputChannel::startEffect(EffectEnvelope *s, const uint8 *effectData) {
+	s->state = kEnvAttacking;
+	s->currentLevel = 0;
+	s->modWheelLast = 31;
+	s->duration = effectData[0] * 63;
+	s->stateTargetLevels[0] = effectData[1];
+	s->stateTargetLevels[1] = effectData[3];
+	s->stateTargetLevels[2] = effectData[5];
+	s->stateTargetLevels[3] = effectData[6];
+	s->stateModWheelLevels[0] = effectData[2];
+	s->stateModWheelLevels[1] = effectData[4];
+	s->stateModWheelLevels[2] = 0;
+	s->stateModWheelLevels[3] = effectData[7];
+	initNextEnvelopeState(s);
+}
+
+void TownsMidiOutputChannel::updateEffectGenerator(EffectEnvelope *s, EffectDef *d) {
+	uint8 f = advanceEffectEnvelope(s, d);
+
+	if (f & 1) {
+		switch (d->type) {
+		case 0:
+			_operator2Tl = s->startLevel + d->phase;
+			break;
+		case 13:
+			_operator1Tl = s->startLevel + d->phase;
+			break;
+		case 30:
+			d->s->modWheelState = d->phase;
+			break;
+		case 31:
+			d->s->modWheelSensitivity = d->phase;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (f & 2) {
+		if (d->loopRefresh)
+			keyOn();
+	}
+}
+
+int TownsMidiOutputChannel::advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d) {
+	if (s->duration) {
+		s->duration -= 17;
+		if (s->duration <= 0) {
+			s->state = kEnvReady;
+			return 0;
+		}
+	}
+
+	int32 t = s->currentLevel + s->incrPerStep;
+
+	s->incrCountRem += s->incrPerStepRem;
+	if (s->incrCountRem >= s->numSteps) {
+		s->incrCountRem -= s->numSteps;
+		t += s->dir;
+	}
+
+	int retFlags = 0;
+
+	if (t != s->currentLevel || (s->modWheelState != s->modWheelLast)) {
+		s->currentLevel = t;
+		s->modWheelLast = s->modWheelState;
+		t = getEffectModLevel(t, s->modWheelState);
+		if (t != d->phase) {
+			d->phase = t;
+			retFlags |= 1;
+		}
+	}
+
+	if (--s->stepCounter)
+		return retFlags;
+
+	if (++s->state > kEnvReleasing) {
+		if (!s->loop) {
+			s->state = kEnvReady;
+			return retFlags;
+		}
+		s->state = kEnvAttacking;
+		retFlags |= 2;
+	}
+
+	initNextEnvelopeState(s);
+
+	return retFlags;
+}
+
+void TownsMidiOutputChannel::initNextEnvelopeState(EffectEnvelope *s) {
+	uint8 v = s->stateTargetLevels[s->state - 1];
+	int32 e = _effectEnvStepTable[_driver->_operatorLevelTable[((v & 0x7f) << 5) + s->modWheelSensitivity]];
+
+	if (v & 0x80)
+		e = _driver->randomValue(e);
+
+	if (!e)
+		e = 1;
+
+	s->numSteps = s->stepCounter = e;
+	int32 d = 0;
+
+	if (s->state != kEnvSustaining) {
+		v = s->stateModWheelLevels[s->state - 1];
+		e = getEffectModLevel(s->maxLevel, (v & 0x7f) - 31);
+
+		if (v & 0x80)
+			e = _driver->randomValue(e);
+
+		if (e + s->startLevel > s->maxLevel) {
+			e = s->maxLevel - s->startLevel;
+		} else {
+			if (e + s->startLevel < 0)
+				e = -s->startLevel;
+		}
+
+		d = e - s->currentLevel;
+	}
+
+	s->incrPerStep = d / s->numSteps;
+	s->dir = (d < 0) ? -1 : 1;
+	d *= s->dir;
+	s->incrPerStepRem = d % s->numSteps;
+	s->incrCountRem = 0;
+}
+
+int16 TownsMidiOutputChannel::getEffectStartLevel(uint8 type) {
+	uint8 chan = (type < 13) ? _chanMap2[_chan] : ((type < 26) ? _chanMap[_chan] : _chan);
+
+	if (type == 28)
+		return 15;
+	else if (type == 29)
+		return 383;
+	else if (type > 29)
+		return 0;
+	else if (type > 12)
+		type -= 13;
+
+	const uint8 *def = &_effectDefaults[type << 2];
+	uint8 res = (_driver->_chanState[chan].get(def[0] >> 5) & def[2]) >> def[1];
+	if (def[3])
+		res = def[3] - res;
+
+	return res;
+}
+
+int TownsMidiOutputChannel::getEffectModLevel(int lvl, int mod) {
+	if (mod == 0)
+		return 0;
+
+	if (mod == 31)
+		return lvl;
+
+	if (lvl > 63 || lvl < -63)
+		return ((lvl + 1) * mod) >> 5;
+
+	if (mod < 0) {
+		if (lvl < 0)
+			return _driver->_operatorLevelTable[((-lvl) << 5) - mod];
+		else
+			return -_driver->_operatorLevelTable[(lvl << 5) - mod];
+	} else {
+		if (lvl < 0)
+			return -_driver->_operatorLevelTable[((-lvl) << 5) + mod];
+		else
+			return _driver->_operatorLevelTable[(lvl << 5) + mod];
+	}
+
+	return 0;
+}
+
+void TownsMidiOutputChannel::keyOn() {
+	out(0x28, 0x30);
+}
+
+void TownsMidiOutputChannel::keyOff() {
+	out(0x28, 0);
+}
+
+void TownsMidiOutputChannel::keyOnSetFreq(uint16 frq) {
+	uint16 note = (frq << 1) >> 8;
+	frq = (_freqMSB[note] << 11) | _freqLSB[note];
+	out(0xa4, frq >> 8);
+	out(0xa0, frq & 0xff);
+	//out(0x28, 0x00);
+	out(0x28, 0x30);
+}
+
+void TownsMidiOutputChannel::out(uint8 reg, uint8 val) {
+	static const uint8 chanRegOffs[] = { 0, 1, 2, 0, 1, 2 };
+	static const uint8 keyValOffs[] = { 0, 1, 2, 4, 5, 6 };
+
+	if (reg == 0x28)
+		val = (val & 0xf0) | keyValOffs[_chan];
+	if (reg < 0x30)
+		_driver->_intf->callback(17, 0, reg, val);
+	else
+		_driver->_intf->callback(17, _chan / 3, (reg & ~3) | chanRegOffs[_chan], val);
+}
+
+const uint8 TownsMidiOutputChannel::_chanMap[] = {
+	0, 1, 2, 8, 9, 10
+};
+
+const uint8 TownsMidiOutputChannel::_chanMap2[] = {
+	3, 4, 5, 11, 12, 13
+};
+
+const uint8 TownsMidiOutputChannel::_effectDefaults[] = {
+	0x40, 0x00, 0x3F, 0x3F, 0xE0, 0x02, 0x00, 0x00, 0x40, 0x06, 0xC0, 0x00,
+	0x20, 0x00, 0x0F, 0x00, 0x60, 0x04, 0xF0, 0x0F, 0x60, 0x00, 0x0F, 0x0F,
+	0x80, 0x04, 0xF0, 0x0F, 0x80, 0x00, 0x0F, 0x0F, 0xE0, 0x00, 0x03, 0x00,
+	0x20, 0x07, 0x80, 0x00, 0x20, 0x06, 0x40, 0x00, 0x20, 0x05, 0x20, 0x00,
+	0x20, 0x04, 0x10, 0x00, 0xC0, 0x00, 0x01, 0x00, 0xC0, 0x01, 0x0E, 0x00
+};
+
+const uint16 TownsMidiOutputChannel::_effectEnvStepTable[] = {
+	0x0001, 0x0002, 0x0004, 0x0005, 0x0006, 0x0007,	0x0008, 0x0009,
+	0x000A, 0x000C, 0x000E, 0x0010,	0x0012, 0x0015, 0x0018, 0x001E,
+	0x0024, 0x0032,	0x0040, 0x0052, 0x0064, 0x0088, 0x00A0, 0x00C0,
+	0x00F0, 0x0114, 0x0154, 0x01CC, 0x0258, 0x035C,	0x04B0, 0x0640
+};
+
+const uint8 TownsMidiOutputChannel::_freqMSB[] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+	0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+	0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+	0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+	0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+	0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+	0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x80, 0x81, 0x83, 0x85,
+	0x87, 0x88, 0x8A, 0x8C, 0x8E, 0x8F, 0x91, 0x93, 0x95, 0x96, 0x98, 0x9A,
+	0x9C, 0x9E, 0x9F, 0xA1, 0xA3, 0xA5, 0xA6, 0xA8, 0xAA, 0xAC, 0xAD, 0xAF,
+	0xB1, 0xB3, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBD, 0xBF, 0xC1, 0xC3, 0xC4,
+	0xC6, 0xC8, 0xCA, 0xCB, 0xCD, 0xCF, 0xD1, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA,
+	0xDB, 0xDD, 0xDF, 0xE1, 0xE2, 0xE4, 0xE6, 0xE8, 0xE9, 0xEB, 0xED, 0xEF
+};
+
+const uint16 TownsMidiOutputChannel::_freqLSB[] = {
+	0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6,
+	0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x0301, 0x032F,
+	0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
+	0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
+	0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
+	0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
+	0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
+	0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
+	0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
+	0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
+	0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
+	0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
+	0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
+	0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
+	0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
+	0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B
+};
+
+TownsMidiInputChannel::TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex) : MidiChannel(), _driver(driver), _out(0), _chanIndex(chanIndex),
+	_priority(0), _tl(0), _transpose(0), _pitchBendFactor(0), _pitchBend(0), _sustain(0), _freqLSB(0), _detune(0), _modWheel(0), _allocated(false) {
+	_instrument = new uint8[30];
+	memset(_instrument, 0, 30);
+}
+
+TownsMidiInputChannel::~TownsMidiInputChannel() {
+	delete[] _instrument;
+}
+
+bool TownsMidiInputChannel::allocate() {
+	if (_allocated)
+		return false;
+	_allocated = true;
+	return true;
+}
+
+void TownsMidiInputChannel::release() {
+	_allocated = false;
+}
+
+void TownsMidiInputChannel::send(uint32 b) {
+	_driver->send(b | _chanIndex);
+}
+
+void TownsMidiInputChannel::noteOff(byte note) {
+	if (!_out)
+		return;
+
+	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
+		if (oc->_note != note)
+			continue;
+
+		if (_sustain)
+			oc->_sustainNoteOff = 1;
+		else
+			oc->disconnect();
+	}
+}
+
+void TownsMidiInputChannel::noteOn(byte note, byte velocity) {
+	TownsMidiOutputChannel *oc = _driver->allocateOutputChannel(_priority);
+
+	if (!oc)
+		return;
+
+	oc->connect(this);
+
+	oc->_adjustModTl = _instrument[10] & 1;
+	oc->_note = note;
+	oc->_sustainNoteOff = 0;
+	oc->_duration = _instrument[29] * 63;
+
+	oc->_operator1Tl = (_instrument[1] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[4] >> 2)];
+	if (oc->_operator1Tl > 63)
+		oc->_operator1Tl = 63;
+
+	oc->_operator2Tl = (_instrument[6] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[9] >> 2)];
+	if (oc->_operator2Tl > 63)
+		oc->_operator2Tl = 63;
+
+	oc->setupProgram(_instrument, oc->_adjustModTl == 1 ? _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator1Tl << 5)]] : oc->_operator1Tl, _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator2Tl << 5)]]);
+	oc->noteOn(note + _transpose, _freqLSB);
+
+	if (_instrument[11] & 0x80)
+		oc->setupEffects(0, _instrument[11], &_instrument[12]);
+	else
+		oc->_effectEnvelopes[0].state = kEnvReady;
+
+	if (_instrument[20] & 0x80)
+		oc->setupEffects(1, _instrument[20], &_instrument[21]);
+	else
+		oc->_effectEnvelopes[1].state = kEnvReady;
+}
+
+void TownsMidiInputChannel::programChange(byte program) {
+	// Not implemented (The loading and assignment of programs
+	// is handled externally by the SCUMM engine. The programs
+	// get sent via sysEx_customInstrument.)
+}
+
+void TownsMidiInputChannel::pitchBend(int16 bend) {
+	_pitchBend = bend;
+	_freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
+	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
+		oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
+}
+
+void TownsMidiInputChannel::controlChange(byte control, byte value) {
+	switch (control) {
+	case 1:
+		controlModulationWheel(value);
+		break;
+	case 7:
+		controlVolume(value);
+		break;
+	case 10:
+		controlPanPos(value);
+		break;
+	case 64:
+		controlSustain(value);
+		break;
+	case 123:
+		while (_out)
+			_out->disconnect();
+		break;
+	default:
+		break;
+	}
+}
+
+void TownsMidiInputChannel::pitchBendFactor(byte value) {
+	_pitchBendFactor = value;
+	_freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
+	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
+		oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
+}
+
+void TownsMidiInputChannel::priority(byte value) {
+	_priority = value;
+}
+
+void TownsMidiInputChannel::sysEx_customInstrument(uint32 type, const byte *instr) {
+	memcpy(_instrument, instr, 30);
+}
+
+void TownsMidiInputChannel::controlModulationWheel(byte value) {
+	_modWheel = value;
+	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
+		oc->setModWheel(value);
+}
+
+void TownsMidiInputChannel::controlVolume(byte value) {
+	/* This is all done inside the imuse code
+	uint16 v1 = _ctrlVolume + 1;
+	uint16 v2 = value;
+	if (_chanIndex != 16) {
+		_ctrlVolume = value;
+		v2 = _player->getEffectiveVolume();
+	}
+	_tl = (v1 * v2) >> 7;*/
+
+	_tl = value;
+}
+
+void TownsMidiInputChannel::controlPanPos(byte value) {
+	// not implemented
+}
+
+void TownsMidiInputChannel::controlSustain(byte value) {
+	_sustain = value;
+	if (!value)
+		releasePedal();
+}
+
+void TownsMidiInputChannel::releasePedal() {
+	for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
+		if (oc->_sustainNoteOff)
+			oc->disconnect();
+	}
+}
+
+const uint8 TownsMidiInputChannel::_programAdjustLevel[] = {
+	0x00, 0x04, 0x07, 0x0B, 0x0D, 0x10, 0x12, 0x14,
+	0x16, 0x18, 0x1A, 0x1B, 0x1D, 0x1E, 0x1F, 0x21,
+	0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+	0x2A, 0x2B, 0x2C, 0x2C, 0x2D, 0x2E, 0x2F, 0x2F,
+	0x30, 0x31, 0x31, 0x32, 0x33, 0x33, 0x34, 0x35,
+	0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x38, 0x39,
+	0x39, 0x3A, 0x3A, 0x3B, 0x3B, 0x3C, 0x3C, 0x3C,
+	0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F
+};
+
+MidiDriver_TOWNS::MidiDriver_TOWNS(Audio::Mixer *mixer) : _timerProc(0), _timerProcPara(0), _channels(0), _out(0),
+	_baseTempo(10080), _chanState(0), _operatorLevelTable(0), _tickCounter(0), _rand(1), _allocCurPos(0), _isOpen(false) {
+	// We set exteral mutex handling to true to avoid lockups in SCUMM which has its own mutex.
+	_intf = new TownsAudioInterface(mixer, this, true);
+
+	_channels = new TownsMidiInputChannel*[32];
+	for (int i = 0; i < 32; i++)
+		_channels[i] = new TownsMidiInputChannel(this, i > 8 ? (i + 1) : i);
+
+	_out = new TownsMidiOutputChannel*[6];
+	for (int i = 0; i < 6; i++)
+		_out[i] = new TownsMidiOutputChannel(this, i);
+
+	_chanState = new TownsMidiChanState[32];
+
+	_operatorLevelTable = new uint8[2048];
+	for (int i = 0; i < 64; i++) {
+		for (int ii = 0; ii < 32; ii++)
+			_operatorLevelTable[(i << 5) + ii] = ((i * (ii + 1)) >> 5) & 0xff;
+	}
+	for (int i = 0; i < 64; i++)
+		_operatorLevelTable[i << 5] = 0;
+}
+
+MidiDriver_TOWNS::~MidiDriver_TOWNS() {
+	close();
+	delete _intf;
+
+	if (_channels) {
+		for (int i = 0; i < 32; i++)
+			delete _channels[i];
+		delete[] _channels;
+	}
+	_channels = 0;
+
+	if (_out) {
+		for (int i = 0; i < 6; i++)
+			delete _out[i];
+		delete[] _out;
+	}
+	_out = 0;
+
+	delete[] _chanState;
+	_chanState = 0;
+	delete[] _operatorLevelTable;
+	_operatorLevelTable = 0;
+}
+
+int MidiDriver_TOWNS::open() {
+	if (_isOpen)
+		return MERR_ALREADY_OPEN;
+
+	if (!_intf->init())
+		return MERR_CANNOT_CONNECT;
+
+	_intf->callback(0);
+
+	_intf->callback(21, 255, 1);
+	_intf->callback(21, 0, 1);
+	_intf->callback(22, 255, 221);
+
+	_intf->callback(33, 8);
+	_intf->setSoundEffectChanMask(~0x3f);
+
+	_allocCurPos = 0;
+
+	_isOpen = true;
+
+	return 0;
+}
+
+void MidiDriver_TOWNS::close() {
+	if (!_isOpen)
+		return;
+
+	_isOpen = false;
+
+	setTimerCallback(0, 0);
+	g_system->delayMillis(20);
+}
+
+void MidiDriver_TOWNS::send(uint32 b) {
+	if (!_isOpen)
+		return;
+
+	byte param2 = (b >> 16) & 0xFF;
+	byte param1 = (b >> 8) & 0xFF;
+	byte cmd = b & 0xF0;
+
+	TownsMidiInputChannel *c = _channels[b & 0x0F];
+
+	switch (cmd) {
+	case 0x80:
+		c->noteOff(param1);
+		break;
+	case 0x90:
+		if (param2)
+			c->noteOn(param1, param2);
+		else
+			c->noteOff(param1);
+		break;
+	case 0xB0:
+		c->controlChange(param1, param2);
+		break;
+	case 0xC0:
+		c->programChange(param1);
+		break;
+	case 0xE0:
+		c->pitchBend((param1 | (param2 << 7)) - 0x2000);
+		break;
+	case 0xF0:
+		warning("MidiDriver_TOWNS: Receiving SysEx command on a send() call");
+		break;
+
+	default:
+		break;
+	}
+}
+
+void MidiDriver_TOWNS::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+	_timerProc = timer_proc;
+	_timerProcPara = timer_param;
+}
+
+uint32 MidiDriver_TOWNS::getBaseTempo() {
+	return _baseTempo;
+}
+
+MidiChannel *MidiDriver_TOWNS::allocateChannel() {
+	if (!_isOpen)
+		return 0;
+
+	for (int i = 0; i < 32; ++i) {
+		TownsMidiInputChannel *chan = _channels[i];
+		if (chan->allocate())
+			return chan;
+	}
+
+	return 0;
+}
+
+MidiChannel *MidiDriver_TOWNS::getPercussionChannel() {
+	return 0;
+}
+
+void MidiDriver_TOWNS::timerCallback(int timerId) {
+	if (!_isOpen)
+		return;
+
+	switch (timerId) {
+	case 1:
+		updateParser();
+		updateOutputChannels();
+		break;
+	default:
+		break;
+	}
+}
+
+void MidiDriver_TOWNS::updateParser() {
+	if (_timerProc)
+		_timerProc(_timerProcPara);
+}
+
+void MidiDriver_TOWNS::updateOutputChannels() {
+	_tickCounter += _baseTempo;
+	while (_tickCounter >= 16667) {
+		_tickCounter -= 16667;
+		for (int i = 0; i < 6; i++) {
+			if (_out[i]->update())
+				return;
+		}
+	}
+}
+
+TownsMidiOutputChannel *MidiDriver_TOWNS::allocateOutputChannel(uint8 pri) {
+	TownsMidiOutputChannel *res = 0;
+
+	for (int i = 0; i < 6; i++) {
+		if (++_allocCurPos == 6)
+			_allocCurPos = 0;
+
+		int s = _out[_allocCurPos]->checkPriority(pri);
+		if (s == TownsMidiOutputChannel::kDisconnected)
+			return _out[_allocCurPos];
+
+		if (s != TownsMidiOutputChannel::kHighPriority) {
+			pri = s;
+			res = _out[_allocCurPos];
+		}
+	}
+
+	if (res)
+		res->disconnect();
+
+	return res;
+}
+
+int MidiDriver_TOWNS::randomValue(int para) {
+	_rand = (_rand & 1) ? (_rand >> 1) ^ 0xb8 : (_rand >> 1);
+	return (_rand * para) >> 8;
+}
diff --git a/engines/scumm/imuse/drivers/fmtowns.h b/engines/scumm/imuse/drivers/fmtowns.h
new file mode 100644
index 0000000..5413e37
--- /dev/null
+++ b/engines/scumm/imuse/drivers/fmtowns.h
@@ -0,0 +1,83 @@
+/* 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 IMUSE_DRV_FMTOWNS_H
+#define IMUSE_DRV_FMTOWNS_H
+
+#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
+#include "audio/mididrv.h"
+
+
+class TownsMidiOutputChannel;
+class TownsMidiInputChannel;
+class TownsMidiChanState;
+
+class MidiDriver_TOWNS : public MidiDriver, public TownsAudioInterfacePluginDriver {
+friend class TownsMidiInputChannel;
+friend class TownsMidiOutputChannel;
+public:
+	MidiDriver_TOWNS(Audio::Mixer *mixer);
+	~MidiDriver_TOWNS();
+
+	int open();
+	bool isOpen() const { return _isOpen; }
+	void close();
+
+	void send(uint32 b);
+
+	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
+
+	uint32 getBaseTempo();
+	MidiChannel *allocateChannel();
+	MidiChannel *getPercussionChannel();
+
+	void timerCallback(int timerId);
+
+private:
+	void updateParser();
+	void updateOutputChannels();
+
+	TownsMidiOutputChannel *allocateOutputChannel(uint8 pri);
+
+	int randomValue(int para);
+
+	TownsMidiInputChannel **_channels;
+	TownsMidiOutputChannel **_out;
+	TownsMidiChanState *_chanState;
+
+	Common::TimerManager::TimerProc _timerProc;
+	void *_timerProcPara;
+
+	TownsAudioInterface *_intf;
+
+	uint32 _tickCounter;
+	uint8 _allocCurPos;
+	uint8 _rand;
+
+	bool _isOpen;
+
+	uint8 *_operatorLevelTable;
+
+	const uint16 _baseTempo;
+};
+
+#endif
diff --git a/engines/scumm/imuse/drivers/mac_m68k.cpp b/engines/scumm/imuse/drivers/mac_m68k.cpp
new file mode 100644
index 0000000..1bcef44
--- /dev/null
+++ b/engines/scumm/imuse/drivers/mac_m68k.cpp
@@ -0,0 +1,515 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/imuse/drivers/mac_m68k.h"
+
+#include "common/util.h"
+#include "common/macresman.h"
+#include "common/stream.h"
+
+namespace Scumm {
+
+MacM68kDriver::MacM68kDriver(Audio::Mixer *mixer)
+	: MidiDriver_Emulated(mixer) {
+}
+
+MacM68kDriver::~MacM68kDriver() {
+}
+
+int MacM68kDriver::open() {
+	if (_isOpen) {
+		return MERR_ALREADY_OPEN;
+	}
+
+	const int error = MidiDriver_Emulated::open();
+	if (error) {
+		return error;
+	}
+
+	for (uint i = 0; i < ARRAYSIZE(_channels); ++i) {
+		_channels[i].init(this, i);
+	}
+
+	memset(_voiceChannels, 0, sizeof(_voiceChannels));
+	_lastUsedVoiceChannel = 0;
+
+	loadAllInstruments();
+
+	_pitchTable[116] = 1664510;
+	_pitchTable[117] = 1763487;
+	_pitchTable[118] = 1868350;
+	_pitchTable[119] = 1979447;
+	_pitchTable[120] = 2097152;
+	_pitchTable[121] = 2221855;
+	_pitchTable[122] = 2353973;
+	_pitchTable[123] = 2493948;
+	_pitchTable[124] = 2642246;
+	_pitchTable[125] = 2799362;
+	_pitchTable[126] = 2965820;
+	_pitchTable[127] = 3142177;
+	for (int i = 115; i >= 0; --i) {
+		_pitchTable[i] = _pitchTable[i + 12] / 2;
+	}
+
+	_volumeTable = new byte[8192];
+	for (int i = 0; i < 32; ++i) {
+		for (int j = 0; j < 256; ++j) {
+			_volumeTable[i * 256 + j] = ((-128 + j) * _volumeBaseTable[i]) / 127 - 128;
+		}
+	}
+
+	_mixBuffer = 0;
+	_mixBufferLength = 0;
+
+	// We set the output sound type to music here to allow sound volume
+	// adjustment. The drawback here is that we can not control the music and
+	// sfx separately here. But the AdLib output has the same issue so it
+	// should not be that bad.
+	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+	return 0;
+}
+
+void MacM68kDriver::close() {
+	if (!_isOpen) {
+		return;
+	}
+
+	_mixer->stopHandle(_mixerSoundHandle);
+	_isOpen = false;
+	for (InstrumentMap::iterator i = _instruments.begin(); i != _instruments.end(); ++i) {
+		delete[] i->_value.data;
+	}
+	_instruments.clear();
+	delete[] _volumeTable;
+	_volumeTable = 0;
+	delete[] _mixBuffer;
+	_mixBuffer = 0;
+	_mixBufferLength = 0;
+}
+
+void MacM68kDriver::send(uint32 d) {
+	assert(false);
+}
+
+void MacM68kDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) {
+	assert(false);
+}
+
+MidiChannel *MacM68kDriver::allocateChannel() {
+	for (uint i = 0; i < ARRAYSIZE(_channels); ++i) {
+		if (_channels[i].allocate()) {
+			return &_channels[i];
+		}
+	}
+
+	return 0;
+}
+
+MacM68kDriver::Instrument MacM68kDriver::getInstrument(int idx) const {
+	InstrumentMap::const_iterator i = _instruments.find(idx);
+	if (i != _instruments.end()) {
+		return i->_value;
+	} else {
+		return _defaultInstrument;
+	}
+}
+
+void MacM68kDriver::generateSamples(int16 *buf, int len) {
+	int silentChannels = 0;
+
+	if (_mixBufferLength < len) {
+		delete[] _mixBuffer;
+
+		_mixBufferLength = len;
+		_mixBuffer = new int[_mixBufferLength];
+		assert(_mixBuffer);
+	}
+	memset(_mixBuffer, 0, sizeof(int) * _mixBufferLength);
+
+	for (int i = 0; i < kChannelCount; ++i) {
+		OutputChannel &out = _voiceChannels[i].out;
+		if (out.isFinished) {
+			++silentChannels;
+			continue;
+		}
+
+		byte *volumeTable = &_volumeTable[(out.volume / 4) * 256];
+		int *buffer = _mixBuffer;
+
+		int samplesLeft = len;
+		while (samplesLeft) {
+			out.subPos += out.pitchModifier;
+			while (out.subPos >= 0x10000) {
+				out.subPos -= 0x10000;
+				out.instrument++;
+			}
+
+			if (out.instrument >= out.end) {
+				if (!out.start) {
+					break;
+				}
+
+				out.instrument = out.start;
+				out.subPos = 0;
+			}
+
+			*buffer++ += volumeTable[*out.instrument];
+			--samplesLeft;
+		}
+
+		if (samplesLeft) {
+			out.isFinished = true;
+			while (samplesLeft--) {
+				*buffer++ += 0x80;
+			}
+		}
+	}
+
+	const int *buffer = _mixBuffer;
+	const int silenceAdd = silentChannels << 7;
+	while (len--) {
+		*buf++ = (((*buffer++ + silenceAdd) >> 3) << 8) ^ 0x8000;
+	}
+}
+
+void MacM68kDriver::loadAllInstruments() {
+	Common::MacResManager resource;
+	if (resource.open("iMUSE Setups")) {
+		if (!resource.hasResFork()) {
+			error("MacM68kDriver::loadAllInstruments: \"iMUSE Setups\" loaded, but no resource fork present");
+		}
+
+		for (int i = 0x3E7; i < 0x468; ++i) {
+			Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i);
+			if (stream) {
+				addInstrument(i, stream);
+				delete stream;
+			}
+		}
+
+		for (int i = 0x7D0; i < 0x8D0; ++i) {
+			Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i);
+			if (stream) {
+				addInstrument(i, stream);
+				delete stream;
+			}
+		}
+
+		InstrumentMap::iterator inst = _instruments.find(kDefaultInstrument);
+		if (inst != _instruments.end()) {
+			_defaultInstrument = inst->_value;
+		} else {
+			error("MacM68kDriver::loadAllInstruments: Could not load default instrument");
+		}
+	} else {
+		error("MacM68kDriver::loadAllInstruments: Could not load \"iMUSE Setups\"");
+	}
+}
+
+void MacM68kDriver::addInstrument(int idx, Common::SeekableReadStream *data) {
+	// We parse the "SND" files manually here, since we need special data
+	// from their header and need to work on them raw while mixing.
+	data->skip(2);
+	int count = data->readUint16BE();
+	data->skip(2 * (3 * count));
+	count = data->readUint16BE();
+	data->skip(2 * (4 * count));
+
+	Instrument inst;
+	// Skip (optional) pointer to data
+	data->skip(4);
+	inst.length        = data->readUint32BE();
+	inst.sampleRate    = data->readUint32BE();
+	inst.loopStart     = data->readUint32BE();
+	inst.loopEnd       = data->readUint32BE();
+	// Skip encoding
+	data->skip(1);
+	inst.baseFrequency = data->readByte();
+
+	inst.data = new byte[inst.length];
+	assert(inst.data);
+	data->read(inst.data, inst.length);
+	_instruments[idx] = inst;
+}
+
+void MacM68kDriver::setPitch(OutputChannel *out, int frequency) {
+	out->frequency = frequency;
+	out->isFinished = false;
+
+	const int pitchIdx = (frequency >> 7) + 60 - out->baseFrequency;
+	assert(pitchIdx >= 0);
+
+	const int low7Bits = frequency & 0x7F;
+	if (low7Bits) {
+		out->pitchModifier = _pitchTable[pitchIdx] + (((_pitchTable[pitchIdx + 1] - _pitchTable[pitchIdx]) * low7Bits) >> 7);
+	} else {
+		out->pitchModifier = _pitchTable[pitchIdx];
+	}
+}
+
+void MacM68kDriver::VoiceChannel::off() {
+	if (out.start) {
+		out.isFinished = true;
+	}
+
+	part->removeVoice(this);
+	part = 0;
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::release() {
+	_allocated = false;
+	while (_voice) {
+		_voice->off();
+	}
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::send(uint32 b) {
+	uint8 type = b & 0xF0;
+	uint8 p1 = (b >> 8) & 0xFF;
+	uint8 p2 = (b >> 16) & 0xFF;
+
+	switch (type) {
+	case 0x80:
+		noteOff(p1);
+		break;
+
+	case 0x90:
+		if (p2) {
+			noteOn(p1, p2);
+		} else {
+			noteOff(p1);
+		}
+		break;
+
+	case 0xB0:
+		controlChange(p1, p2);
+		break;
+
+	case 0xE0:
+		pitchBend((p1 | (p2 << 7)) - 0x2000);
+		break;
+
+	default:
+		break;
+	}
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::noteOff(byte note) {
+	for (VoiceChannel *i = _voice; i; i = i->next) {
+		if (i->note == note) {
+			if (_sustain) {
+				i->sustainNoteOff = true;
+			} else {
+				i->off();
+			}
+		}
+	}
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::noteOn(byte note, byte velocity) {
+	// Do not start a not unless there is an instrument set up
+	if (!_instrument.data) {
+		return;
+	}
+
+	// Allocate a voice channel
+	VoiceChannel *voice = _owner->allocateVoice(_priority);
+	if (!voice) {
+		return;
+	}
+	addVoice(voice);
+
+	voice->note = note;
+	// This completly ignores the note's volume, but is in accordance
+	// to the original.
+	voice->out.volume = _volume;
+
+	// Set up the instrument data
+	voice->out.baseFrequency = _instrument.baseFrequency;
+	voice->out.soundStart    = _instrument.data;
+	voice->out.soundEnd      = _instrument.data + _instrument.length;
+	if (_instrument.loopEnd && _instrument.loopEnd - 12 > _instrument.loopStart) {
+		voice->out.loopStart = _instrument.data + _instrument.loopStart;
+		voice->out.loopEnd   = _instrument.data + _instrument.loopEnd;
+	} else {
+		voice->out.loopStart = 0;
+		voice->out.loopEnd   = voice->out.soundEnd;
+	}
+
+	voice->out.start = voice->out.loopStart;
+	voice->out.end   = voice->out.loopEnd;
+
+	// Set up the pitch
+	_owner->setPitch(&voice->out, (note << 7) + _pitchBend);
+
+	// Set up the sample position
+	voice->out.instrument = voice->out.soundStart;
+	voice->out.subPos = 0;
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::programChange(byte program) {
+	_instrument = _owner->getInstrument(program + kProgramChangeBase);
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::pitchBend(int16 bend) {
+	_pitchBend = (bend * _pitchBendFactor) >> 6;
+	for (VoiceChannel *i = _voice; i; i = i->next) {
+		_owner->setPitch(&i->out, (i->note << 7) + _pitchBend);
+	}
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::controlChange(byte control, byte value) {
+	switch (control) {
+	// volume change
+	case 7:
+		_volume = value;
+		for (VoiceChannel *i = _voice; i; i = i->next) {
+			i->out.volume = value;
+			i->out.isFinished = false;
+		}
+		break;
+
+	// sustain
+	case 64:
+		_sustain = value;
+		if (!_sustain) {
+			for (VoiceChannel *i = _voice; i; i = i->next) {
+				if (i->sustainNoteOff) {
+					i->off();
+				}
+			}
+		}
+		break;
+
+	// all notes off
+	case 123:
+		for (VoiceChannel *i = _voice; i; i = i->next) {
+			i->off();
+		}
+		break;
+
+	default:
+		break;
+	}
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::pitchBendFactor(byte value) {
+	_pitchBendFactor = value;
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::priority(byte value) {
+	_priority = value;
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::sysEx_customInstrument(uint32 type, const byte *instr) {
+	assert(instr);
+	if (type == 'MAC ') {
+		_instrument = _owner->getInstrument(*instr + kSysExBase);
+	}
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::init(MacM68kDriver *owner, byte channel) {
+	_owner = owner;
+	_number = channel;
+	_allocated = false;
+}
+
+bool MacM68kDriver::MidiChannel_MacM68k::allocate() {
+	if (_allocated) {
+		return false;
+	}
+
+	_allocated = true;
+	_voice = 0;
+	_priority = 0;
+	memset(&_instrument, 0, sizeof(_instrument));
+	_pitchBend = 0;
+	_pitchBendFactor = 0;
+	_volume = 0;
+	return true;
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::addVoice(VoiceChannel *voice) {
+	voice->next = _voice;
+	voice->prev = 0;
+	voice->part = this;
+	if (_voice) {
+		_voice->prev = voice;
+	}
+	_voice = voice;
+}
+
+void MacM68kDriver::MidiChannel_MacM68k::removeVoice(VoiceChannel *voice) {
+	VoiceChannel *i = _voice;
+	while (i && i != voice) {
+		i = i->next;
+	}
+
+	if (i) {
+		if (i->next) {
+			i->next->prev = i->prev;
+		}
+
+		if (i->prev) {
+			i->prev->next = i->next;
+		} else {
+			_voice = i->next;
+		}
+	}
+}
+
+MacM68kDriver::VoiceChannel *MacM68kDriver::allocateVoice(int priority) {
+	VoiceChannel *channel = 0;
+	for (int i = 0; i < kChannelCount; ++i) {
+		if (++_lastUsedVoiceChannel == kChannelCount) {
+			_lastUsedVoiceChannel = 0;
+		}
+
+		VoiceChannel *cur = &_voiceChannels[_lastUsedVoiceChannel];
+		if (!cur->part) {
+			memset(cur, 0, sizeof(*cur));
+			return cur;
+		} else if (!cur->next) {
+			if (cur->part->_priority <= priority) {
+				priority = cur->part->_priority;
+				channel = cur;
+			}
+		}
+	}
+
+	if (channel) {
+		channel->off();
+		memset(channel, 0, sizeof(*channel));
+	}
+
+	return channel;
+}
+
+const int MacM68kDriver::_volumeBaseTable[32] = {
+	  0,   0,   1,   1,   2,   3,   5,   6,
+	  8,  11,  13,  16,  19,  22,  26,  30,
+	 34,  38,  43,  48,  53,  58,  64,  70,
+	 76,  83,  89,  96, 104, 111, 119, 127
+};
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse/drivers/mac_m68k.h b/engines/scumm/imuse/drivers/mac_m68k.h
new file mode 100644
index 0000000..31beaf4
--- /dev/null
+++ b/engines/scumm/imuse/drivers/mac_m68k.h
@@ -0,0 +1,178 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_IMUSE_MAC_M68K_H
+#define SCUMM_IMUSE_MAC_M68K_H
+
+#include "audio/softsynth/emumidi.h"
+
+#include "common/hashmap.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Scumm {
+
+class MacM68kDriver : public MidiDriver_Emulated {
+	friend class MidiChannel_MacM68k;
+public:
+	MacM68kDriver(Audio::Mixer *mixer);
+	~MacM68kDriver();
+
+	virtual int open();
+	virtual void close();
+
+	virtual void send(uint32 d);
+	virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr);
+
+	virtual MidiChannel *allocateChannel();
+	virtual MidiChannel *getPercussionChannel() { return 0; }
+
+	virtual bool isStereo() const { return false; }
+	virtual int getRate() const {
+		// The original is using a frequency of approx. 22254.54546 here.
+		// To be precise it uses the 16.16 fixed point value 0x56EE8BA3.
+		return 22254;
+	}
+
+protected:
+	virtual void generateSamples(int16 *buf, int len);
+	virtual void onTimer() {}
+
+private:
+	int *_mixBuffer;
+	int _mixBufferLength;
+
+	struct Instrument {
+		uint length;
+		uint sampleRate;
+		uint loopStart;
+		uint loopEnd;
+		int baseFrequency;
+
+		byte *data;
+	};
+
+	enum {
+		kDefaultInstrument = 0x3E7,
+		kProgramChangeBase = 0x3E8,
+		kSysExBase         = 0x7D0
+	};
+
+	Instrument getInstrument(int idx) const;
+	typedef Common::HashMap<int, Instrument> InstrumentMap;
+	InstrumentMap _instruments;
+	Instrument _defaultInstrument;
+	void loadAllInstruments();
+	void addInstrument(int idx, Common::SeekableReadStream *data);
+
+	struct OutputChannel {
+		int pitchModifier;
+
+		const byte *instrument;
+		uint subPos;
+
+		const byte *start;
+		const byte *end;
+
+		const byte *soundStart;
+		const byte *soundEnd;
+		const byte *loopStart;
+		const byte *loopEnd;
+
+		int frequency;
+		int volume;
+
+		bool isFinished;
+
+		int baseFrequency;
+	};
+
+	void setPitch(OutputChannel *out, int frequency);
+	int _pitchTable[128];
+
+	byte *_volumeTable;
+	static const int _volumeBaseTable[32];
+
+	class MidiChannel_MacM68k;
+
+	struct VoiceChannel {
+		MidiChannel_MacM68k *part;
+		VoiceChannel *prev, *next;
+		int channel;
+		int note;
+		bool sustainNoteOff;
+		OutputChannel out;
+
+		void off();
+	};
+
+	class MidiChannel_MacM68k : public MidiChannel {
+		friend class MacM68kDriver;
+	public:
+		virtual MidiDriver *device() { return _owner; }
+		virtual byte getNumber() { return _number; }
+		virtual void release();
+
+		virtual void send(uint32 b);
+		virtual void noteOff(byte note);
+		virtual void noteOn(byte note, byte velocity);
+		virtual void programChange(byte program);
+		virtual void pitchBend(int16 bend);
+		virtual void controlChange(byte control, byte value);
+		virtual void pitchBendFactor(byte value);
+		virtual void priority(byte value);
+		virtual void sysEx_customInstrument(uint32 type, const byte *instr);
+
+		void init(MacM68kDriver *owner, byte channel);
+		bool allocate();
+
+		void addVoice(VoiceChannel *voice);
+		void removeVoice(VoiceChannel *voice);
+	private:
+		MacM68kDriver *_owner;
+		bool _allocated;
+		int _number;
+
+		VoiceChannel *_voice;
+		int _priority;
+		int _sustain;
+		Instrument _instrument;
+		int _pitchBend;
+		int _pitchBendFactor;
+		int _volume;
+	};
+
+	MidiChannel_MacM68k _channels[32];
+
+	enum {
+		kChannelCount = 8
+	};
+	VoiceChannel _voiceChannels[kChannelCount];
+	int _lastUsedVoiceChannel;
+	VoiceChannel *allocateVoice(int priority);
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/imuse/drivers/pcspk.cpp b/engines/scumm/imuse/drivers/pcspk.cpp
new file mode 100644
index 0000000..0e516c2
--- /dev/null
+++ b/engines/scumm/imuse/drivers/pcspk.cpp
@@ -0,0 +1,835 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/imuse/drivers/pcspk.h"
+
+#include "common/util.h"
+
+namespace Scumm {
+
+PcSpkDriver::PcSpkDriver(Audio::Mixer *mixer)
+	: MidiDriver_Emulated(mixer), _pcSpk(mixer->getOutputRate()) {
+}
+
+PcSpkDriver::~PcSpkDriver() {
+	close();
+}
+
+int PcSpkDriver::open() {
+	if (_isOpen)
+		return MERR_ALREADY_OPEN;
+
+	MidiDriver_Emulated::open();
+
+	for (uint i = 0; i < 6; ++i)
+		_channels[i].init(this, i);
+	_activeChannel = 0;
+	_effectTimer = 0;
+	_randBase = 1;
+
+	// We need to take care we only send note frequencies, when the internal
+	// settings actually changed, thus we need some extra state to keep track
+	// of that.
+	_lastActiveChannel = 0;
+	_lastActiveOut = 0;
+
+	// We set the output sound type to music here to allow sound volume
+	// adjustment. The drawback here is that we can not control the music and
+	// sfx separately here. But the AdLib output has the same issue so it
+	// should not be that bad.
+	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+	return 0;
+}
+
+void PcSpkDriver::close() {
+	if (!_isOpen)
+		return;
+	_isOpen = false;
+
+	_mixer->stopHandle(_mixerSoundHandle);
+}
+
+void PcSpkDriver::send(uint32 d) {
+	assert((d & 0x0F) < 6);
+	_channels[(d & 0x0F)].send(d);
+}
+
+void PcSpkDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) {
+	assert(channel < 6);
+	if (type == 'SPK ')
+		_channels[channel].sysEx_customInstrument(type, instr);
+}
+
+MidiChannel *PcSpkDriver::allocateChannel() {
+	for (uint i = 0; i < 6; ++i) {
+		if (_channels[i].allocate())
+			return &_channels[i];
+	}
+
+	return 0;
+}
+
+void PcSpkDriver::generateSamples(int16 *buf, int len) {
+	_pcSpk.readBuffer(buf, len);
+}
+
+void PcSpkDriver::onTimer() {
+	if (!_activeChannel)
+		return;
+
+	for (uint i = 0; i < 6; ++i) {
+		OutputChannel &out = _channels[i]._out;
+
+		if (!out.active)
+			continue;
+
+		if (out.length == 0 || --out.length != 0) {
+			if (out.unkB && out.unkC) {
+				out.unkA += out.unkB;
+				if (out.instrument)
+					out.unkE = ((int8)out.instrument[out.unkA] * out.unkC) >> 4;
+			}
+
+			++_effectTimer;
+			if (_effectTimer > 3) {
+				_effectTimer = 0;
+
+				if (out.effectEnvelopeA.state)
+					updateEffectGenerator(_channels[i], out.effectEnvelopeA, out.effectDefA);
+				if (out.effectEnvelopeB.state)
+					updateEffectGenerator(_channels[i], out.effectEnvelopeB, out.effectDefB);
+			}
+		} else {
+			out.active = 0;
+			updateNote();
+			return;
+		}
+	}
+
+	if (_activeChannel->_tl) {
+		output((_activeChannel->_out.note << 7) + _activeChannel->_pitchBend + _activeChannel->_out.unk60 + _activeChannel->_out.unkE);
+	} else {
+		_pcSpk.stop();
+		_lastActiveChannel = 0;
+		_lastActiveOut = 0;
+	}
+}
+
+void PcSpkDriver::updateNote() {
+	uint8 priority = 0;
+	_activeChannel = 0;
+	for (uint i = 0; i < 6; ++i) {
+		if (_channels[i]._allocated && _channels[i]._out.active && _channels[i]._priority >= priority) {
+			priority = _channels[i]._priority;
+			_activeChannel = &_channels[i];
+		}
+	}
+
+	if (_activeChannel == 0 || _activeChannel->_tl == 0) {
+		_pcSpk.stop();
+		_lastActiveChannel = 0;
+		_lastActiveOut = 0;
+	} else {
+		output(_activeChannel->_pitchBend + (_activeChannel->_out.note << 7));
+	}
+}
+
+void PcSpkDriver::output(uint16 out) {
+	byte v1 = (out >> 7) & 0xFF;
+	byte v2 = (out >> 2) & 0x1E;
+
+	byte shift = _outputTable1[v1];
+	uint16 indexBase = _outputTable2[v1] << 5;
+	uint16 frequency = _frequencyTable[(indexBase + v2) / 2] >> shift;
+
+	// Only output in case the active channel changed or the frequency changed.
+	// This is not faithful to the original. Since our timings differ we would
+	// get distorted sound otherwise though.
+	if (_lastActiveChannel != _activeChannel || _lastActiveOut != out) {
+		_pcSpk.play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / frequency, -1);
+		_lastActiveChannel = _activeChannel;
+		_lastActiveOut = out;
+	}
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::init(PcSpkDriver *owner, byte channel) {
+	_owner = owner;
+	_channel = channel;
+	_allocated = false;
+	memset(&_out, 0, sizeof(_out));
+}
+
+bool PcSpkDriver::MidiChannel_PcSpk::allocate() {
+	if (_allocated)
+		return false;
+
+	memset(&_out, 0, sizeof(_out));
+	memset(_instrument, 0, sizeof(_instrument));
+	_out.effectDefA.envelope = &_out.effectEnvelopeA;
+	_out.effectDefB.envelope = &_out.effectEnvelopeB;
+
+	_allocated = true;
+	return true;
+}
+
+MidiDriver *PcSpkDriver::MidiChannel_PcSpk::device() {
+	return _owner;
+}
+
+byte PcSpkDriver::MidiChannel_PcSpk::getNumber() {
+	return _channel;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::release() {
+	_out.active = 0;
+	_allocated = false;
+	_owner->updateNote();
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::send(uint32 b) {
+	uint8 type = b & 0xF0;
+	uint8 p1 = (b >> 8) & 0xFF;
+	uint8 p2 = (b >> 16) & 0xFF;
+
+	switch (type) {
+	case 0x80:
+		noteOff(p1);
+		break;
+
+	case 0x90:
+		if (p2)
+			noteOn(p1, p2);
+		else
+			noteOff(p1);
+		break;
+
+	case 0xB0:
+		controlChange(p1, p2);
+		break;
+
+	case 0xE0:
+		pitchBend((p1 | (p2 << 7)) - 0x2000);
+		break;
+
+	default:
+		break;
+	}
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::noteOff(byte note) {
+	if (!_allocated)
+		return;
+
+	if (_sustain) {
+		if (_out.note == note)
+			_out.sustainNoteOff = 1;
+	} else {
+		if (_out.note == note) {
+			_out.active = 0;
+			_owner->updateNote();
+		}
+	}
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::noteOn(byte note, byte velocity) {
+	if (!_allocated)
+		return;
+
+	_out.note = note;
+	_out.sustainNoteOff = 0;
+	_out.length = _instrument[0];
+
+	if (_instrument[4] * 256 < ARRAYSIZE(PcSpkDriver::_outInstrumentData))
+		_out.instrument = _owner->_outInstrumentData + _instrument[4] * 256;
+	else
+		_out.instrument = 0;
+
+	_out.unkA = 0;
+	_out.unkB = _instrument[1];
+	_out.unkC = _instrument[2];
+	_out.unkE = 0;
+	_out.unk60 = 0;
+	_out.active = 1;
+
+	// In case we get a note on event on the last active channel, we reset the
+	// last active channel, thus we assure the frequency is correctly set, even
+	// when the same note was sent.
+	if (_owner->_lastActiveChannel == this) {
+		_owner->_lastActiveChannel = 0;
+		_owner->_lastActiveOut = 0;
+	}
+	_owner->updateNote();
+
+	_out.unkC += PcSpkDriver::getEffectModifier(_instrument[3] + ((velocity & 0xFE) << 4));
+	if (_out.unkC > 63)
+		_out.unkC = 63;
+
+	if ((_instrument[5] & 0x80) != 0)
+		_owner->setupEffects(*this, _out.effectEnvelopeA, _out.effectDefA, _instrument[5], _instrument + 6);
+
+	if ((_instrument[14] & 0x80) != 0)
+		_owner->setupEffects(*this, _out.effectEnvelopeB, _out.effectDefB, _instrument[14], _instrument + 15);
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::programChange(byte program) {
+	// Nothing to implement here, the iMuse code takes care of passing us the
+	// instrument data.
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::pitchBend(int16 bend) {
+	_pitchBend = (bend * _pitchBendFactor) >> 6;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::controlChange(byte control, byte value) {
+	switch (control) {
+	case 1:
+		if (_out.effectEnvelopeA.state && _out.effectDefA.useModWheel)
+			_out.effectEnvelopeA.modWheelState = (value >> 2);
+		if (_out.effectEnvelopeB.state && _out.effectDefB.useModWheel)
+			_out.effectEnvelopeB.modWheelState = (value >> 2);
+		break;
+
+	case 7:
+		_tl = value;
+		if (_owner->_activeChannel == this) {
+			if (_tl == 0) {
+				_owner->_lastActiveChannel = 0;
+				_owner->_lastActiveOut = 0;
+				_owner->_pcSpk.stop();
+			} else {
+				_owner->output((_out.note << 7) + _pitchBend + _out.unk60 + _out.unkE);
+			}
+		}
+		break;
+
+	case 64:
+		_sustain = value;
+		if (!value && _out.sustainNoteOff) {
+			_out.active = 0;
+			_owner->updateNote();
+		}
+		break;
+
+	case 123:
+		_out.active = 0;
+		_owner->updateNote();
+		break;
+
+	default:
+		break;
+	}
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::pitchBendFactor(byte value) {
+	_pitchBendFactor = value;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::priority(byte value) {
+	_priority = value;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::sysEx_customInstrument(uint32 type, const byte *instr) {
+	memcpy(_instrument, instr, sizeof(_instrument));
+}
+
+uint8 PcSpkDriver::getEffectModifier(uint16 level) {
+	uint8 base = level / 32;
+	uint8 index = level % 32;
+
+	if (index == 0)
+		return 0;
+
+	return (base * (index + 1)) >> 5;
+}
+
+int16 PcSpkDriver::getEffectModLevel(int16 level, int8 mod) {
+	if (!mod) {
+		return 0;
+	} else if (mod == 31) {
+		return level;
+	} else if (level < -63 || level > 63) {
+		return (mod * (level + 1)) >> 6;
+	} else if (mod < 0) {
+		if (level < 0)
+			return getEffectModifier(((-level) << 5) - mod);
+		else
+			return -getEffectModifier((level << 5) - mod);
+	} else {
+		if (level < 0)
+			return -getEffectModifier(((-level) << 5) + mod);
+		else
+			return getEffectModifier(((-level) << 5) + mod);
+	}
+}
+
+int16 PcSpkDriver::getRandScale(int16 input) {
+	if (_randBase & 1)
+		_randBase = (_randBase >> 1) ^ 0xB8;
+	else
+		_randBase >>= 1;
+
+	return (_randBase * input) >> 8;
+}
+
+void PcSpkDriver::setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data) {
+	def.phase = 0;
+	def.useModWheel = flags & 0x40;
+	env.loop = flags & 0x20;
+	def.type = flags & 0x1F;
+
+	env.modWheelSensitivity = 31;
+	if (def.useModWheel)
+		env.modWheelState = chan._modWheel >> 2;
+	else
+		env.modWheelState = 31;
+
+	switch (def.type) {
+	case 0:
+		env.maxLevel = 767;
+		env.startLevel = 383;
+		break;
+
+	case 1:
+		env.maxLevel = 31;
+		env.startLevel = 15;
+		break;
+
+	case 2:
+		env.maxLevel = 63;
+		env.startLevel = chan._out.unkB;
+		break;
+
+	case 3:
+		env.maxLevel = 63;
+		env.startLevel = chan._out.unkC;
+		break;
+
+	case 4:
+		env.maxLevel = 3;
+		env.startLevel = chan._instrument[4];
+		break;
+
+	case 5:
+		env.maxLevel = 62;
+		env.startLevel = 31;
+		env.modWheelState = 0;
+		break;
+
+	case 6:
+		env.maxLevel = 31;
+		env.startLevel = 0;
+		env.modWheelSensitivity = 0;
+		break;
+
+	default:
+		break;
+	}
+
+	startEffect(env, data);
+}
+
+void PcSpkDriver::startEffect(EffectEnvelope &env, const byte *data) {
+	env.state = 1;
+	env.currentLevel = 0;
+	env.modWheelLast = 31;
+	env.duration = data[0] * 63;
+
+	env.stateTargetLevels[0] = data[1];
+	env.stateTargetLevels[1] = data[3];
+	env.stateTargetLevels[2] = data[5];
+	env.stateTargetLevels[3] = data[6];
+
+	env.stateModWheelLevels[0] = data[2];
+	env.stateModWheelLevels[1] = data[4];
+	env.stateModWheelLevels[2] = 0;
+	env.stateModWheelLevels[3] = data[7];
+
+	initNextEnvelopeState(env);
+}
+
+void PcSpkDriver::initNextEnvelopeState(EffectEnvelope &env) {
+	uint8 lastState = env.state - 1;
+
+	uint16 stepCount = _effectEnvStepTable[getEffectModifier(((env.stateTargetLevels[lastState] & 0x7F) << 5) + env.modWheelSensitivity)];
+	if (env.stateTargetLevels[lastState] & 0x80)
+		stepCount = getRandScale(stepCount);
+	if (!stepCount)
+		stepCount = 1;
+
+	env.stateNumSteps = env.stateStepCounter = stepCount;
+
+	int16 totalChange = 0;
+	if (lastState != 2) {
+		totalChange = getEffectModLevel(env.maxLevel, (env.stateModWheelLevels[lastState] & 0x7F) - 31);
+		if (env.stateModWheelLevels[lastState] & 0x80)
+			totalChange = getRandScale(totalChange);
+
+		if (totalChange + env.startLevel > env.maxLevel)
+			totalChange = env.maxLevel - env.startLevel;
+		else if (totalChange + env.startLevel < 0)
+			totalChange = -env.startLevel;
+
+		totalChange -= env.currentLevel;
+	}
+
+	env.changePerStep = totalChange / stepCount;
+	if (totalChange < 0) {
+		totalChange = -totalChange;
+		env.dir = -1;
+	} else {
+		env.dir = 1;
+	}
+	env.changePerStepRem = totalChange % stepCount;
+	env.changeCountRem = 0;
+}
+
+void PcSpkDriver::updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def) {
+	if (advanceEffectEnvelope(env, def) & 1) {
+		switch (def.type) {
+		case 0: case 1:
+			chan._out.unk60 = def.phase << 4;
+			break;
+
+		case 2:
+			chan._out.unkB = (def.phase & 0xFF) + chan._instrument[1];
+			break;
+
+		case 3:
+			chan._out.unkC = (def.phase & 0xFF) + chan._instrument[2];
+			break;
+
+		case 4:
+			if ((chan._instrument[4] + (def.phase & 0xFF)) * 256 < ARRAYSIZE(_outInstrumentData))
+				chan._out.instrument = _outInstrumentData + (chan._instrument[4] + (def.phase & 0xFF)) * 256;
+			else
+				chan._out.instrument = 0;
+			break;
+
+		case 5:
+			env.modWheelState = (def.phase & 0xFF);
+			break;
+
+		case 6:
+			env.modWheelSensitivity = (def.phase & 0xFF);
+			break;
+
+		default:
+			break;
+		}
+	}
+}
+
+uint8 PcSpkDriver::advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def) {
+	if (env.duration != 0) {
+		env.duration -= 17;
+		if (env.duration <= 0) {
+			env.state = 0;
+			return 0;
+		}
+	}
+
+	uint8 changedFlags = 0;
+	int16 newLevel = env.currentLevel + env.changePerStep;
+	env.changeCountRem += env.changePerStepRem;
+	if (env.changeCountRem >= env.stateNumSteps) {
+		env.changeCountRem -= env.stateNumSteps;
+		newLevel += env.dir;
+	}
+
+	if (env.currentLevel != newLevel || env.modWheelLast != env.modWheelState) {
+		env.currentLevel = newLevel;
+		env.modWheelLast = env.modWheelState;
+
+		int16 newPhase = getEffectModLevel(newLevel, env.modWheelState);
+		if (def.phase != newPhase) {
+			changedFlags |= 1;
+			def.phase = newPhase;
+		}
+	}
+
+	--env.stateStepCounter;
+	if (!env.stateStepCounter) {
+		++env.state;
+		if (env.state > 4) {
+			if (env.loop) {
+				env.state = 1;
+				changedFlags |= 2;
+			} else {
+				env.state = 0;
+				return changedFlags;
+			}
+		}
+
+		initNextEnvelopeState(env);
+	}
+
+	return changedFlags;
+}
+
+const byte PcSpkDriver::_outInstrumentData[1024] = {
+	0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15,
+	0x18, 0x1B, 0x1E, 0x21, 0x24, 0x27, 0x2A, 0x2D,
+	0x30, 0x33, 0x36, 0x39, 0x3B, 0x3E, 0x41, 0x43,
+	0x46, 0x49, 0x4B, 0x4E, 0x50, 0x52, 0x55, 0x57,
+	0x59, 0x5B, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x67,
+	0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x71, 0x72, 0x74,
+	0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7B,
+	0x7C, 0x7D, 0x7D, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E,
+	0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7D, 0x7D,
+	0x7C, 0x7B, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76,
+	0x75, 0x74, 0x72, 0x71, 0x70, 0x6E, 0x6C, 0x6B,
+	0x69, 0x67, 0x66, 0x64, 0x62, 0x60, 0x5E, 0x5B,
+	0x59, 0x57, 0x55, 0x52, 0x50, 0x4E, 0x4B, 0x49,
+	0x46, 0x43, 0x41, 0x3E, 0x3B, 0x39, 0x36, 0x33,
+	0x30, 0x2D, 0x2A, 0x27, 0x24, 0x21, 0x1E, 0x1B,
+	0x18, 0x15, 0x12, 0x0F, 0x0C, 0x09, 0x06, 0x03,
+	0x00, 0xFD, 0xFA, 0xF7, 0xF4, 0xF1, 0xEE, 0xEB,
+	0xE8, 0xE5, 0xE2, 0xDF, 0xDC, 0xD9, 0xD6, 0xD3,
+	0xD0, 0xCD, 0xCA, 0xC7, 0xC5, 0xC2, 0xBF, 0xBD,
+	0xBA, 0xB7, 0xB5, 0xB2, 0xB0, 0xAE, 0xAB, 0xA9,
+	0xA7, 0xA5, 0xA2, 0xA0, 0x9E, 0x9C, 0x9A, 0x99,
+	0x97, 0x95, 0x94, 0x92, 0x90, 0x8F, 0x8E, 0x8C,
+	0x8B, 0x8A, 0x89, 0x88, 0x87, 0x86, 0x85, 0x85,
+	0x84, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82,
+	0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x83, 0x83,
+	0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A,
+	0x8B, 0x8C, 0x8E, 0x8F, 0x90, 0x92, 0x94, 0x95,
+	0x97, 0x99, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA5,
+	0xA7, 0xA9, 0xAB, 0xAE, 0xB0, 0xB2, 0xB5, 0xB7,
+	0xBA, 0xBD, 0xBF, 0xC2, 0xC5, 0xC7, 0xCA, 0xCD,
+	0xD0, 0xD3, 0xD6, 0xD9, 0xDC, 0xDF, 0xE2, 0xE5,
+	0xE8, 0xEB, 0xEE, 0xF1, 0xF4, 0xF7, 0xFA, 0xFD,
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+	0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+	0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+	0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+	0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+	0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+	0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+	0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+	0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+	0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+	0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
+	0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+	0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
+	0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+	0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
+	0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+	0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
+	0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+	0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
+	0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+	0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
+	0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+	0x29, 0x23, 0xBE, 0x84, 0xE1, 0x6C, 0xD6, 0xAE,
+	0x52, 0x90, 0x49, 0xF1, 0xF1, 0xBB, 0xE9, 0xEB,
+	0xB3, 0xA6, 0xDB, 0x3C, 0x87, 0x0C, 0x3E, 0x99,
+	0x24, 0x5E, 0x0D, 0x1C, 0x06, 0xB7, 0x47, 0xDE,
+	0xB3, 0x12, 0x4D, 0xC8, 0x43, 0xBB, 0x8B, 0xA6,
+	0x1F, 0x03, 0x5A, 0x7D, 0x09, 0x38, 0x25, 0x1F,
+	0x5D, 0xD4, 0xCB, 0xFC, 0x96, 0xF5, 0x45, 0x3B,
+	0x13, 0x0D, 0x89, 0x0A, 0x1C, 0xDB, 0xAE, 0x32,
+	0x20, 0x9A, 0x50, 0xEE, 0x40, 0x78, 0x36, 0xFD,
+	0x12, 0x49, 0x32, 0xF6, 0x9E, 0x7D, 0x49, 0xDC,
+	0xAD, 0x4F, 0x14, 0xF2, 0x44, 0x40, 0x66, 0xD0,
+	0x6B, 0xC4, 0x30, 0xB7, 0x32, 0x3B, 0xA1, 0x22,
+	0xF6, 0x22, 0x91, 0x9D, 0xE1, 0x8B, 0x1F, 0xDA,
+	0xB0, 0xCA, 0x99, 0x02, 0xB9, 0x72, 0x9D, 0x49,
+	0x2C, 0x80, 0x7E, 0xC5, 0x99, 0xD5, 0xE9, 0x80,
+	0xB2, 0xEA, 0xC9, 0xCC, 0x53, 0xBF, 0x67, 0xD6,
+	0xBF, 0x14, 0xD6, 0x7E, 0x2D, 0xDC, 0x8E, 0x66,
+	0x83, 0xEF, 0x57, 0x49, 0x61, 0xFF, 0x69, 0x8F,
+	0x61, 0xCD, 0xD1, 0x1E, 0x9D, 0x9C, 0x16, 0x72,
+	0x72, 0xE6, 0x1D, 0xF0, 0x84, 0x4F, 0x4A, 0x77,
+	0x02, 0xD7, 0xE8, 0x39, 0x2C, 0x53, 0xCB, 0xC9,
+	0x12, 0x1E, 0x33, 0x74, 0x9E, 0x0C, 0xF4, 0xD5,
+	0xD4, 0x9F, 0xD4, 0xA4, 0x59, 0x7E, 0x35, 0xCF,
+	0x32, 0x22, 0xF4, 0xCC, 0xCF, 0xD3, 0x90, 0x2D,
+	0x48, 0xD3, 0x8F, 0x75, 0xE6, 0xD9, 0x1D, 0x2A,
+	0xE5, 0xC0, 0xF7, 0x2B, 0x78, 0x81, 0x87, 0x44,
+	0x0E, 0x5F, 0x50, 0x00, 0xD4, 0x61, 0x8D, 0xBE,
+	0x7B, 0x05, 0x15, 0x07, 0x3B, 0x33, 0x82, 0x1F,
+	0x18, 0x70, 0x92, 0xDA, 0x64, 0x54, 0xCE, 0xB1,
+	0x85, 0x3E, 0x69, 0x15, 0xF8, 0x46, 0x6A, 0x04,
+	0x96, 0x73, 0x0E, 0xD9, 0x16, 0x2F, 0x67, 0x68,
+	0xD4, 0xF7, 0x4A, 0x4A, 0xD0, 0x57, 0x68, 0x76
+};
+
+const byte PcSpkDriver::_outputTable1[] = {
+	0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1,
+	2, 2, 2, 2, 2, 2, 2, 2,
+	2, 2, 2, 2, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	4, 4, 4, 4, 4, 4, 4, 4,
+	4, 4, 4, 4, 5, 5, 5, 5,
+	5, 5, 5, 5, 5, 5, 5, 5,
+	6, 6, 6, 6, 6, 6, 6, 6,
+	6, 6, 6, 6, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 7
+};
+
+const byte PcSpkDriver::_outputTable2[] = {
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7,
+	8,  9, 10, 11,
+	0,  1,  2,  3,
+	4,  5,  6,  7
+};
+
+const uint16 PcSpkDriver::_effectEnvStepTable[] = {
+	  1,    2,    4,    5,
+	  6,    7,    8,    9,
+	 10,   12,   14,   16,
+	 18,   21,   24,   30,
+	 36,   50,   64,   82,
+	100,  136,  160,  192,
+	240,  276,  340,  460,
+	600,  860, 1200, 1600
+};
+
+const uint16 PcSpkDriver::_frequencyTable[] = {
+	0x8E84, 0x8E00, 0x8D7D, 0x8CFA,
+	0x8C78, 0x8BF7, 0x8B76, 0x8AF5,
+	0x8A75, 0x89F5, 0x8976, 0x88F7,
+	0x8879, 0x87FB, 0x877D, 0x8700,
+	0x8684, 0x8608, 0x858C, 0x8511,
+	0x8496, 0x841C, 0x83A2, 0x8328,
+	0x82AF, 0x8237, 0x81BF, 0x8147,
+	0x80D0, 0x8059, 0x7FE3, 0x7F6D,
+	0x7EF7, 0x7E82, 0x7E0D, 0x7D99,
+	0x7D25, 0x7CB2, 0x7C3F, 0x7BCC,
+	0x7B5A, 0x7AE8, 0x7A77, 0x7A06,
+	0x7995, 0x7925, 0x78B5, 0x7846,
+	0x77D7, 0x7768, 0x76FA, 0x768C,
+	0x761F, 0x75B2, 0x7545, 0x74D9,
+	0x746D, 0x7402, 0x7397, 0x732C,
+	0x72C2, 0x7258, 0x71EF, 0x7186,
+	0x711D, 0x70B5, 0x704D, 0x6FE5,
+	0x6F7E, 0x6F17, 0x6EB0, 0x6E4A,
+	0x6DE5, 0x6D7F, 0x6D1A, 0x6CB5,
+	0x6C51, 0x6BED, 0x6B8A, 0x6B26,
+	0x6AC4, 0x6A61, 0x69FF, 0x699D,
+	0x693C, 0x68DB, 0x687A, 0x681A,
+	0x67BA, 0x675A, 0x66FA, 0x669B,
+	0x663D, 0x65DF, 0x6581, 0x6523,
+	0x64C6, 0x6469, 0x640C, 0x63B0,
+	0x6354, 0x62F8, 0x629D, 0x6242,
+	0x61E7, 0x618D, 0x6133, 0x60D9,
+	0x6080, 0x6027, 0x5FCE, 0x5F76,
+	0x5F1E, 0x5EC6, 0x5E6E, 0x5E17,
+	0x5DC1, 0x5D6A, 0x5D14, 0x5CBE,
+	0x5C68, 0x5C13, 0x5BBE, 0x5B6A,
+	0x5B15, 0x5AC1, 0x5A6E, 0x5A1A,
+	0x59C7, 0x5974, 0x5922, 0x58CF,
+	0x587D, 0x582C, 0x57DA, 0x5789,
+	0x5739, 0x56E8, 0x5698, 0x5648,
+	0x55F9, 0x55A9, 0x555A, 0x550B,
+	0x54BD, 0x546F, 0x5421, 0x53D3,
+	0x5386, 0x5339, 0x52EC, 0x52A0,
+	0x5253, 0x5207, 0x51BC, 0x5170,
+	0x5125, 0x50DA, 0x5090, 0x5046,
+	0x4FFB, 0x4FB2, 0x4F68, 0x4F1F,
+	0x4ED6, 0x4E8D, 0x4E45, 0x4DFC,
+	0x4DB5, 0x4D6D, 0x4D25, 0x4CDE,
+	0x4C97, 0x4C51, 0x4C0A, 0x4BC4,
+	0x4B7E, 0x4B39, 0x4AF3, 0x4AAE,
+	0x4A69, 0x4A24, 0x49E0, 0x499C,
+	0x4958, 0x4914, 0x48D1, 0x488E,
+	0x484B, 0x4808, 0x47C6, 0x4783
+};
+
+} // End of namespace Scumm
diff --git a/engines/scumm/imuse/drivers/pcspk.h b/engines/scumm/imuse/drivers/pcspk.h
new file mode 100644
index 0000000..6a107e1
--- /dev/null
+++ b/engines/scumm/imuse/drivers/pcspk.h
@@ -0,0 +1,161 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_IMUSE_PCSPK_H
+#define SCUMM_IMUSE_PCSPK_H
+
+#include "audio/softsynth/emumidi.h"
+#include "audio/softsynth/pcspk.h"
+
+namespace Scumm {
+
+class PcSpkDriver : public MidiDriver_Emulated {
+public:
+	PcSpkDriver(Audio::Mixer *mixer);
+	~PcSpkDriver();
+
+	virtual int open();
+	virtual void close();
+
+	virtual void send(uint32 d);
+	virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr);
+
+	virtual MidiChannel *allocateChannel();
+	virtual MidiChannel *getPercussionChannel() { return 0; }
+
+	bool isStereo() const { return _pcSpk.isStereo(); }
+	int getRate() const { return _pcSpk.getRate(); }
+protected:
+	void generateSamples(int16 *buf, int len);
+	void onTimer();
+
+private:
+	Audio::PCSpeaker _pcSpk;
+	int _effectTimer;
+	uint8 _randBase;
+
+	void updateNote();
+	void output(uint16 out);
+
+	static uint8 getEffectModifier(uint16 level);
+	int16 getEffectModLevel(int16 level, int8 mod);
+	int16 getRandScale(int16 input);
+
+	struct EffectEnvelope {
+		uint8 state;
+		int16 currentLevel;
+		int16 duration;
+		int16 maxLevel;
+		int16 startLevel;
+		uint8 loop;
+		uint8 stateTargetLevels[4];
+		uint8 stateModWheelLevels[4];
+		uint8 modWheelSensitivity;
+		uint8 modWheelState;
+		uint8 modWheelLast;
+		int16 stateNumSteps;
+		int16 stateStepCounter;
+		int16 changePerStep;
+		int8 dir;
+		int16 changePerStepRem;
+		int16 changeCountRem;
+	};
+
+	struct EffectDefinition {
+		int16 phase;
+		uint8 type;
+		uint8 useModWheel;
+		EffectEnvelope *envelope;
+	};
+
+	struct OutputChannel {
+		uint8 active;
+		uint8 note;
+		uint8 sustainNoteOff;
+		uint8 length;
+		const uint8 *instrument;
+		uint8 unkA;
+		uint8 unkB;
+		uint8 unkC;
+		int16 unkE;
+		EffectEnvelope effectEnvelopeA;
+		EffectDefinition effectDefA;
+		EffectEnvelope effectEnvelopeB;
+		EffectDefinition effectDefB;
+		int16 unk60;
+	};
+
+	struct MidiChannel_PcSpk : public MidiChannel {
+		virtual MidiDriver *device();
+		virtual byte getNumber();
+		virtual void release();
+
+		virtual void send(uint32 b);
+		virtual void noteOff(byte note);
+		virtual void noteOn(byte note, byte velocity);
+		virtual void programChange(byte program);
+		virtual void pitchBend(int16 bend);
+		virtual void controlChange(byte control, byte value);
+		virtual void pitchBendFactor(byte value);
+		virtual void priority(byte value);
+		virtual void sysEx_customInstrument(uint32 type, const byte *instr);
+
+		void init(PcSpkDriver *owner, byte channel);
+		bool allocate();
+
+		PcSpkDriver *_owner;
+		bool _allocated;
+		byte _channel;
+
+		OutputChannel _out;
+		uint8 _instrument[23];
+		uint8 _programNr;
+		uint8 _priority;
+		uint8 _tl;
+		uint8 _modWheel;
+		uint8 _sustain;
+		uint8 _pitchBendFactor;
+		int16 _pitchBend;
+	};
+
+	void setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data);
+	void startEffect(EffectEnvelope &env, const byte *data);
+	void initNextEnvelopeState(EffectEnvelope &env);
+	void updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def);
+	uint8 advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def);
+
+	MidiChannel_PcSpk _channels[6];
+	MidiChannel_PcSpk *_activeChannel;
+
+	MidiChannel_PcSpk *_lastActiveChannel;
+	uint16 _lastActiveOut;
+
+	static const byte _outInstrumentData[1024];
+	static const byte _outputTable1[];
+	static const byte _outputTable2[];
+	static const uint16 _effectEnvStepTable[];
+	static const uint16 _frequencyTable[];
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/imuse/mac_m68k.cpp b/engines/scumm/imuse/mac_m68k.cpp
deleted file mode 100644
index 8ebd8e4..0000000
--- a/engines/scumm/imuse/mac_m68k.cpp
+++ /dev/null
@@ -1,515 +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 "scumm/imuse/mac_m68k.h"
-
-#include "common/util.h"
-#include "common/macresman.h"
-#include "common/stream.h"
-
-namespace Scumm {
-
-MacM68kDriver::MacM68kDriver(Audio::Mixer *mixer)
-	: MidiDriver_Emulated(mixer) {
-}
-
-MacM68kDriver::~MacM68kDriver() {
-}
-
-int MacM68kDriver::open() {
-	if (_isOpen) {
-		return MERR_ALREADY_OPEN;
-	}
-
-	const int error = MidiDriver_Emulated::open();
-	if (error) {
-		return error;
-	}
-
-	for (uint i = 0; i < ARRAYSIZE(_channels); ++i) {
-		_channels[i].init(this, i);
-	}
-
-	memset(_voiceChannels, 0, sizeof(_voiceChannels));
-	_lastUsedVoiceChannel = 0;
-
-	loadAllInstruments();
-
-	_pitchTable[116] = 1664510;
-	_pitchTable[117] = 1763487;
-	_pitchTable[118] = 1868350;
-	_pitchTable[119] = 1979447;
-	_pitchTable[120] = 2097152;
-	_pitchTable[121] = 2221855;
-	_pitchTable[122] = 2353973;
-	_pitchTable[123] = 2493948;
-	_pitchTable[124] = 2642246;
-	_pitchTable[125] = 2799362;
-	_pitchTable[126] = 2965820;
-	_pitchTable[127] = 3142177;
-	for (int i = 115; i >= 0; --i) {
-		_pitchTable[i] = _pitchTable[i + 12] / 2;
-	}
-
-	_volumeTable = new byte[8192];
-	for (int i = 0; i < 32; ++i) {
-		for (int j = 0; j < 256; ++j) {
-			_volumeTable[i * 256 + j] = ((-128 + j) * _volumeBaseTable[i]) / 127 - 128;
-		}
-	}
-
-	_mixBuffer = 0;
-	_mixBufferLength = 0;
-
-	// We set the output sound type to music here to allow sound volume
-	// adjustment. The drawback here is that we can not control the music and
-	// sfx separately here. But the AdLib output has the same issue so it
-	// should not be that bad.
-	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
-
-	return 0;
-}
-
-void MacM68kDriver::close() {
-	if (!_isOpen) {
-		return;
-	}
-
-	_mixer->stopHandle(_mixerSoundHandle);
-	_isOpen = false;
-	for (InstrumentMap::iterator i = _instruments.begin(); i != _instruments.end(); ++i) {
-		delete[] i->_value.data;
-	}
-	_instruments.clear();
-	delete[] _volumeTable;
-	_volumeTable = 0;
-	delete[] _mixBuffer;
-	_mixBuffer = 0;
-	_mixBufferLength = 0;
-}
-
-void MacM68kDriver::send(uint32 d) {
-	assert(false);
-}
-
-void MacM68kDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) {
-	assert(false);
-}
-
-MidiChannel *MacM68kDriver::allocateChannel() {
-	for (uint i = 0; i < ARRAYSIZE(_channels); ++i) {
-		if (_channels[i].allocate()) {
-			return &_channels[i];
-		}
-	}
-
-	return 0;
-}
-
-MacM68kDriver::Instrument MacM68kDriver::getInstrument(int idx) const {
-	InstrumentMap::const_iterator i = _instruments.find(idx);
-	if (i != _instruments.end()) {
-		return i->_value;
-	} else {
-		return _defaultInstrument;
-	}
-}
-
-void MacM68kDriver::generateSamples(int16 *buf, int len) {
-	int silentChannels = 0;
-
-	if (_mixBufferLength < len) {
-		delete[] _mixBuffer;
-
-		_mixBufferLength = len;
-		_mixBuffer = new int[_mixBufferLength];
-		assert(_mixBuffer);
-	}
-	memset(_mixBuffer, 0, sizeof(int) * _mixBufferLength);
-
-	for (int i = 0; i < kChannelCount; ++i) {
-		OutputChannel &out = _voiceChannels[i].out;
-		if (out.isFinished) {
-			++silentChannels;
-			continue;
-		}
-
-		byte *volumeTable = &_volumeTable[(out.volume / 4) * 256];
-		int *buffer = _mixBuffer;
-
-		int samplesLeft = len;
-		while (samplesLeft) {
-			out.subPos += out.pitchModifier;
-			while (out.subPos >= 0x10000) {
-				out.subPos -= 0x10000;
-				out.instrument++;
-			}
-
-			if (out.instrument >= out.end) {
-				if (!out.start) {
-					break;
-				}
-
-				out.instrument = out.start;
-				out.subPos = 0;
-			}
-
-			*buffer++ += volumeTable[*out.instrument];
-			--samplesLeft;
-		}
-
-		if (samplesLeft) {
-			out.isFinished = true;
-			while (samplesLeft--) {
-				*buffer++ += 0x80;
-			}
-		}
-	}
-
-	const int *buffer = _mixBuffer;
-	const int silenceAdd = silentChannels << 7;
-	while (len--) {
-		*buf++ = (((*buffer++ + silenceAdd) >> 3) << 8) ^ 0x8000;
-	}
-}
-
-void MacM68kDriver::loadAllInstruments() {
-	Common::MacResManager resource;
-	if (resource.open("iMUSE Setups")) {
-		if (!resource.hasResFork()) {
-			error("MacM68kDriver::loadAllInstruments: \"iMUSE Setups\" loaded, but no resource fork present");
-		}
-
-		for (int i = 0x3E7; i < 0x468; ++i) {
-			Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i);
-			if (stream) {
-				addInstrument(i, stream);
-				delete stream;
-			}
-		}
-
-		for (int i = 0x7D0; i < 0x8D0; ++i) {
-			Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i);
-			if (stream) {
-				addInstrument(i, stream);
-				delete stream;
-			}
-		}
-
-		InstrumentMap::iterator inst = _instruments.find(kDefaultInstrument);
-		if (inst != _instruments.end()) {
-			_defaultInstrument = inst->_value;
-		} else {
-			error("MacM68kDriver::loadAllInstruments: Could not load default instrument");
-		}
-	} else {
-		error("MacM68kDriver::loadAllInstruments: Could not load \"iMUSE Setups\"");
-	}
-}
-
-void MacM68kDriver::addInstrument(int idx, Common::SeekableReadStream *data) {
-	// We parse the "SND" files manually here, since we need special data
-	// from their header and need to work on them raw while mixing.
-	data->skip(2);
-	int count = data->readUint16BE();
-	data->skip(2 * (3 * count));
-	count = data->readUint16BE();
-	data->skip(2 * (4 * count));
-
-	Instrument inst;
-	// Skip (optional) pointer to data
-	data->skip(4);
-	inst.length        = data->readUint32BE();
-	inst.sampleRate    = data->readUint32BE();
-	inst.loopStart     = data->readUint32BE();
-	inst.loopEnd       = data->readUint32BE();
-	// Skip encoding
-	data->skip(1);
-	inst.baseFrequency = data->readByte();
-
-	inst.data = new byte[inst.length];
-	assert(inst.data);
-	data->read(inst.data, inst.length);
-	_instruments[idx] = inst;
-}
-
-void MacM68kDriver::setPitch(OutputChannel *out, int frequency) {
-	out->frequency = frequency;
-	out->isFinished = false;
-
-	const int pitchIdx = (frequency >> 7) + 60 - out->baseFrequency;
-	assert(pitchIdx >= 0);
-
-	const int low7Bits = frequency & 0x7F;
-	if (low7Bits) {
-		out->pitchModifier = _pitchTable[pitchIdx] + (((_pitchTable[pitchIdx + 1] - _pitchTable[pitchIdx]) * low7Bits) >> 7);
-	} else {
-		out->pitchModifier = _pitchTable[pitchIdx];
-	}
-}
-
-void MacM68kDriver::VoiceChannel::off() {
-	if (out.start) {
-		out.isFinished = true;
-	}
-
-	part->removeVoice(this);
-	part = 0;
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::release() {
-	_allocated = false;
-	while (_voice) {
-		_voice->off();
-	}
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::send(uint32 b) {
-	uint8 type = b & 0xF0;
-	uint8 p1 = (b >> 8) & 0xFF;
-	uint8 p2 = (b >> 16) & 0xFF;
-
-	switch (type) {
-	case 0x80:
-		noteOff(p1);
-		break;
-
-	case 0x90:
-		if (p2) {
-			noteOn(p1, p2);
-		} else {
-			noteOff(p1);
-		}
-		break;
-
-	case 0xB0:
-		controlChange(p1, p2);
-		break;
-
-	case 0xE0:
-		pitchBend((p1 | (p2 << 7)) - 0x2000);
-		break;
-
-	default:
-		break;
-	}
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::noteOff(byte note) {
-	for (VoiceChannel *i = _voice; i; i = i->next) {
-		if (i->note == note) {
-			if (_sustain) {
-				i->sustainNoteOff = true;
-			} else {
-				i->off();
-			}
-		}
-	}
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::noteOn(byte note, byte velocity) {
-	// Do not start a not unless there is an instrument set up
-	if (!_instrument.data) {
-		return;
-	}
-
-	// Allocate a voice channel
-	VoiceChannel *voice = _owner->allocateVoice(_priority);
-	if (!voice) {
-		return;
-	}
-	addVoice(voice);
-
-	voice->note = note;
-	// This completly ignores the note's volume, but is in accordance
-	// to the original.
-	voice->out.volume = _volume;
-
-	// Set up the instrument data
-	voice->out.baseFrequency = _instrument.baseFrequency;
-	voice->out.soundStart    = _instrument.data;
-	voice->out.soundEnd      = _instrument.data + _instrument.length;
-	if (_instrument.loopEnd && _instrument.loopEnd - 12 > _instrument.loopStart) {
-		voice->out.loopStart = _instrument.data + _instrument.loopStart;
-		voice->out.loopEnd   = _instrument.data + _instrument.loopEnd;
-	} else {
-		voice->out.loopStart = 0;
-		voice->out.loopEnd   = voice->out.soundEnd;
-	}
-
-	voice->out.start = voice->out.loopStart;
-	voice->out.end   = voice->out.loopEnd;
-
-	// Set up the pitch
-	_owner->setPitch(&voice->out, (note << 7) + _pitchBend);
-
-	// Set up the sample position
-	voice->out.instrument = voice->out.soundStart;
-	voice->out.subPos = 0;
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::programChange(byte program) {
-	_instrument = _owner->getInstrument(program + kProgramChangeBase);
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::pitchBend(int16 bend) {
-	_pitchBend = (bend * _pitchBendFactor) >> 6;
-	for (VoiceChannel *i = _voice; i; i = i->next) {
-		_owner->setPitch(&i->out, (i->note << 7) + _pitchBend);
-	}
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::controlChange(byte control, byte value) {
-	switch (control) {
-	// volume change
-	case 7:
-		_volume = value;
-		for (VoiceChannel *i = _voice; i; i = i->next) {
-			i->out.volume = value;
-			i->out.isFinished = false;
-		}
-		break;
-
-	// sustain
-	case 64:
-		_sustain = value;
-		if (!_sustain) {
-			for (VoiceChannel *i = _voice; i; i = i->next) {
-				if (i->sustainNoteOff) {
-					i->off();
-				}
-			}
-		}
-		break;
-
-	// all notes off
-	case 123:
-		for (VoiceChannel *i = _voice; i; i = i->next) {
-			i->off();
-		}
-		break;
-
-	default:
-		break;
-	}
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::pitchBendFactor(byte value) {
-	_pitchBendFactor = value;
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::priority(byte value) {
-	_priority = value;
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::sysEx_customInstrument(uint32 type, const byte *instr) {
-	assert(instr);
-	if (type == 'MAC ') {
-		_instrument = _owner->getInstrument(*instr + kSysExBase);
-	}
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::init(MacM68kDriver *owner, byte channel) {
-	_owner = owner;
-	_number = channel;
-	_allocated = false;
-}
-
-bool MacM68kDriver::MidiChannel_MacM68k::allocate() {
-	if (_allocated) {
-		return false;
-	}
-
-	_allocated = true;
-	_voice = 0;
-	_priority = 0;
-	memset(&_instrument, 0, sizeof(_instrument));
-	_pitchBend = 0;
-	_pitchBendFactor = 0;
-	_volume = 0;
-	return true;
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::addVoice(VoiceChannel *voice) {
-	voice->next = _voice;
-	voice->prev = 0;
-	voice->part = this;
-	if (_voice) {
-		_voice->prev = voice;
-	}
-	_voice = voice;
-}
-
-void MacM68kDriver::MidiChannel_MacM68k::removeVoice(VoiceChannel *voice) {
-	VoiceChannel *i = _voice;
-	while (i && i != voice) {
-		i = i->next;
-	}
-
-	if (i) {
-		if (i->next) {
-			i->next->prev = i->prev;
-		}
-
-		if (i->prev) {
-			i->prev->next = i->next;
-		} else {
-			_voice = i->next;
-		}
-	}
-}
-
-MacM68kDriver::VoiceChannel *MacM68kDriver::allocateVoice(int priority) {
-	VoiceChannel *channel = 0;
-	for (int i = 0; i < kChannelCount; ++i) {
-		if (++_lastUsedVoiceChannel == kChannelCount) {
-			_lastUsedVoiceChannel = 0;
-		}
-
-		VoiceChannel *cur = &_voiceChannels[_lastUsedVoiceChannel];
-		if (!cur->part) {
-			memset(cur, 0, sizeof(*cur));
-			return cur;
-		} else if (!cur->next) {
-			if (cur->part->_priority <= priority) {
-				priority = cur->part->_priority;
-				channel = cur;
-			}
-		}
-	}
-
-	if (channel) {
-		channel->off();
-		memset(channel, 0, sizeof(*channel));
-	}
-
-	return channel;
-}
-
-const int MacM68kDriver::_volumeBaseTable[32] = {
-	  0,   0,   1,   1,   2,   3,   5,   6,
-	  8,  11,  13,  16,  19,  22,  26,  30,
-	 34,  38,  43,  48,  53,  58,  64,  70,
-	 76,  83,  89,  96, 104, 111, 119, 127
-};
-
-} // End of namespace Scumm
diff --git a/engines/scumm/imuse/mac_m68k.h b/engines/scumm/imuse/mac_m68k.h
deleted file mode 100644
index 31beaf4..0000000
--- a/engines/scumm/imuse/mac_m68k.h
+++ /dev/null
@@ -1,178 +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 SCUMM_IMUSE_MAC_M68K_H
-#define SCUMM_IMUSE_MAC_M68K_H
-
-#include "audio/softsynth/emumidi.h"
-
-#include "common/hashmap.h"
-
-namespace Common {
-class SeekableReadStream;
-}
-
-namespace Scumm {
-
-class MacM68kDriver : public MidiDriver_Emulated {
-	friend class MidiChannel_MacM68k;
-public:
-	MacM68kDriver(Audio::Mixer *mixer);
-	~MacM68kDriver();
-
-	virtual int open();
-	virtual void close();
-
-	virtual void send(uint32 d);
-	virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr);
-
-	virtual MidiChannel *allocateChannel();
-	virtual MidiChannel *getPercussionChannel() { return 0; }
-
-	virtual bool isStereo() const { return false; }
-	virtual int getRate() const {
-		// The original is using a frequency of approx. 22254.54546 here.
-		// To be precise it uses the 16.16 fixed point value 0x56EE8BA3.
-		return 22254;
-	}
-
-protected:
-	virtual void generateSamples(int16 *buf, int len);
-	virtual void onTimer() {}
-
-private:
-	int *_mixBuffer;
-	int _mixBufferLength;
-
-	struct Instrument {
-		uint length;
-		uint sampleRate;
-		uint loopStart;
-		uint loopEnd;
-		int baseFrequency;
-
-		byte *data;
-	};
-
-	enum {
-		kDefaultInstrument = 0x3E7,
-		kProgramChangeBase = 0x3E8,
-		kSysExBase         = 0x7D0
-	};
-
-	Instrument getInstrument(int idx) const;
-	typedef Common::HashMap<int, Instrument> InstrumentMap;
-	InstrumentMap _instruments;
-	Instrument _defaultInstrument;
-	void loadAllInstruments();
-	void addInstrument(int idx, Common::SeekableReadStream *data);
-
-	struct OutputChannel {
-		int pitchModifier;
-
-		const byte *instrument;
-		uint subPos;
-
-		const byte *start;
-		const byte *end;
-
-		const byte *soundStart;
-		const byte *soundEnd;
-		const byte *loopStart;
-		const byte *loopEnd;
-
-		int frequency;
-		int volume;
-
-		bool isFinished;
-
-		int baseFrequency;
-	};
-
-	void setPitch(OutputChannel *out, int frequency);
-	int _pitchTable[128];
-
-	byte *_volumeTable;
-	static const int _volumeBaseTable[32];
-
-	class MidiChannel_MacM68k;
-
-	struct VoiceChannel {
-		MidiChannel_MacM68k *part;
-		VoiceChannel *prev, *next;
-		int channel;
-		int note;
-		bool sustainNoteOff;
-		OutputChannel out;
-
-		void off();
-	};
-
-	class MidiChannel_MacM68k : public MidiChannel {
-		friend class MacM68kDriver;
-	public:
-		virtual MidiDriver *device() { return _owner; }
-		virtual byte getNumber() { return _number; }
-		virtual void release();
-
-		virtual void send(uint32 b);
-		virtual void noteOff(byte note);
-		virtual void noteOn(byte note, byte velocity);
-		virtual void programChange(byte program);
-		virtual void pitchBend(int16 bend);
-		virtual void controlChange(byte control, byte value);
-		virtual void pitchBendFactor(byte value);
-		virtual void priority(byte value);
-		virtual void sysEx_customInstrument(uint32 type, const byte *instr);
-
-		void init(MacM68kDriver *owner, byte channel);
-		bool allocate();
-
-		void addVoice(VoiceChannel *voice);
-		void removeVoice(VoiceChannel *voice);
-	private:
-		MacM68kDriver *_owner;
-		bool _allocated;
-		int _number;
-
-		VoiceChannel *_voice;
-		int _priority;
-		int _sustain;
-		Instrument _instrument;
-		int _pitchBend;
-		int _pitchBendFactor;
-		int _volume;
-	};
-
-	MidiChannel_MacM68k _channels[32];
-
-	enum {
-		kChannelCount = 8
-	};
-	VoiceChannel _voiceChannels[kChannelCount];
-	int _lastUsedVoiceChannel;
-	VoiceChannel *allocateVoice(int priority);
-};
-
-} // End of namespace Scumm
-
-#endif
diff --git a/engines/scumm/imuse/pcspk.cpp b/engines/scumm/imuse/pcspk.cpp
deleted file mode 100644
index 856b771..0000000
--- a/engines/scumm/imuse/pcspk.cpp
+++ /dev/null
@@ -1,835 +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 "scumm/imuse/pcspk.h"
-
-#include "common/util.h"
-
-namespace Scumm {
-
-PcSpkDriver::PcSpkDriver(Audio::Mixer *mixer)
-	: MidiDriver_Emulated(mixer), _pcSpk(mixer->getOutputRate()) {
-}
-
-PcSpkDriver::~PcSpkDriver() {
-	close();
-}
-
-int PcSpkDriver::open() {
-	if (_isOpen)
-		return MERR_ALREADY_OPEN;
-
-	MidiDriver_Emulated::open();
-
-	for (uint i = 0; i < 6; ++i)
-		_channels[i].init(this, i);
-	_activeChannel = 0;
-	_effectTimer = 0;
-	_randBase = 1;
-
-	// We need to take care we only send note frequencies, when the internal
-	// settings actually changed, thus we need some extra state to keep track
-	// of that.
-	_lastActiveChannel = 0;
-	_lastActiveOut = 0;
-
-	// We set the output sound type to music here to allow sound volume
-	// adjustment. The drawback here is that we can not control the music and
-	// sfx separately here. But the AdLib output has the same issue so it
-	// should not be that bad.
-	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
-	return 0;
-}
-
-void PcSpkDriver::close() {
-	if (!_isOpen)
-		return;
-	_isOpen = false;
-
-	_mixer->stopHandle(_mixerSoundHandle);
-}
-
-void PcSpkDriver::send(uint32 d) {
-	assert((d & 0x0F) < 6);
-	_channels[(d & 0x0F)].send(d);
-}
-
-void PcSpkDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) {
-	assert(channel < 6);
-	if (type == 'SPK ')
-		_channels[channel].sysEx_customInstrument(type, instr);
-}
-
-MidiChannel *PcSpkDriver::allocateChannel() {
-	for (uint i = 0; i < 6; ++i) {
-		if (_channels[i].allocate())
-			return &_channels[i];
-	}
-
-	return 0;
-}
-
-void PcSpkDriver::generateSamples(int16 *buf, int len) {
-	_pcSpk.readBuffer(buf, len);
-}
-
-void PcSpkDriver::onTimer() {
-	if (!_activeChannel)
-		return;
-
-	for (uint i = 0; i < 6; ++i) {
-		OutputChannel &out = _channels[i]._out;
-
-		if (!out.active)
-			continue;
-
-		if (out.length == 0 || --out.length != 0) {
-			if (out.unkB && out.unkC) {
-				out.unkA += out.unkB;
-				if (out.instrument)
-					out.unkE = ((int8)out.instrument[out.unkA] * out.unkC) >> 4;
-			}
-
-			++_effectTimer;
-			if (_effectTimer > 3) {
-				_effectTimer = 0;
-
-				if (out.effectEnvelopeA.state)
-					updateEffectGenerator(_channels[i], out.effectEnvelopeA, out.effectDefA);
-				if (out.effectEnvelopeB.state)
-					updateEffectGenerator(_channels[i], out.effectEnvelopeB, out.effectDefB);
-			}
-		} else {
-			out.active = 0;
-			updateNote();
-			return;
-		}
-	}
-
-	if (_activeChannel->_tl) {
-		output((_activeChannel->_out.note << 7) + _activeChannel->_pitchBend + _activeChannel->_out.unk60 + _activeChannel->_out.unkE);
-	} else {
-		_pcSpk.stop();
-		_lastActiveChannel = 0;
-		_lastActiveOut = 0;
-	}
-}
-
-void PcSpkDriver::updateNote() {
-	uint8 priority = 0;
-	_activeChannel = 0;
-	for (uint i = 0; i < 6; ++i) {
-		if (_channels[i]._allocated && _channels[i]._out.active && _channels[i]._priority >= priority) {
-			priority = _channels[i]._priority;
-			_activeChannel = &_channels[i];
-		}
-	}
-
-	if (_activeChannel == 0 || _activeChannel->_tl == 0) {
-		_pcSpk.stop();
-		_lastActiveChannel = 0;
-		_lastActiveOut = 0;
-	} else {
-		output(_activeChannel->_pitchBend + (_activeChannel->_out.note << 7));
-	}
-}
-
-void PcSpkDriver::output(uint16 out) {
-	byte v1 = (out >> 7) & 0xFF;
-	byte v2 = (out >> 2) & 0x1E;
-
-	byte shift = _outputTable1[v1];
-	uint16 indexBase = _outputTable2[v1] << 5;
-	uint16 frequency = _frequencyTable[(indexBase + v2) / 2] >> shift;
-
-	// Only output in case the active channel changed or the frequency changed.
-	// This is not faithful to the original. Since our timings differ we would
-	// get distorted sound otherwise though.
-	if (_lastActiveChannel != _activeChannel || _lastActiveOut != out) {
-		_pcSpk.play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / frequency, -1);
-		_lastActiveChannel = _activeChannel;
-		_lastActiveOut = out;
-	}
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::init(PcSpkDriver *owner, byte channel) {
-	_owner = owner;
-	_channel = channel;
-	_allocated = false;
-	memset(&_out, 0, sizeof(_out));
-}
-
-bool PcSpkDriver::MidiChannel_PcSpk::allocate() {
-	if (_allocated)
-		return false;
-
-	memset(&_out, 0, sizeof(_out));
-	memset(_instrument, 0, sizeof(_instrument));
-	_out.effectDefA.envelope = &_out.effectEnvelopeA;
-	_out.effectDefB.envelope = &_out.effectEnvelopeB;
-
-	_allocated = true;
-	return true;
-}
-
-MidiDriver *PcSpkDriver::MidiChannel_PcSpk::device() {
-	return _owner;
-}
-
-byte PcSpkDriver::MidiChannel_PcSpk::getNumber() {
-	return _channel;
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::release() {
-	_out.active = 0;
-	_allocated = false;
-	_owner->updateNote();
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::send(uint32 b) {
-	uint8 type = b & 0xF0;
-	uint8 p1 = (b >> 8) & 0xFF;
-	uint8 p2 = (b >> 16) & 0xFF;
-
-	switch (type) {
-	case 0x80:
-		noteOff(p1);
-		break;
-
-	case 0x90:
-		if (p2)
-			noteOn(p1, p2);
-		else
-			noteOff(p1);
-		break;
-
-	case 0xB0:
-		controlChange(p1, p2);
-		break;
-
-	case 0xE0:
-		pitchBend((p1 | (p2 << 7)) - 0x2000);
-		break;
-
-	default:
-		break;
-	}
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::noteOff(byte note) {
-	if (!_allocated)
-		return;
-
-	if (_sustain) {
-		if (_out.note == note)
-			_out.sustainNoteOff = 1;
-	} else {
-		if (_out.note == note) {
-			_out.active = 0;
-			_owner->updateNote();
-		}
-	}
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::noteOn(byte note, byte velocity) {
-	if (!_allocated)
-		return;
-
-	_out.note = note;
-	_out.sustainNoteOff = 0;
-	_out.length = _instrument[0];
-
-	if (_instrument[4] * 256 < ARRAYSIZE(PcSpkDriver::_outInstrumentData))
-		_out.instrument = _owner->_outInstrumentData + _instrument[4] * 256;
-	else
-		_out.instrument = 0;
-
-	_out.unkA = 0;
-	_out.unkB = _instrument[1];
-	_out.unkC = _instrument[2];
-	_out.unkE = 0;
-	_out.unk60 = 0;
-	_out.active = 1;
-
-	// In case we get a note on event on the last active channel, we reset the
-	// last active channel, thus we assure the frequency is correctly set, even
-	// when the same note was sent.
-	if (_owner->_lastActiveChannel == this) {
-		_owner->_lastActiveChannel = 0;
-		_owner->_lastActiveOut = 0;
-	}
-	_owner->updateNote();
-
-	_out.unkC += PcSpkDriver::getEffectModifier(_instrument[3] + ((velocity & 0xFE) << 4));
-	if (_out.unkC > 63)
-		_out.unkC = 63;
-
-	if ((_instrument[5] & 0x80) != 0)
-		_owner->setupEffects(*this, _out.effectEnvelopeA, _out.effectDefA, _instrument[5], _instrument + 6);
-
-	if ((_instrument[14] & 0x80) != 0)
-		_owner->setupEffects(*this, _out.effectEnvelopeB, _out.effectDefB, _instrument[14], _instrument + 15);
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::programChange(byte program) {
-	// Nothing to implement here, the iMuse code takes care of passing us the
-	// instrument data.
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::pitchBend(int16 bend) {
-	_pitchBend = (bend * _pitchBendFactor) >> 6;
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::controlChange(byte control, byte value) {
-	switch (control) {
-	case 1:
-		if (_out.effectEnvelopeA.state && _out.effectDefA.useModWheel)
-			_out.effectEnvelopeA.modWheelState = (value >> 2);
-		if (_out.effectEnvelopeB.state && _out.effectDefB.useModWheel)
-			_out.effectEnvelopeB.modWheelState = (value >> 2);
-		break;
-
-	case 7:
-		_tl = value;
-		if (_owner->_activeChannel == this) {
-			if (_tl == 0) {
-				_owner->_lastActiveChannel = 0;
-				_owner->_lastActiveOut = 0;
-				_owner->_pcSpk.stop();
-			} else {
-				_owner->output((_out.note << 7) + _pitchBend + _out.unk60 + _out.unkE);
-			}
-		}
-		break;
-
-	case 64:
-		_sustain = value;
-		if (!value && _out.sustainNoteOff) {
-			_out.active = 0;
-			_owner->updateNote();
-		}
-		break;
-
-	case 123:
-		_out.active = 0;
-		_owner->updateNote();
-		break;
-
-	default:
-		break;
-	}
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::pitchBendFactor(byte value) {
-	_pitchBendFactor = value;
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::priority(byte value) {
-	_priority = value;
-}
-
-void PcSpkDriver::MidiChannel_PcSpk::sysEx_customInstrument(uint32 type, const byte *instr) {
-	memcpy(_instrument, instr, sizeof(_instrument));
-}
-
-uint8 PcSpkDriver::getEffectModifier(uint16 level) {
-	uint8 base = level / 32;
-	uint8 index = level % 32;
-
-	if (index == 0)
-		return 0;
-
-	return (base * (index + 1)) >> 5;
-}
-
-int16 PcSpkDriver::getEffectModLevel(int16 level, int8 mod) {
-	if (!mod) {
-		return 0;
-	} else if (mod == 31) {
-		return level;
-	} else if (level < -63 || level > 63) {
-		return (mod * (level + 1)) >> 6;
-	} else if (mod < 0) {
-		if (level < 0)
-			return getEffectModifier(((-level) << 5) - mod);
-		else
-			return -getEffectModifier((level << 5) - mod);
-	} else {
-		if (level < 0)
-			return -getEffectModifier(((-level) << 5) + mod);
-		else
-			return getEffectModifier(((-level) << 5) + mod);
-	}
-}
-
-int16 PcSpkDriver::getRandScale(int16 input) {
-	if (_randBase & 1)
-		_randBase = (_randBase >> 1) ^ 0xB8;
-	else
-		_randBase >>= 1;
-
-	return (_randBase * input) >> 8;
-}
-
-void PcSpkDriver::setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data) {
-	def.phase = 0;
-	def.useModWheel = flags & 0x40;
-	env.loop = flags & 0x20;
-	def.type = flags & 0x1F;
-
-	env.modWheelSensitivity = 31;
-	if (def.useModWheel)
-		env.modWheelState = chan._modWheel >> 2;
-	else
-		env.modWheelState = 31;
-
-	switch (def.type) {
-	case 0:
-		env.maxLevel = 767;
-		env.startLevel = 383;
-		break;
-
-	case 1:
-		env.maxLevel = 31;
-		env.startLevel = 15;
-		break;
-
-	case 2:
-		env.maxLevel = 63;
-		env.startLevel = chan._out.unkB;
-		break;
-
-	case 3:
-		env.maxLevel = 63;
-		env.startLevel = chan._out.unkC;
-		break;
-
-	case 4:
-		env.maxLevel = 3;
-		env.startLevel = chan._instrument[4];
-		break;
-
-	case 5:
-		env.maxLevel = 62;
-		env.startLevel = 31;
-		env.modWheelState = 0;
-		break;
-
-	case 6:
-		env.maxLevel = 31;
-		env.startLevel = 0;
-		env.modWheelSensitivity = 0;
-		break;
-
-	default:
-		break;
-	}
-
-	startEffect(env, data);
-}
-
-void PcSpkDriver::startEffect(EffectEnvelope &env, const byte *data) {
-	env.state = 1;
-	env.currentLevel = 0;
-	env.modWheelLast = 31;
-	env.duration = data[0] * 63;
-
-	env.stateTargetLevels[0] = data[1];
-	env.stateTargetLevels[1] = data[3];
-	env.stateTargetLevels[2] = data[5];
-	env.stateTargetLevels[3] = data[6];
-
-	env.stateModWheelLevels[0] = data[2];
-	env.stateModWheelLevels[1] = data[4];
-	env.stateModWheelLevels[2] = 0;
-	env.stateModWheelLevels[3] = data[7];
-
-	initNextEnvelopeState(env);
-}
-
-void PcSpkDriver::initNextEnvelopeState(EffectEnvelope &env) {
-	uint8 lastState = env.state - 1;
-
-	uint16 stepCount = _effectEnvStepTable[getEffectModifier(((env.stateTargetLevels[lastState] & 0x7F) << 5) + env.modWheelSensitivity)];
-	if (env.stateTargetLevels[lastState] & 0x80)
-		stepCount = getRandScale(stepCount);
-	if (!stepCount)
-		stepCount = 1;
-
-	env.stateNumSteps = env.stateStepCounter = stepCount;
-
-	int16 totalChange = 0;
-	if (lastState != 2) {
-		totalChange = getEffectModLevel(env.maxLevel, (env.stateModWheelLevels[lastState] & 0x7F) - 31);
-		if (env.stateModWheelLevels[lastState] & 0x80)
-			totalChange = getRandScale(totalChange);
-
-		if (totalChange + env.startLevel > env.maxLevel)
-			totalChange = env.maxLevel - env.startLevel;
-		else if (totalChange + env.startLevel < 0)
-			totalChange = -env.startLevel;
-
-		totalChange -= env.currentLevel;
-	}
-
-	env.changePerStep = totalChange / stepCount;
-	if (totalChange < 0) {
-		totalChange = -totalChange;
-		env.dir = -1;
-	} else {
-		env.dir = 1;
-	}
-	env.changePerStepRem = totalChange % stepCount;
-	env.changeCountRem = 0;
-}
-
-void PcSpkDriver::updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def) {
-	if (advanceEffectEnvelope(env, def) & 1) {
-		switch (def.type) {
-		case 0: case 1:
-			chan._out.unk60 = def.phase << 4;
-			break;
-
-		case 2:
-			chan._out.unkB = (def.phase & 0xFF) + chan._instrument[1];
-			break;
-
-		case 3:
-			chan._out.unkC = (def.phase & 0xFF) + chan._instrument[2];
-			break;
-
-		case 4:
-			if ((chan._instrument[4] + (def.phase & 0xFF)) * 256 < ARRAYSIZE(_outInstrumentData))
-				chan._out.instrument = _outInstrumentData + (chan._instrument[4] + (def.phase & 0xFF)) * 256;
-			else
-				chan._out.instrument = 0;
-			break;
-
-		case 5:
-			env.modWheelState = (def.phase & 0xFF);
-			break;
-
-		case 6:
-			env.modWheelSensitivity = (def.phase & 0xFF);
-			break;
-
-		default:
-			break;
-		}
-	}
-}
-
-uint8 PcSpkDriver::advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def) {
-	if (env.duration != 0) {
-		env.duration -= 17;
-		if (env.duration <= 0) {
-			env.state = 0;
-			return 0;
-		}
-	}
-
-	uint8 changedFlags = 0;
-	int16 newLevel = env.currentLevel + env.changePerStep;
-	env.changeCountRem += env.changePerStepRem;
-	if (env.changeCountRem >= env.stateNumSteps) {
-		env.changeCountRem -= env.stateNumSteps;
-		newLevel += env.dir;
-	}
-
-	if (env.currentLevel != newLevel || env.modWheelLast != env.modWheelState) {
-		env.currentLevel = newLevel;
-		env.modWheelLast = env.modWheelState;
-
-		int16 newPhase = getEffectModLevel(newLevel, env.modWheelState);
-		if (def.phase != newPhase) {
-			changedFlags |= 1;
-			def.phase = newPhase;
-		}
-	}
-
-	--env.stateStepCounter;
-	if (!env.stateStepCounter) {
-		++env.state;
-		if (env.state > 4) {
-			if (env.loop) {
-				env.state = 1;
-				changedFlags |= 2;
-			} else {
-				env.state = 0;
-				return changedFlags;
-			}
-		}
-
-		initNextEnvelopeState(env);
-	}
-
-	return changedFlags;
-}
-
-const byte PcSpkDriver::_outInstrumentData[1024] = {
-	0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15,
-	0x18, 0x1B, 0x1E, 0x21, 0x24, 0x27, 0x2A, 0x2D,
-	0x30, 0x33, 0x36, 0x39, 0x3B, 0x3E, 0x41, 0x43,
-	0x46, 0x49, 0x4B, 0x4E, 0x50, 0x52, 0x55, 0x57,
-	0x59, 0x5B, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x67,
-	0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x71, 0x72, 0x74,
-	0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7B,
-	0x7C, 0x7D, 0x7D, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E,
-	0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7D, 0x7D,
-	0x7C, 0x7B, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76,
-	0x75, 0x74, 0x72, 0x71, 0x70, 0x6E, 0x6C, 0x6B,
-	0x69, 0x67, 0x66, 0x64, 0x62, 0x60, 0x5E, 0x5B,
-	0x59, 0x57, 0x55, 0x52, 0x50, 0x4E, 0x4B, 0x49,
-	0x46, 0x43, 0x41, 0x3E, 0x3B, 0x39, 0x36, 0x33,
-	0x30, 0x2D, 0x2A, 0x27, 0x24, 0x21, 0x1E, 0x1B,
-	0x18, 0x15, 0x12, 0x0F, 0x0C, 0x09, 0x06, 0x03,
-	0x00, 0xFD, 0xFA, 0xF7, 0xF4, 0xF1, 0xEE, 0xEB,
-	0xE8, 0xE5, 0xE2, 0xDF, 0xDC, 0xD9, 0xD6, 0xD3,
-	0xD0, 0xCD, 0xCA, 0xC7, 0xC5, 0xC2, 0xBF, 0xBD,
-	0xBA, 0xB7, 0xB5, 0xB2, 0xB0, 0xAE, 0xAB, 0xA9,
-	0xA7, 0xA5, 0xA2, 0xA0, 0x9E, 0x9C, 0x9A, 0x99,
-	0x97, 0x95, 0x94, 0x92, 0x90, 0x8F, 0x8E, 0x8C,
-	0x8B, 0x8A, 0x89, 0x88, 0x87, 0x86, 0x85, 0x85,
-	0x84, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82,
-	0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x83, 0x83,
-	0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A,
-	0x8B, 0x8C, 0x8E, 0x8F, 0x90, 0x92, 0x94, 0x95,
-	0x97, 0x99, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA5,
-	0xA7, 0xA9, 0xAB, 0xAE, 0xB0, 0xB2, 0xB5, 0xB7,
-	0xBA, 0xBD, 0xBF, 0xC2, 0xC5, 0xC7, 0xCA, 0xCD,
-	0xD0, 0xD3, 0xD6, 0xD9, 0xDC, 0xDF, 0xE2, 0xE5,
-	0xE8, 0xEB, 0xEE, 0xF1, 0xF4, 0xF7, 0xFA, 0xFD,
-	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-	0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
-	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
-	0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
-	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
-	0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
-	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
-	0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
-	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
-	0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
-	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
-	0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
-	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
-	0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
-	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
-	0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
-	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-	0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
-	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
-	0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
-	0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
-	0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
-	0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
-	0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
-	0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
-	0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
-	0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
-	0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
-	0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
-	0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
-	0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
-	0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
-	0x29, 0x23, 0xBE, 0x84, 0xE1, 0x6C, 0xD6, 0xAE,
-	0x52, 0x90, 0x49, 0xF1, 0xF1, 0xBB, 0xE9, 0xEB,
-	0xB3, 0xA6, 0xDB, 0x3C, 0x87, 0x0C, 0x3E, 0x99,
-	0x24, 0x5E, 0x0D, 0x1C, 0x06, 0xB7, 0x47, 0xDE,
-	0xB3, 0x12, 0x4D, 0xC8, 0x43, 0xBB, 0x8B, 0xA6,
-	0x1F, 0x03, 0x5A, 0x7D, 0x09, 0x38, 0x25, 0x1F,
-	0x5D, 0xD4, 0xCB, 0xFC, 0x96, 0xF5, 0x45, 0x3B,
-	0x13, 0x0D, 0x89, 0x0A, 0x1C, 0xDB, 0xAE, 0x32,
-	0x20, 0x9A, 0x50, 0xEE, 0x40, 0x78, 0x36, 0xFD,
-	0x12, 0x49, 0x32, 0xF6, 0x9E, 0x7D, 0x49, 0xDC,
-	0xAD, 0x4F, 0x14, 0xF2, 0x44, 0x40, 0x66, 0xD0,
-	0x6B, 0xC4, 0x30, 0xB7, 0x32, 0x3B, 0xA1, 0x22,
-	0xF6, 0x22, 0x91, 0x9D, 0xE1, 0x8B, 0x1F, 0xDA,
-	0xB0, 0xCA, 0x99, 0x02, 0xB9, 0x72, 0x9D, 0x49,
-	0x2C, 0x80, 0x7E, 0xC5, 0x99, 0xD5, 0xE9, 0x80,
-	0xB2, 0xEA, 0xC9, 0xCC, 0x53, 0xBF, 0x67, 0xD6,
-	0xBF, 0x14, 0xD6, 0x7E, 0x2D, 0xDC, 0x8E, 0x66,
-	0x83, 0xEF, 0x57, 0x49, 0x61, 0xFF, 0x69, 0x8F,
-	0x61, 0xCD, 0xD1, 0x1E, 0x9D, 0x9C, 0x16, 0x72,
-	0x72, 0xE6, 0x1D, 0xF0, 0x84, 0x4F, 0x4A, 0x77,
-	0x02, 0xD7, 0xE8, 0x39, 0x2C, 0x53, 0xCB, 0xC9,
-	0x12, 0x1E, 0x33, 0x74, 0x9E, 0x0C, 0xF4, 0xD5,
-	0xD4, 0x9F, 0xD4, 0xA4, 0x59, 0x7E, 0x35, 0xCF,
-	0x32, 0x22, 0xF4, 0xCC, 0xCF, 0xD3, 0x90, 0x2D,
-	0x48, 0xD3, 0x8F, 0x75, 0xE6, 0xD9, 0x1D, 0x2A,
-	0xE5, 0xC0, 0xF7, 0x2B, 0x78, 0x81, 0x87, 0x44,
-	0x0E, 0x5F, 0x50, 0x00, 0xD4, 0x61, 0x8D, 0xBE,
-	0x7B, 0x05, 0x15, 0x07, 0x3B, 0x33, 0x82, 0x1F,
-	0x18, 0x70, 0x92, 0xDA, 0x64, 0x54, 0xCE, 0xB1,
-	0x85, 0x3E, 0x69, 0x15, 0xF8, 0x46, 0x6A, 0x04,
-	0x96, 0x73, 0x0E, 0xD9, 0x16, 0x2F, 0x67, 0x68,
-	0xD4, 0xF7, 0x4A, 0x4A, 0xD0, 0x57, 0x68, 0x76
-};
-
-const byte PcSpkDriver::_outputTable1[] = {
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 1, 1, 1, 1,
-	1, 1, 1, 1, 1, 1, 1, 1,
-	2, 2, 2, 2, 2, 2, 2, 2,
-	2, 2, 2, 2, 3, 3, 3, 3,
-	3, 3, 3, 3, 3, 3, 3, 3,
-	4, 4, 4, 4, 4, 4, 4, 4,
-	4, 4, 4, 4, 5, 5, 5, 5,
-	5, 5, 5, 5, 5, 5, 5, 5,
-	6, 6, 6, 6, 6, 6, 6, 6,
-	6, 6, 6, 6, 7, 7, 7, 7,
-	7, 7, 7, 7, 7, 7, 7, 7,
-	7, 7, 7, 7, 7, 7, 7, 7
-};
-
-const byte PcSpkDriver::_outputTable2[] = {
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7,
-	8,  9, 10, 11,
-	0,  1,  2,  3,
-	4,  5,  6,  7
-};
-
-const uint16 PcSpkDriver::_effectEnvStepTable[] = {
-	  1,    2,    4,    5,
-	  6,    7,    8,    9,
-	 10,   12,   14,   16,
-	 18,   21,   24,   30,
-	 36,   50,   64,   82,
-	100,  136,  160,  192,
-	240,  276,  340,  460,
-	600,  860, 1200, 1600
-};
-
-const uint16 PcSpkDriver::_frequencyTable[] = {
-	0x8E84, 0x8E00, 0x8D7D, 0x8CFA,
-	0x8C78, 0x8BF7, 0x8B76, 0x8AF5,
-	0x8A75, 0x89F5, 0x8976, 0x88F7,
-	0x8879, 0x87FB, 0x877D, 0x8700,
-	0x8684, 0x8608, 0x858C, 0x8511,
-	0x8496, 0x841C, 0x83A2, 0x8328,
-	0x82AF, 0x8237, 0x81BF, 0x8147,
-	0x80D0, 0x8059, 0x7FE3, 0x7F6D,
-	0x7EF7, 0x7E82, 0x7E0D, 0x7D99,
-	0x7D25, 0x7CB2, 0x7C3F, 0x7BCC,
-	0x7B5A, 0x7AE8, 0x7A77, 0x7A06,
-	0x7995, 0x7925, 0x78B5, 0x7846,
-	0x77D7, 0x7768, 0x76FA, 0x768C,
-	0x761F, 0x75B2, 0x7545, 0x74D9,
-	0x746D, 0x7402, 0x7397, 0x732C,
-	0x72C2, 0x7258, 0x71EF, 0x7186,
-	0x711D, 0x70B5, 0x704D, 0x6FE5,
-	0x6F7E, 0x6F17, 0x6EB0, 0x6E4A,
-	0x6DE5, 0x6D7F, 0x6D1A, 0x6CB5,
-	0x6C51, 0x6BED, 0x6B8A, 0x6B26,
-	0x6AC4, 0x6A61, 0x69FF, 0x699D,
-	0x693C, 0x68DB, 0x687A, 0x681A,
-	0x67BA, 0x675A, 0x66FA, 0x669B,
-	0x663D, 0x65DF, 0x6581, 0x6523,
-	0x64C6, 0x6469, 0x640C, 0x63B0,
-	0x6354, 0x62F8, 0x629D, 0x6242,
-	0x61E7, 0x618D, 0x6133, 0x60D9,
-	0x6080, 0x6027, 0x5FCE, 0x5F76,
-	0x5F1E, 0x5EC6, 0x5E6E, 0x5E17,
-	0x5DC1, 0x5D6A, 0x5D14, 0x5CBE,
-	0x5C68, 0x5C13, 0x5BBE, 0x5B6A,
-	0x5B15, 0x5AC1, 0x5A6E, 0x5A1A,
-	0x59C7, 0x5974, 0x5922, 0x58CF,
-	0x587D, 0x582C, 0x57DA, 0x5789,
-	0x5739, 0x56E8, 0x5698, 0x5648,
-	0x55F9, 0x55A9, 0x555A, 0x550B,
-	0x54BD, 0x546F, 0x5421, 0x53D3,
-	0x5386, 0x5339, 0x52EC, 0x52A0,
-	0x5253, 0x5207, 0x51BC, 0x5170,
-	0x5125, 0x50DA, 0x5090, 0x5046,
-	0x4FFB, 0x4FB2, 0x4F68, 0x4F1F,
-	0x4ED6, 0x4E8D, 0x4E45, 0x4DFC,
-	0x4DB5, 0x4D6D, 0x4D25, 0x4CDE,
-	0x4C97, 0x4C51, 0x4C0A, 0x4BC4,
-	0x4B7E, 0x4B39, 0x4AF3, 0x4AAE,
-	0x4A69, 0x4A24, 0x49E0, 0x499C,
-	0x4958, 0x4914, 0x48D1, 0x488E,
-	0x484B, 0x4808, 0x47C6, 0x4783
-};
-
-} // End of namespace Scumm
diff --git a/engines/scumm/imuse/pcspk.h b/engines/scumm/imuse/pcspk.h
deleted file mode 100644
index 6a107e1..0000000
--- a/engines/scumm/imuse/pcspk.h
+++ /dev/null
@@ -1,161 +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 SCUMM_IMUSE_PCSPK_H
-#define SCUMM_IMUSE_PCSPK_H
-
-#include "audio/softsynth/emumidi.h"
-#include "audio/softsynth/pcspk.h"
-
-namespace Scumm {
-
-class PcSpkDriver : public MidiDriver_Emulated {
-public:
-	PcSpkDriver(Audio::Mixer *mixer);
-	~PcSpkDriver();
-
-	virtual int open();
-	virtual void close();
-
-	virtual void send(uint32 d);
-	virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr);
-
-	virtual MidiChannel *allocateChannel();
-	virtual MidiChannel *getPercussionChannel() { return 0; }
-
-	bool isStereo() const { return _pcSpk.isStereo(); }
-	int getRate() const { return _pcSpk.getRate(); }
-protected:
-	void generateSamples(int16 *buf, int len);
-	void onTimer();
-
-private:
-	Audio::PCSpeaker _pcSpk;
-	int _effectTimer;
-	uint8 _randBase;
-
-	void updateNote();
-	void output(uint16 out);
-
-	static uint8 getEffectModifier(uint16 level);
-	int16 getEffectModLevel(int16 level, int8 mod);
-	int16 getRandScale(int16 input);
-
-	struct EffectEnvelope {
-		uint8 state;
-		int16 currentLevel;
-		int16 duration;
-		int16 maxLevel;
-		int16 startLevel;
-		uint8 loop;
-		uint8 stateTargetLevels[4];
-		uint8 stateModWheelLevels[4];
-		uint8 modWheelSensitivity;
-		uint8 modWheelState;
-		uint8 modWheelLast;
-		int16 stateNumSteps;
-		int16 stateStepCounter;
-		int16 changePerStep;
-		int8 dir;
-		int16 changePerStepRem;
-		int16 changeCountRem;
-	};
-
-	struct EffectDefinition {
-		int16 phase;
-		uint8 type;
-		uint8 useModWheel;
-		EffectEnvelope *envelope;
-	};
-
-	struct OutputChannel {
-		uint8 active;
-		uint8 note;
-		uint8 sustainNoteOff;
-		uint8 length;
-		const uint8 *instrument;
-		uint8 unkA;
-		uint8 unkB;
-		uint8 unkC;
-		int16 unkE;
-		EffectEnvelope effectEnvelopeA;
-		EffectDefinition effectDefA;
-		EffectEnvelope effectEnvelopeB;
-		EffectDefinition effectDefB;
-		int16 unk60;
-	};
-
-	struct MidiChannel_PcSpk : public MidiChannel {
-		virtual MidiDriver *device();
-		virtual byte getNumber();
-		virtual void release();
-
-		virtual void send(uint32 b);
-		virtual void noteOff(byte note);
-		virtual void noteOn(byte note, byte velocity);
-		virtual void programChange(byte program);
-		virtual void pitchBend(int16 bend);
-		virtual void controlChange(byte control, byte value);
-		virtual void pitchBendFactor(byte value);
-		virtual void priority(byte value);
-		virtual void sysEx_customInstrument(uint32 type, const byte *instr);
-
-		void init(PcSpkDriver *owner, byte channel);
-		bool allocate();
-
-		PcSpkDriver *_owner;
-		bool _allocated;
-		byte _channel;
-
-		OutputChannel _out;
-		uint8 _instrument[23];
-		uint8 _programNr;
-		uint8 _priority;
-		uint8 _tl;
-		uint8 _modWheel;
-		uint8 _sustain;
-		uint8 _pitchBendFactor;
-		int16 _pitchBend;
-	};
-
-	void setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data);
-	void startEffect(EffectEnvelope &env, const byte *data);
-	void initNextEnvelopeState(EffectEnvelope &env);
-	void updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def);
-	uint8 advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def);
-
-	MidiChannel_PcSpk _channels[6];
-	MidiChannel_PcSpk *_activeChannel;
-
-	MidiChannel_PcSpk *_lastActiveChannel;
-	uint16 _lastActiveOut;
-
-	static const byte _outInstrumentData[1024];
-	static const byte _outputTable1[];
-	static const byte _outputTable2[];
-	static const uint16 _effectEnvStepTable[];
-	static const uint16 _frequencyTable[];
-};
-
-} // End of namespace Scumm
-
-#endif
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index bcc1eba..799176f 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -28,11 +28,12 @@ MODULE_OBJS := \
 	imuse/imuse_part.o \
 	imuse/imuse_player.o \
 	imuse/instrument.o \
-	imuse/mac_m68k.o \
-	imuse/pcspk.o \
 	imuse/sysex_samnmax.o \
 	imuse/sysex_scumm.o \
 	imuse/drivers/amiga.o \
+	imuse/drivers/fmtowns.o \
+	imuse/drivers/mac_m68k.o \
+	imuse/drivers/pcspk.o \
 	input.o \
 	midiparser_ro.o \
 	object.o \
diff --git a/engines/scumm/players/player_towns.h b/engines/scumm/players/player_towns.h
index ad51c3e..6283547 100644
--- a/engines/scumm/players/player_towns.h
+++ b/engines/scumm/players/player_towns.h
@@ -25,8 +25,8 @@
 
 #include "scumm/scumm.h"
 #include "scumm/imuse/imuse.h"
+#include "scumm/imuse/drivers/fmtowns.h"
 #include "audio/softsynth/fmtowns_pc98/towns_euphony.h"
-#include "audio/softsynth/fmtowns_pc98/towns_midi.h"
 
 namespace Scumm {
 
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 64f45c1..e3919ee 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -76,9 +76,10 @@
 #include "scumm/he/cup_player_he.h"
 #include "scumm/util.h"
 #include "scumm/verbs.h"
-#include "scumm/imuse/pcspk.h"
-#include "scumm/imuse/mac_m68k.h"
+#include "scumm/imuse/drivers/pcspk.h"
+#include "scumm/imuse/drivers/mac_m68k.h"
 #include "scumm/imuse/drivers/amiga.h"
+#include "scumm/imuse/drivers/fmtowns.h"
 
 #include "backends/audiocd/audiocd.h"
 
@@ -2001,7 +2002,9 @@ void ScummEngine::setupMusic(int midi) {
 			nativeMidiDriver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
 
 		if (!useOnlyNative) {
-			if (_sound->_musicType == MDT_ADLIB || _sound->_musicType == MDT_TOWNS || multi_midi) {
+			if (_sound->_musicType == MDT_TOWNS) {
+				adlibMidiDriver = new MidiDriver_TOWNS(_mixer);
+			} else if (_sound->_musicType == MDT_ADLIB || multi_midi) {
 				adlibMidiDriver = MidiDriver::createMidi(MidiDriver::detectDevice(_sound->_musicType == MDT_TOWNS ? MDT_TOWNS : MDT_ADLIB));
 				adlibMidiDriver->property(MidiDriver::PROP_OLD_ADLIB, (_game.features & GF_SMALL_HEADER) ? 1 : 0);
 				// Try to use OPL3 mode for Sam&Max when possible.


Commit: 4ee4d2d9aff62136c42004e4463b8d696571f9ec
    https://github.com/scummvm/scummvm/commit/4ee4d2d9aff62136c42004e4463b8d696571f9ec
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:48+02:00

Commit Message:
SCUMM: (FM-Towns Audio) remove TODO

The audio track is not broken. LEC simply didn't make good  euphony music tracks for the FM-Towns. There is nothing we can do about that.

The TODO implies that someone should mess around with the audio data (change the composition?).

Changed paths:
    engines/scumm/players/player_towns.cpp


diff --git a/engines/scumm/players/player_towns.cpp b/engines/scumm/players/player_towns.cpp
index 41bcf9e..c5f6b21 100644
--- a/engines/scumm/players/player_towns.cpp
+++ b/engines/scumm/players/player_towns.cpp
@@ -262,9 +262,6 @@ void Player_Towns_v1::startSound(int sound) {
 		// type (255 instead of 1).
 		// It doesn't sound great but we'll enable it to have music at all in this scene.
 		// See Trac#1873 and Trac#10561.
-		//
-		// TODO: Check if playback of this can be improved somehow (maybe there's something else
-		// off with the data for which we can add a workaround?).
 		playEuphonyTrack(sound, ptr + 6);
 
 	} else if (type == 2) {


Commit: 19643175a8fa605f8703c61c0060bbcaf69302ec
    https://github.com/scummvm/scummvm/commit/19643175a8fa605f8703c61c0060bbcaf69302ec
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:52+02:00

Commit Message:
SCUMM: limit 'Unrecognized base tag' warning to valid cases

This warning will not only show up if a tag is actually unrecognized but also in cases where the tag is recognized, but the resource size is 0. This happens quite a lot in the Amiga version of MI2 with 'SOU ' tags.

Changed paths:
    engines/scumm/sound.cpp


diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index c90b13c..14bf1c3 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -1335,7 +1335,8 @@ int ScummEngine::readSoundResource(ResId idx) {
 		}
 	}
 
-	warning("Unrecognized base tag 0x%08x in sound %d", basetag, idx);
+	if (total_size)
+		warning("Unrecognized base tag 0x%08x in sound %d", basetag, idx);
 	_res->_types[rtSound][idx]._roomoffs = RES_INVALID_OFFSET;
 	return 0;
 }


Commit: 6694cdcd7389f7e5452733c8bfcfda9585223d3d
    https://github.com/scummvm/scummvm/commit/6694cdcd7389f7e5452733c8bfcfda9585223d3d
Author: athrxx (athrxx at scummvm.org)
Date: 2019-07-14T21:45:56+02:00

Commit Message:
SCUMM: update news (Amiga iMuse support)

Changed paths:
    NEWS.md


diff --git a/NEWS.md b/NEWS.md
index c2decb6..1459ae6 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -61,6 +61,7 @@ For a more comprehensive changelog of the latest experimental code, see:
      for this to take effect when using compressed audio.
    - Fixed an issue in the wig maker room in the German version of SPY Fox 3: Operation Ozone
      which makes the game completable.
+   - Added sound driver for the Amiga versions of Monkey Island 2 and Indiana Jones and the Fate of Atlantis
 
  Sherlock:
    - Fixed crash in Spanish version talking to lady in Tailor shop





More information about the Scummvm-git-logs mailing list