[Scummvm-cvs-logs] scummvm master -> 385bd7c6a045b1a4c7b64ebef4471805020bdadd

DrMcCoy drmccoy at drmccoy.de
Mon Jun 11 05:27:28 CEST 2012


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

Summary:
03ef6689c0 GOB: Rewrite the AdLib players
8548dea13d GOB: Hook up the MDY player in Fascination
a64e8a6d30 GOB: Hook up the MDY player in Geisha
385bd7c6a0 NEWS: Mention Gob news for 1.5.0


Commit: 03ef6689c015742c192d5d92d936e60d638caa1c
    https://github.com/scummvm/scummvm/commit/03ef6689c015742c192d5d92d936e60d638caa1c
Author: Sven Hesse (drmccoy at users.sourceforge.net)
Date: 2012-06-10T20:18:06-07:00

Commit Message:
GOB: Rewrite the AdLib players

This is a complete rewrite of the AdLib players for ADL and MDY/TBR
files in the Gob engine.

Major changes
1) The AdLib base class is now completely separated from all file
   format code and can theoretically be used by any OPL2-based
   format (within reason)
2) The new code is far better documented and more readable
3) The MDY player now actually works. The MDY/TBR format is
   in reality the MUS/SND format created by AdLib as a simpler
   alternative to the ROL format
4) Since the MAME emulator is quite buggy and leads to noticable
   wrong percussion in the Gobliins 2 title music, the new AdLib
   player will try to create a DOSBox OPL. If it's not compiled in,
   or if the user configured opl_driver to "mame", it will print
   out appropriate warnings.

Changed paths:
  A engines/gob/sound/adlplayer.cpp
  A engines/gob/sound/adlplayer.h
  A engines/gob/sound/musplayer.cpp
  A engines/gob/sound/musplayer.h
    engines/gob/module.mk
    engines/gob/sound/adlib.cpp
    engines/gob/sound/adlib.h
    engines/gob/sound/sound.cpp
    engines/gob/sound/sound.h



diff --git a/engines/gob/module.mk b/engines/gob/module.mk
index b9680fa..7c5d7de 100644
--- a/engines/gob/module.mk
+++ b/engines/gob/module.mk
@@ -103,6 +103,8 @@ MODULE_OBJS := \
 	sound/sounddesc.o \
 	sound/pcspeaker.o \
 	sound/adlib.o \
+	sound/musplayer.o \
+	sound/adlplayer.o \
 	sound/infogrames.o \
 	sound/protracker.o \
 	sound/soundmixer.o \
diff --git a/engines/gob/sound/adlib.cpp b/engines/gob/sound/adlib.cpp
index f1ab2a2..3f46f6c 100644
--- a/engines/gob/sound/adlib.cpp
+++ b/engines/gob/sound/adlib.cpp
@@ -20,771 +20,615 @@
  *
  */
 
-#include "common/debug.h"
-#include "common/file.h"
-#include "common/endian.h"
+#include "common/util.h"
 #include "common/textconsole.h"
+#include "common/debug.h"
+#include "common/config-manager.h"
+
+#include "audio/fmopl.h"
 
 #include "gob/gob.h"
 #include "gob/sound/adlib.h"
 
 namespace Gob {
 
-const unsigned char AdLib::_operators[] = {0, 1, 2, 8, 9, 10, 16, 17, 18};
-const unsigned char AdLib::_volRegNums[] = {
-	3,  4,  5,
-	11, 12, 13,
-	19, 20, 21
+static const int kPitchTom        = 24;
+static const int kPitchTomToSnare =  7;
+static const int kPitchSnareDrum  = kPitchTom + kPitchTomToSnare;
+
+
+// Is the operator a modulator (0) or a carrier (1)?
+const uint8 AdLib::kOperatorType[kOperatorCount] = {
+	0, 0, 0, 1, 1, 1,
+	0, 0, 0, 1, 1, 1,
+	0, 0, 0, 1, 1, 1
+};
+
+// Operator number to register offset on the OPL
+const uint8 AdLib::kOperatorOffset[kOperatorCount] = {
+	 0,  1,  2,  3,  4,  5,
+	 8,  9, 10, 11, 12, 13,
+	16, 17, 18, 19, 20, 21
+};
+
+// For each operator, the voice it belongs to
+const uint8 AdLib::kOperatorVoice[kOperatorCount] = {
+	0, 1, 2,
+	0, 1, 2,
+	3, 4, 5,
+	3, 4, 5,
+	6, 7, 8,
+	6, 7, 8,
+};
+
+// Voice to operator set, for the 9 melodyvoices (only 6 useable in percussion mode)
+const uint8 AdLib::kVoiceMelodyOperator[kOperatorsPerVoice][kMelodyVoiceCount] = {
+	{0, 1, 2, 6,  7,  8, 12, 13, 14},
+	{3, 4, 5, 9, 10, 11, 15, 16, 17}
 };
 
-AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer) {
-	init();
+// Voice to operator set, for the 5 percussion voices (only useable in percussion mode)
+const uint8 AdLib::kVoicePercussionOperator[kOperatorsPerVoice][kPercussionVoiceCount] = {
+	{12, 16, 14, 17, 13},
+	{15,  0,  0,  0,  0}
+};
+
+// Mask bits to set each percussion instrument on/off
+const byte AdLib::kPercussionMasks[kPercussionVoiceCount] = {0x10, 0x08, 0x04, 0x02, 0x01};
+
+// Default instrument presets
+const uint16 AdLib::kPianoParams   [kOperatorsPerVoice][kParamCount] = {
+	{ 1,  1,  3, 15,  5,  0,  1,  3, 15,  0,  0,  0,  1,  0},
+	{ 0,  1,  1, 15,  7,  0,  2,  4,  0,  0,  0,  1,  0,  0}  };
+const uint16 AdLib::kBaseDrumParams[kOperatorsPerVoice][kParamCount] = {
+	{ 0,  0,  0, 10,  4,  0,  8, 12, 11,  0,  0,  0,  1,  0 },
+	{ 0,  0,  0, 13,  4,  0,  6, 15,  0,  0,  0,  0,  1,  0 } };
+const uint16 AdLib::kSnareDrumParams[kParamCount] = {
+	  0, 12,  0, 15, 11,  0,  8,  5,  0,  0,  0,  0,  0,  0   };
+const uint16 AdLib::kTomParams      [kParamCount] = {
+	  0,  4,  0, 15, 11,  0,  7,  5,  0,  0,  0,  0,  0,  0   };
+const uint16 AdLib::kCymbalParams   [kParamCount] = {
+	  0,  1,  0, 15, 11,  0,  5,  5,  0,  0,  0,  0,  0,  0   };
+const uint16 AdLib::kHihatParams    [kParamCount] = {
+	  0,  1,  0, 15, 11,  0,  7,  5,  0,  0,  0,  0,  0,  0   };
+
+
+AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer), _opl(0),
+	_toPoll(0), _repCount(0), _first(true), _playing(false), _ended(true) {
+
+	_rate = _mixer->getOutputRate();
+
+	createOPL();
+	initOPL();
+
+	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle,
+			this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
 }
 
 AdLib::~AdLib() {
-	Common::StackLock slock(_mutex);
-
 	_mixer->stopHandle(_handle);
-	OPLDestroy(_opl);
-	if (_data && _freeData)
-		delete[] _data;
+
+	delete _opl;
 }
 
-void AdLib::init() {
-	_index = -1;
-	_data = 0;
-	_playPos = 0;
-	_dataSize = 0;
+// Creates the OPL. Try to use the DOSBox emulator, unless that one is not compiled in,
+// or the user explicitly wants the MAME emulator. The MAME one is slightly buggy, leading
+// to some wrong sounds, especially noticeable in the title music of Gobliins 2, so we
+// really don't want to use it, if we can help it.
+void AdLib::createOPL() {
+	Common::String oplDriver = ConfMan.get("opl_driver");
 
-	_rate = _mixer->getOutputRate();
+	if (oplDriver.empty() || (oplDriver == "auto") || (OPL::Config::parse(oplDriver) == -1)) {
+		// User has selected OPL driver auto detection or an invalid OPL driver.
+		// Set it to our preferred driver (DOSBox), if we can.
 
-	_opl = makeAdLibOPL(_rate);
+		if (OPL::Config::parse("db") <= 0) {
+			warning("The DOSBox AdLib emulator is not compiled in. Please keep in mind that the MAME one is buggy");
+		} else
+			oplDriver = "db";
 
-	_first = true;
-	_ended = false;
-	_playing = false;
+	} else if (oplDriver == "mame") {
+		// User has selected the MAME OPL driver. It is buggy, so warn the user about that.
 
-	_freeData = false;
-
-	_repCount = -1;
-	_samplesTillPoll = 0;
+		warning("You have selected the MAME AdLib emulator. It is buggy; AdLib music might be slightly glitchy now");
+	}
 
-	for (int i = 0; i < 16; i ++)
-		_pollNotes[i] = 0;
-	setFreqs();
+	_opl = OPL::Config::create(OPL::Config::parse(oplDriver), OPL::Config::kOpl2);
+	if (!_opl || !_opl->init(_rate)) {
+		delete _opl;
 
-	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle,
-			this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+		error("Could not create an AdLib emulator");
+	}
 }
 
 int AdLib::readBuffer(int16 *buffer, const int numSamples) {
 	Common::StackLock slock(_mutex);
-	int samples;
-	int render;
 
-	if (!_playing || (numSamples < 0)) {
-		memset(buffer, 0, numSamples * sizeof(int16));
-		return numSamples;
-	}
-	if (_first) {
+	// Nothing to do, fill with silence
+	if (!_playing) {
 		memset(buffer, 0, numSamples * sizeof(int16));
-		pollMusic();
 		return numSamples;
 	}
 
-	samples = numSamples;
+	// Read samples from the OPL, polling in more music when necessary
+	uint32 samples = numSamples;
 	while (samples && _playing) {
-		if (_samplesTillPoll) {
-			render = (samples > _samplesTillPoll) ?  (_samplesTillPoll) : (samples);
+		if (_toPoll) {
+			const uint32 render = MIN(samples, _toPoll);
+
+			_opl->readBuffer(buffer, render);
+
+			buffer  += render;
 			samples -= render;
-			_samplesTillPoll -= render;
-			YM3812UpdateOne(_opl, buffer, render);
-			buffer += render;
+			_toPoll -= render;
+
 		} else {
-			pollMusic();
+			// Song ended, fill the rest with silence
 			if (_ended) {
 				memset(buffer, 0, samples * sizeof(int16));
 				samples = 0;
+				break;
 			}
+
+			// Poll more music
+			_toPoll = pollMusic(_first);
+			_first  = false;
 		}
 	}
 
+	// Song ended, loop if requested
 	if (_ended) {
-		_first = true;
-		_ended = false;
+		_toPoll = 0;
 
-		rewind();
+		// _repCount == 0: No looping (anymore); _repCount < 0: Infinite looping
+		if (_repCount != 0) {
+			if (_repCount > 0)
+				_repCount--;
+
+			_first  = true;
+			_ended  = false;
 
-		_samplesTillPoll = 0;
-		if (_repCount == -1) {
-			reset();
-			setVoices();
-		} else if (_repCount > 0) {
-			_repCount--;
 			reset();
-			setVoices();
-		}
-		else
+			rewind();
+		} else
 			_playing = false;
 	}
-	return numSamples;
-}
-
-void AdLib::writeOPL(byte reg, byte val) {
-	debugC(6, kDebugSound, "AdLib::writeOPL (%02X, %02X)", reg, val);
-	OPLWriteReg(_opl, reg, val);
-}
 
-void AdLib::setFreqs() {
-	byte lin;
-	byte col;
-	long val = 0;
-
-	// Run through the 11 channels
-	for (lin = 0; lin < 11; lin ++) {
-		_notes[lin] = 0;
-		_notCol[lin] = 0;
-		_notLin[lin] = 0;
-		_notOn[lin] = false;
-	}
-
-	// Run through the 25 lines
-	for (lin = 0; lin < 25; lin ++) {
-		// Run through the 12 columns
-		for (col = 0; col < 12; col ++) {
-			if (!col)
-				val = (((0x2710L + lin * 0x18) * 0xCB78 / 0x3D090) << 0xE) *
-					9 / 0x1B503;
-			_freqs[lin][col] = (short)((val + 4) >> 3);
-			val = val * 0x6A / 0x64;
-		}
-	}
+	return numSamples;
 }
 
-void AdLib::reset() {
-	_first = true;
-	OPLResetChip(_opl);
-	_samplesTillPoll = 0;
-
-	setFreqs();
-	// Set frequencies and octave to 0; notes off
-	for (int i = 0; i < 9; i++) {
-		writeOPL(0xA0 | i, 0);
-		writeOPL(0xB0 | i, 0);
-		writeOPL(0xE0 | _operators[i]     , 0);
-		writeOPL(0xE0 |(_operators[i] + 3), 0);
-	}
-
-	// Authorize the control of the waveformes
-	writeOPL(0x01, 0x20);
-}
-
-void AdLib::setKey(byte voice, byte note, bool on, bool spec) {
-	short freq = 0;
-	short octa = 0;
-
-	// Instruction AX
-	if (spec) {
-		// 0x7F donne 0x16B;
-		//     7F
-		// <<   7 =  3F80
-		// + E000 = 11F80
-		// & FFFF =  1F80
-		// *   19 = 31380
-		// / 2000 =    18 => Ligne 18h, colonne  0 => freq 16B
-
-		// 0x3A donne 0x2AF;
-		//     3A
-		// <<   7 =  1D00
-		// + E000 =  FD00 negatif
-		// *   19 = xB500
-		// / 2000 =    -2 => Ligne 17h, colonne -1
-
-		//     2E
-		// <<   7 =  1700
-		// + E000 =  F700 negatif
-		// *   19 = x1F00
-		// / 2000 =
-		short a;
-		short lin;
-		short col;
-
-		a = (note << 7) + 0xE000; // Volontairement tronque
-		a = (short)((long)a * 25 / 0x2000);
-		if (a < 0) {
-			col = - ((24 - a) / 25);
-			lin = (-a % 25);
-			if (lin)
-				lin = 25 - lin;
-		}
-		else {
-			col = a / 25;
-			lin = a % 25;
-		}
-
-		_notCol[voice] = col;
-		_notLin[voice] = lin;
-		note = _notes[voice];
-	}
-	// Instructions 0X 9X 8X
-	else {
-		note -= 12;
-		_notOn[voice] = on;
-	}
-
-	_notes[voice] = note;
-	note += _notCol[voice];
-	note = MIN((byte) 0x5F, note);
-	octa = note / 12;
-	freq = _freqs[_notLin[voice]][note - octa * 12];
-
-	writeOPL(0xA0 + voice,  freq & 0xFF);
-	writeOPL(0xB0 + voice, (freq >> 8) | (octa << 2) | (0x20 * (on ? 1 : 0)));
-
-	if (!freq)
-		warning("AdLib::setKey Voice %d, note %02X unknown", voice, note);
+bool AdLib::isStereo() const {
+	return _opl->isStereo();
 }
 
-void AdLib::setVolume(byte voice, byte volume) {
-	debugC(6, kDebugSound, "AdLib::setVolume(%d, %d)", voice, volume);
-	//assert(voice >= 0 && voice <= 9);
-	volume = 0x3F - ((volume * 0x7E) + 0x7F) / 0xFE;
-	writeOPL(0x40 + _volRegNums[voice], volume);
+bool AdLib::endOfData() const {
+	return !_playing;
 }
 
-void AdLib::pollMusic() {
-	if ((_playPos > (_data + _dataSize)) && (_dataSize != 0xFFFFFFFF)) {
-		_ended = true;
-		return;
-	}
-
-	interpret();
+bool AdLib::endOfStream() const {
+	return false;
 }
 
-void AdLib::unload() {
-	_playing = false;
-	_index = -1;
-
-	if (_data && _freeData)
-		delete[] _data;
-
-	_freeData = false;
+int AdLib::getRate() const {
+	return _rate;
 }
 
 bool AdLib::isPlaying() const {
 	return _playing;
 }
 
-bool AdLib::getRepeating() const {
-	return _repCount != 0;
+int32 AdLib::getRepeating() const {
+	Common::StackLock slock(_mutex);
+
+	return _repCount;
 }
 
 void AdLib::setRepeating(int32 repCount) {
+	Common::StackLock slock(_mutex);
+
 	_repCount = repCount;
 }
 
-int AdLib::getIndex() const {
-	return _index;
+uint32 AdLib::getSamplesPerSecond() const {
+	return _rate * (isStereo() ? 2 : 1);
 }
 
 void AdLib::startPlay() {
-	if (_data) _playing = true;
+	Common::StackLock slock(_mutex);
+
+	_playing = true;
+	_ended   = false;
+	_first   = true;
+
+	reset();
+	rewind();
 }
 
 void AdLib::stopPlay() {
 	Common::StackLock slock(_mutex);
+
+	end(true);
+
 	_playing = false;
 }
 
-ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer) {
-}
+void AdLib::writeOPL(byte reg, byte val) {
+	debugC(6, kDebugSound, "AdLib::writeOPL (%02X, %02X)", reg, val);
 
-ADLPlayer::~ADLPlayer() {
+	_opl->writeReg(reg, val);
 }
 
-bool ADLPlayer::load(const char *fileName) {
-	Common::File song;
+void AdLib::reset() {
+	allOff();
+	initOPL();
+}
 
-	unload();
-	song.open(fileName);
-	if (!song.isOpen())
-		return false;
+void AdLib::allOff() {
+	// NOTE: Explicit casts are necessary, because of 5.16 paragraph 4 of the C++ standard
+	int numVoices = isPercussionMode() ? (int)kMaxVoiceCount : (int)kMelodyVoiceCount;
 
-	_freeData = true;
-	_dataSize = song.size();
-	_data = new byte[_dataSize];
-	song.read(_data, _dataSize);
-	song.close();
+	for (int i = 0; i < numVoices; i++)
+		noteOff(i);
+}
 
+void AdLib::end(bool killRepeat) {
 	reset();
-	setVoices();
-	_playPos = _data + 3 + (_data[1] + 1) * 0x38;
 
-	return true;
+	_ended = true;
+
+	if (killRepeat)
+		_repCount = 0;
 }
 
-bool ADLPlayer::load(byte *data, uint32 size, int index) {
-	unload();
-	_repCount = 0;
+void AdLib::initOPL() {
+	_tremoloDepth = false;
+	_vibratoDepth = false;
+	_keySplit     = false;
 
-	_dataSize = size;
-	_data = data;
-	_index = index;
+	_enableWaveSelect = true;
 
-	reset();
-	setVoices();
-	_playPos = _data + 3 + (_data[1] + 1) * 0x38;
+	for (int i = 0; i < kMaxVoiceCount; i++) {
+		_voiceNote[i] = 0;
+		_voiceOn  [i] = 0;
+	}
+
+	_opl->reset();
+
+	initOperatorVolumes();
+	initFreqs();
+
+	setPercussionMode(false);
+
+	setTremoloDepth(false);
+	setVibratoDepth(false);
+	setKeySplit(false);
 
-	return true;
+	for(int i = 0; i < kMelodyVoiceCount; i++)
+		voiceOff(i);
+
+	setPitchRange(1);
+
+	enableWaveSelect(true);
 }
 
-void ADLPlayer::unload() {
-	AdLib::unload();
+bool AdLib::isPercussionMode() const {
+	return _percussionMode;
 }
 
-void ADLPlayer::interpret() {
-	unsigned char instr;
-	byte channel;
-	byte note;
-	byte volume;
-	uint16 tempo;
+void AdLib::setPercussionMode(bool percussion) {
+	if (percussion) {
+		voiceOff(kVoiceBaseDrum);
+		voiceOff(kVoiceSnareDrum);
+		voiceOff(kVoiceTom);
 
-	// First tempo, we'll ignore it...
-	if (_first) {
-		tempo = *(_playPos++);
-		// Tempo on 2 bytes
-		if (tempo & 0x80)
-			tempo = ((tempo & 3) << 8) | *(_playPos++);
-	}
-	_first = false;
-
-	// Instruction
-	instr = *(_playPos++);
-	channel = instr & 0x0F;
-
-	switch (instr & 0xF0) {
-		// Note on + Volume
-		case 0x00:
-			note = *(_playPos++);
-			_pollNotes[channel] = note;
-			setVolume(channel, *(_playPos++));
-			setKey(channel, note, true, false);
-			break;
-		// Note on
-		case 0x90:
-			note = *(_playPos++);
-			_pollNotes[channel] = note;
-			setKey(channel, note, true, false);
-			break;
-		// Last note off
-		case 0x80:
-			note = _pollNotes[channel];
-			setKey(channel, note, false, false);
-			break;
-		// Frequency on/off
-		case 0xA0:
-			note = *(_playPos++);
-			setKey(channel, note, _notOn[channel], true);
-			break;
-		// Volume
-		case 0xB0:
-			volume = *(_playPos++);
-			setVolume(channel, volume);
-			break;
-		// Program change
-		case 0xC0:
-			setVoice(channel, *(_playPos++), false);
-			break;
-		// Special
-		case 0xF0:
-			switch (instr & 0x0F) {
-			case 0xF: // End instruction
-				_ended = true;
-				_samplesTillPoll = 0;
-				return;
-			default:
-				warning("ADLPlayer: Unknown special command %X, stopping playback",
-						instr & 0x0F);
-				_repCount = 0;
-				_ended = true;
-				break;
-			}
-			break;
-		default:
-			warning("ADLPlayer: Unknown command %X, stopping playback",
-					instr & 0xF0);
-			_repCount = 0;
-			_ended = true;
-			break;
+		/* set the frequency for the last 4 percussion voices: */
+		setFreq(kVoiceTom, kPitchTom, 0);
+		setFreq(kVoiceSnareDrum, kPitchSnareDrum, 0);
 	}
 
-	// Temporization
-	tempo = *(_playPos++);
-	// End tempo
-	if (tempo == 0xFF) {
-		_ended = true;
-		return;
-	}
-	// Tempo on 2 bytes
-	if (tempo & 0x80)
-		tempo = ((tempo & 3) << 8) | *(_playPos++);
-	if (!tempo)
-		tempo ++;
+	_percussionMode = percussion;
+	_percussionBits = 0;
 
-	_samplesTillPoll = tempo * (_rate / 1000);
+	initOperatorParams();
+	writeTremoloVibratoDepthPercMode();
 }
 
-void ADLPlayer::reset() {
-	AdLib::reset();
+void AdLib::enableWaveSelect(bool enable) {
+	_enableWaveSelect = enable;
+
+	for (int i = 0; i < kOperatorCount; i++)
+		writeOPL(0xE0 + kOperatorOffset[i], 0);
+
+	writeOPL(0x011, _enableWaveSelect ? 0x20 : 0);
 }
 
-void ADLPlayer::rewind() {
-	_playPos = _data + 3 + (_data[1] + 1) * 0x38;
+void AdLib::setPitchRange(uint8 range) {
+	_pitchRange     = CLIP<uint8>(range, 0, 12);
+	_pitchRangeStep = _pitchRange * kPitchStepCount;
 }
 
-void ADLPlayer::setVoices() {
-	// Definitions of the 9 instruments
-	for (int i = 0; i < 9; i++)
-		setVoice(i, i, true);
+void AdLib::setTremoloDepth(bool tremoloDepth) {
+	_tremoloDepth = tremoloDepth;
+
+	writeTremoloVibratoDepthPercMode();
 }
 
-void ADLPlayer::setVoice(byte voice, byte instr, bool set) {
-	uint16 strct[27];
-	byte channel;
-	byte *dataPtr;
+void AdLib::setVibratoDepth(bool vibratoDepth) {
+	_vibratoDepth = vibratoDepth;
 
-	// i = 0 :  0  1  2  3  4  5  6  7  8  9 10 11 12 26
-	// i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27
-	for (int i = 0; i < 2; i++) {
-		dataPtr = _data + 3 + instr * 0x38 + i * 0x1A;
-		for (int j = 0; j < 27; j++) {
-			strct[j] = READ_LE_UINT16(dataPtr);
-			dataPtr += 2;
-		}
-		channel = _operators[voice] + i * 3;
-		writeOPL(0xBD, 0x00);
-		writeOPL(0x08, 0x00);
-		writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F));
-		if (!i)
-			writeOPL(0xC0 | voice,
-					((strct[2] & 7) << 1) | (1 - (strct[12] & 1)));
-		writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF));
-		writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF));
-		writeOPL(0x20 | channel, ((strct[9] & 1) << 7) |
-			((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) |
-			((strct[11] & 1) << 4) |  (strct[1] & 0xF));
-		if (!i)
-			writeOPL(0xE0 | channel, (strct[26] & 3));
-		else
-			writeOPL(0xE0 | channel, (strct[14] & 3));
-		if (i && set)
-			writeOPL(0x40 | channel, 0);
+	writeTremoloVibratoDepthPercMode();
+}
+
+void AdLib::setKeySplit(bool keySplit) {
+	_keySplit = keySplit;
+
+	writeKeySplit();
+}
+
+void AdLib::setVoiceTimbre(uint8 voice, const uint16 *params) {
+	const uint16 *params0 = params;
+	const uint16 *params1 = params + kParamCount - 1;
+	const uint16 *waves   = params + 2 * (kParamCount - 1);
+
+	const int voicePerc = voice - kVoiceBaseDrum;
+
+	if (!isPercussionMode() || (voice < kVoiceBaseDrum)) {
+		setOperatorParams(kVoiceMelodyOperator[0][voice], params0, waves[0]);
+		setOperatorParams(kVoiceMelodyOperator[1][voice], params1, waves[1]);
+	} else if (voice == kVoiceBaseDrum) {
+		setOperatorParams(kVoicePercussionOperator[0][voicePerc], params0, waves[0]);
+		setOperatorParams(kVoicePercussionOperator[1][voicePerc], params1, waves[1]);
+	} else {
+		setOperatorParams(kVoicePercussionOperator[0][voicePerc], params0, waves[0]);
 	}
 }
 
+void AdLib::setVoiceVolume(uint8 voice, uint8 volume) {
+	int oper;
+
+	const int voicePerc = voice - kVoiceBaseDrum;
+
+	if (!isPercussionMode() || (voice < kVoiceBaseDrum))
+		oper = kVoiceMelodyOperator[1][ voice];
+	else
+		oper = kVoicePercussionOperator[voice == kVoiceBaseDrum ? 1 : 0][voicePerc];
 
-MDYPlayer::MDYPlayer(Audio::Mixer &mixer) : AdLib(mixer) {
-	init();
+	_operatorVolume[oper] = MIN<uint8>(volume, kMaxVolume);
+	writeKeyScaleLevelVolume(oper);
 }
 
-MDYPlayer::~MDYPlayer() {
+void AdLib::bendVoicePitch(uint8 voice, uint16 pitchBend) {
+	if (isPercussionMode() && (voice > kVoiceBaseDrum))
+		return;
+
+	changePitch(voice, MIN<uint16>(pitchBend, kMaxPitch));
+	setFreq(voice, _voiceNote[voice], _voiceOn[voice]);
 }
 
-void MDYPlayer::init() {
-	_soundMode = 0;
+void AdLib::noteOn(uint8 voice, uint8 note) {
+	note = MAX<int>(0, note - (kStandardMidC - kOPLMidC));
+
+	if (isPercussionMode() && (voice >= kVoiceBaseDrum)) {
+
+		if        (voice == kVoiceBaseDrum) {
+			setFreq(kVoiceBaseDrum , note                   , false);
+		} else if (voice == kVoiceTom) {
+			setFreq(kVoiceTom      , note                   , false);
+			setFreq(kVoiceSnareDrum, note + kPitchTomToSnare, false);
+		}
+
+		_percussionBits |= kPercussionMasks[voice - kVoiceBaseDrum];
+		writeTremoloVibratoDepthPercMode();
 
-	_timbres = 0;
-	_tbrCount = 0;
-	_tbrStart = 0;
-	_timbresSize = 0;
+	} else
+		setFreq(voice, note, true);
 }
 
-bool MDYPlayer::loadMDY(Common::SeekableReadStream &stream) {
-	unloadMDY();
+void AdLib::noteOff(uint8 voice) {
+	if (isPercussionMode() && (voice >= kVoiceBaseDrum)) {
+		_percussionBits &= ~kPercussionMasks[voice - kVoiceBaseDrum];
+		writeTremoloVibratoDepthPercMode();
+	} else
+		setFreq(voice, _voiceNote[voice], false);
+}
 
-	_freeData = true;
+void AdLib::writeKeyScaleLevelVolume(uint8 oper) {
+	uint16 volume = 0;
 
-	byte mdyHeader[70];
-	stream.read(mdyHeader, 70);
+	volume = (63 - (_operatorParams[oper][kParamLevel] & 0x3F)) * _operatorVolume[oper];
+	volume = 63 - ((2 * volume + kMaxVolume) / (2 * kMaxVolume));
 
-	_tickBeat = mdyHeader[36];
-	_beatMeasure = mdyHeader[37];
-	_totalTick = mdyHeader[38] + (mdyHeader[39] << 8) + (mdyHeader[40] << 16) + (mdyHeader[41] << 24);
-	_dataSize = mdyHeader[42] + (mdyHeader[43] << 8) + (mdyHeader[44] << 16) + (mdyHeader[45] << 24);
-	_nrCommand = mdyHeader[46] + (mdyHeader[47] << 8) + (mdyHeader[48] << 16) + (mdyHeader[49] << 24);
-// _soundMode is either 0 (melodic) or 1 (percussive)
-	_soundMode = mdyHeader[58];
-	assert((_soundMode == 0) || (_soundMode == 1));
+	uint8 keyScale = _operatorParams[oper][kParamKeyScaleLevel] << 6;
 
-	_pitchBendRangeStep = 25*mdyHeader[59];
-	_basicTempo = mdyHeader[60] + (mdyHeader[61] << 8);
+	writeOPL(0x40 + kOperatorOffset[oper], volume | keyScale);
+}
 
-	if (_pitchBendRangeStep < 25)
-		_pitchBendRangeStep = 25;
-	else if (_pitchBendRangeStep > 300)
-		_pitchBendRangeStep = 300;
+void AdLib::writeKeySplit() {
+	writeOPL(0x08, _keySplit ? 0x40 : 0);
+}
 
-	_data = new byte[_dataSize];
-	stream.read(_data, _dataSize);
+void AdLib::writeFeedbackFM(uint8 oper) {
+	if (kOperatorType[oper] == 1)
+		return;
 
-	reset();
-	_playPos = _data;
+	uint8 value = 0;
+
+	value |= _operatorParams[oper][kParamFeedback] << 1;
+	value |= _operatorParams[oper][kParamFM] ? 0 : 1;
 
-	return true;
+	writeOPL(0xC0 + kOperatorVoice[oper], value);
 }
 
-bool MDYPlayer::loadMDY(const char *fileName) {
-	Common::File song;
+void AdLib::writeAttackDecay(uint8 oper) {
+	uint8 value = 0;
 
-	song.open(fileName);
-	if (!song.isOpen())
-		return false;
+	value |= _operatorParams[oper][kParamAttack] << 4;
+	value |= _operatorParams[oper][kParamDecay] & 0x0F;
 
-	bool loaded = loadMDY(song);
+	writeOPL(0x60 + kOperatorOffset[oper], value);
+}
 
-	song.close();
+void AdLib::writeSustainRelease(uint8 oper) {
+	uint8 value = 0;
 
-	return loaded;
+	value |= _operatorParams[oper][kParamSustain] << 4;
+	value |= _operatorParams[oper][kParamRelease] & 0x0F;
+
+	writeOPL(0x80 + kOperatorOffset[oper], value);
 }
 
-bool MDYPlayer::loadTBR(Common::SeekableReadStream &stream) {
-	unloadTBR();
+void AdLib::writeTremoloVibratoSustainingKeyScaleRateFreqMulti(uint8 oper) {
+	uint8 value = 0;
+
+	value |= _operatorParams[oper][kParamAM]           ? 0x80 : 0;
+	value |= _operatorParams[oper][kParamVib]          ? 0x40 : 0;
+	value |= _operatorParams[oper][kParamSustaining]   ? 0x20 : 0;
+	value |= _operatorParams[oper][kParamKeyScaleRate] ? 0x10 : 0;
+	value |= _operatorParams[oper][kParamFreqMulti]    & 0x0F;
 
-	_timbresSize = stream.size();
+	writeOPL(0x20 + kOperatorOffset[oper], value);
+}
 
-	_timbres = new byte[_timbresSize];
-	stream.read(_timbres, _timbresSize);
+void AdLib::writeTremoloVibratoDepthPercMode() {
+	uint8 value = 0;
 
-	reset();
-	setVoices();
+	value |= _tremoloDepth       ? 0x80 : 0;
+	value |= _vibratoDepth       ? 0x40 : 0;
+	value |= isPercussionMode() ? 0x20 : 0;
+	value |= _percussionBits;
 
-	return true;
+	writeOPL(0xBD, value);
 }
 
-bool MDYPlayer::loadTBR(const char *fileName) {
-	Common::File timbres;
+void AdLib::writeWaveSelect(uint8 oper) {
+	uint8 wave = 0;
+	if (_enableWaveSelect)
+		wave = _operatorParams[oper][kParamWaveSelect] & 0x03;
 
-	timbres.open(fileName);
-	if (!timbres.isOpen())
-		return false;
+	writeOPL(0xE0 + kOperatorOffset[ oper], wave);
+}
+
+void AdLib::writeAllParams(uint8 oper) {
+	writeTremoloVibratoDepthPercMode();
+	writeKeySplit();
+	writeKeyScaleLevelVolume(oper);
+	writeFeedbackFM(oper);
+	writeAttackDecay(oper);
+	writeSustainRelease(oper);
+	writeTremoloVibratoSustainingKeyScaleRateFreqMulti(oper);
+	writeWaveSelect(oper);
+}
 
-	bool loaded = loadTBR(timbres);
+void AdLib::initOperatorParams() {
+	for (int i = 0; i < kOperatorCount; i++)
+		setOperatorParams(i, kPianoParams[kOperatorType[i]], kPianoParams[kOperatorType[i]][kParamCount - 1]);
 
-	timbres.close();
+	if (isPercussionMode()) {
+		setOperatorParams(12, kBaseDrumParams [0], kBaseDrumParams [0][kParamCount - 1]);
+		setOperatorParams(15, kBaseDrumParams [1], kBaseDrumParams [1][kParamCount - 1]);
+		setOperatorParams(16, kSnareDrumParams   , kSnareDrumParams   [kParamCount - 1]);
+		setOperatorParams(14, kTomParams         , kTomParams         [kParamCount - 1]);
+		setOperatorParams(17, kCymbalParams      , kCymbalParams      [kParamCount - 1]);
+		setOperatorParams(13, kHihatParams       , kHihatParams       [kParamCount - 1]);
+	}
+}
 
-	return loaded;
+void AdLib::initOperatorVolumes() {
+	for(int i = 0; i < kOperatorCount; i++)
+		_operatorVolume[i] = kMaxVolume;
 }
 
-void MDYPlayer::unload() {
-	unloadTBR();
-	unloadMDY();
+void AdLib::setOperatorParams(uint8 oper, const uint16 *params, uint8 wave) {
+	byte *operParams = _operatorParams[oper];
+
+	for (int i = 0; i < (kParamCount - 1); i++)
+		operParams[i] = params[i];
+
+	operParams[kParamCount - 1] = wave & 0x03;
+
+	writeAllParams(oper);
 }
 
-void MDYPlayer::unloadMDY() {
-	AdLib::unload();
+void AdLib::voiceOff(uint8 voice) {
+	writeOPL(0xA0 + voice, 0);
+	writeOPL(0xB0 + voice, 0);
 }
 
-void MDYPlayer::unloadTBR() {
-	delete[] _timbres;
+int32 AdLib::calcFreq(int32 deltaDemiToneNum, int32 deltaDemiToneDenom) {
+	int32 freq = 0;
 
-	_timbres = 0;
-	_timbresSize = 0;
+	freq  = ((deltaDemiToneDenom * 100) + 6 * deltaDemiToneNum) * 52088;
+	freq /= deltaDemiToneDenom * 2500;
+
+	return (freq * 147456) / 111875;
 }
 
-void MDYPlayer::interpret() {
-	unsigned char instr;
-	byte channel;
-	byte note;
-	byte volume;
-	uint8 tempoMult, tempoFrac;
-	uint8 ctrlByte1, ctrlByte2;
-	uint8 timbre;
+void AdLib::setFreqs(uint16 *freqs, int32 num, int32 denom) {
+	int32 val = calcFreq(num, denom);
 
-// TODO : Verify the loop for percussive mode (11 ?)
-	if (_first) {
-		for (int i = 0; i < 9; i ++)
-			setVolume(i, 0);
+	*freqs++ = (4 + val) >> 3;
 
-//	TODO : Set pitch range
+	for (int i = 1; i < kHalfToneCount; i++) {
+		val = (val * 106) / 100;
 
-		_tempo = _basicTempo;
-		_wait = *(_playPos++);
-		_first = false;
+		*freqs++ = (4 + val) >> 3;
 	}
-	do {
-		instr = *_playPos;
-		debugC(6, kDebugSound, "MDYPlayer::interpret instr 0x%X", instr);
-		switch (instr) {
-		case 0xF8:
-			_wait = *(_playPos++);
-			break;
-		case 0xFC:
-			_ended = true;
-			_samplesTillPoll = 0;
-			return;
-		case 0xF0:
-			_playPos++;
-			ctrlByte1 = *(_playPos++);
-			ctrlByte2 = *(_playPos++);
-			debugC(6, kDebugSound, "MDYPlayer::interpret ctrlBytes 0x%X 0x%X", ctrlByte1, ctrlByte2);
-			if (ctrlByte1 != 0x7F || ctrlByte2 != 0) {
-				_playPos -= 2;
-				while (*(_playPos++) != 0xF7)
-					;
-			} else {
-				tempoMult = *(_playPos++);
-				tempoFrac = *(_playPos++);
-				_tempo = _basicTempo * tempoMult + (unsigned)(((long)_basicTempo * tempoFrac) >> 7);
-				_playPos++;
-			}
-			_wait = *(_playPos++);
-			break;
-		default:
-			if (instr >= 0x80) {
-				_playPos++;
-			}
-			channel = (int)(instr & 0x0f);
-
-			switch (instr & 0xf0) {
-			case 0x90:
-				note = *(_playPos++);
-				volume = *(_playPos++);
-				_pollNotes[channel] = note;
-				setVolume(channel, volume);
-				setKey(channel, note, true, false);
-				break;
-			case 0x80:
-				_playPos += 2;
-				note = _pollNotes[channel];
-				setKey(channel, note, false, false);
-				break;
-			case 0xA0:
-				setVolume(channel, *(_playPos++));
-				break;
-			case 0xC0:
-				timbre = *(_playPos++);
-				setVoice(channel, timbre, false);
-				break;
-			case 0xE0:
-				warning("MDYPlayer: Pitch bend not yet implemented");
+}
 
-				note = *(_playPos)++;
-				note += (unsigned)(*(_playPos++)) << 7;
+void AdLib::initFreqs() {
+	const int numStep = 100 / kPitchStepCount;
 
-				setKey(channel, note, _notOn[channel], true);
+	for (int i = 0; i < kPitchStepCount; i++)
+		setFreqs(_freqs[i], i * numStep, 100);
 
-				break;
-			case 0xB0:
-				_playPos += 2;
-				break;
-			case 0xD0:
-				_playPos++;
-				break;
-			default:
-				warning("MDYPlayer: Bad MIDI instr byte: 0%X", instr);
-				while ((*_playPos) < 0x80)
-					_playPos++;
-				if (*_playPos != 0xF8)
-					_playPos--;
-				break;
-			} //switch instr & 0xF0
-			_wait = *(_playPos++);
-			break;
-		} //switch instr
-	} while (_wait == 0);
-
-	if (_wait == 0xF8) {
-		_wait = 0xF0;
-		if (*_playPos != 0xF8)
-			_wait += *(_playPos++) & 0x0F;
+	for (int i = 0; i < kMaxVoiceCount; i++) {
+		_freqPtr       [i] = _freqs[0];
+		_halfToneOffset[i] = 0;
 	}
-//		_playPos++;
-	_samplesTillPoll = _wait * (_rate / 1000);
 }
 
-void MDYPlayer::reset() {
-	AdLib::reset();
+void AdLib::changePitch(uint8 voice, uint16 pitchBend) {
+
+	int full   = 0;
+	int frac   = 0;
+	int amount = ((pitchBend - kMidPitch) * _pitchRangeStep) / kMidPitch;
+
+	if (amount >= 0) {
+		// Bend up
+
+		full = amount / kPitchStepCount;
+		frac = amount % kPitchStepCount;
 
-// _soundMode 1 : Percussive mode.
-	if (_soundMode == 1) {
-		writeOPL(0xA6, 0);
-		writeOPL(0xB6, 0);
-		writeOPL(0xA7, 0);
-		writeOPL(0xB7, 0);
-		writeOPL(0xA8, 0);
-		writeOPL(0xB8, 0);
+	} else {
+		// Bend down
+
+		amount = kPitchStepCount - 1 - amount;
+
+		full = -(amount / kPitchStepCount);
+		frac = (amount - kPitchStepCount + 1) % kPitchStepCount;
+		if (frac)
+			frac = kPitchStepCount - frac;
 
-// TODO set the correct frequency for the last 4 percussive voices
 	}
+
+	_halfToneOffset[voice] = full;
+	_freqPtr       [voice] = _freqs[frac];
 }
 
-void MDYPlayer::rewind() {
-	_playPos = _data;
-}
-
-void MDYPlayer::setVoices() {
-	byte *timbrePtr;
-
-	timbrePtr = _timbres;
-	debugC(6, kDebugSound, "MDYPlayer::setVoices TBR version: %X.%X", timbrePtr[0], timbrePtr[1]);
-	timbrePtr += 2;
-
-	_tbrCount = READ_LE_UINT16(timbrePtr);
-	debugC(6, kDebugSound, "MDYPlayer::setVoices Timbres counter: %d", _tbrCount);
-	timbrePtr += 2;
-	_tbrStart = READ_LE_UINT16(timbrePtr);
-
-	timbrePtr += 2;
-	for (int i = 0; i < _tbrCount; i++)
-		setVoice(i, i, true);
-}
-
-void MDYPlayer::setVoice(byte voice, byte instr, bool set) {
-//	uint16 strct[27];
-	uint8 strct[27];
-	byte channel;
-	byte *timbrePtr;
-	char timbreName[10];
-
-	timbreName[9] = '\0';
-	for (int j = 0; j < 9; j++)
-		timbreName[j] = _timbres[6 + j + (instr * 9)];
-	debugC(6, kDebugSound, "MDYPlayer::setVoice Loading timbre %s", timbreName);
-
-	// i = 0 :  0  1  2  3  4  5  6  7  8  9 10 11 12 26
-	// i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27
-	for (int i = 0; i < 2; i++) {
-		timbrePtr = _timbres + _tbrStart + instr * 0x38 + i * 0x1A;
-		for (int j = 0; j < 27; j++) {
-			if (timbrePtr >= (_timbres + _timbresSize)) {
-				warning("MDYPlayer: Instrument %d out of range (%d, %d)", instr,
-						(uint32) (timbrePtr - _timbres), _timbresSize);
-				strct[j] = 0;
-			} else
-				//strct[j] = READ_LE_UINT16(timbrePtr);
-				strct[j] = timbrePtr[0];
-			//timbrePtr += 2;
-			timbrePtr++;
-		}
-		channel = _operators[voice] + i * 3;
-		writeOPL(0xBD, 0x00);
-		writeOPL(0x08, 0x00);
-		writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F));
-		if (!i)
-			writeOPL(0xC0 | voice,
-					((strct[2] & 7) << 1) | (1 - (strct[12] & 1)));
-		writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF));
-		writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF));
-		writeOPL(0x20 | channel, ((strct[9] & 1) << 7) |
-			((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) |
-			((strct[11] & 1) << 4) |  (strct[1] & 0xF));
-		if (!i)
-			writeOPL(0xE0 | channel, (strct[26] & 3));
-		else {
-			writeOPL(0xE0 | channel, (strct[14] & 3));
-			writeOPL(0x40 | channel, 0);
-		}
-	}
+void AdLib::setFreq(uint8 voice, uint16 note, bool on) {
+	_voiceOn  [voice] = on;
+	_voiceNote[voice] = note;
+
+	note = CLIP<int>(note + _halfToneOffset[voice], 0, kNoteCount - 1);
+
+	uint16 freq = _freqPtr[voice][note % kHalfToneCount];
+
+	uint8 value = 0;
+	value |= on ? 0x20 : 0;
+	value |= ((note / kHalfToneCount) << 2) | ((freq >> 8) & 0x03);
+
+	writeOPL(0xA0 + voice, freq);
+	writeOPL(0xB0 + voice, value);
 }
 
 } // End of namespace Gob
diff --git a/engines/gob/sound/adlib.h b/engines/gob/sound/adlib.h
index 934e996..df1b77f 100644
--- a/engines/gob/sound/adlib.h
+++ b/engines/gob/sound/adlib.h
@@ -20,154 +20,287 @@
  *
  */
 
-#ifndef GOB_SOUND_ADLIB_H
-#define GOB_SOUND_ADLIB_H
+#ifndef GOB_SOUND_NEWADLIB_H
+#define GOB_SOUND_NEWADLIB_H
 
 #include "common/mutex.h"
+
 #include "audio/audiostream.h"
 #include "audio/mixer.h"
-#include "audio/fmopl.h"
 
-namespace Gob {
+namespace OPL {
+	class OPL;
+}
 
-class GobEngine;
+namespace Gob {
 
+/** Base class for a player of an AdLib music format. */
 class AdLib : public Audio::AudioStream {
 public:
 	AdLib(Audio::Mixer &mixer);
 	virtual ~AdLib();
 
-	bool isPlaying() const;
-	int getIndex() const;
-	bool getRepeating() const;
+	bool isPlaying() const;    ///< Are we currently playing?
+	int32 getRepeating() const; ///< Return number of times left to loop.
 
+	/** Set the loop counter.
+	 *
+	 *  @param repCount Number of times to loop (i.e. number of additional
+	 *                  paythroughs to the first one, not overall).
+	 *                  A negative value means infinite looping.
+	 */
 	void setRepeating(int32 repCount);
 
 	void startPlay();
 	void stopPlay();
 
-	virtual void unload();
-
 // AudioStream API
 	int  readBuffer(int16 *buffer, const int numSamples);
-	bool isStereo()    const { return false;     }
-	bool endOfData()   const { return !_playing; }
-	bool endOfStream() const { return false;     }
-	int  getRate()     const { return _rate;     }
+	bool isStereo()    const;
+	bool endOfData()   const;
+	bool endOfStream() const;
+	int  getRate()     const;
 
 protected:
-	static const unsigned char _operators[];
-	static const unsigned char _volRegNums [];
+	enum kVoice {
+		kVoiceMelody0   =  0,
+		kVoiceMelody1   =  1,
+		kVoiceMelody2   =  2,
+		kVoiceMelody3   =  3,
+		kVoiceMelody4   =  4,
+		kVoiceMelody5   =  5,
+		kVoiceMelody6   =  6, // Only available in melody mode.
+		kVoiceMelody7   =  7, // Only available in melody mode.
+		kVoiceMelody8   =  8, // Only available in melody mode.
+		kVoiceBaseDrum  =  6, // Only available in percussion mode.
+		kVoiceSnareDrum =  7, // Only available in percussion mode.
+		kVoiceTom       =  8, // Only available in percussion mode.
+		kVoiceCymbal    =  9, // Only available in percussion mode.
+		kVoiceHihat     = 10  // Only available in percussion mode.
+	};
+
+	/** Operator parameters. */
+	enum kParam {
+		kParamKeyScaleLevel =  0,
+		kParamFreqMulti     =  1,
+		kParamFeedback      =  2,
+		kParamAttack        =  3,
+		kParamSustain       =  4,
+		kParamSustaining    =  5,
+		kParamDecay         =  6,
+		kParamRelease       =  7,
+		kParamLevel         =  8,
+		kParamAM            =  9,
+		kParamVib           = 10,
+		kParamKeyScaleRate  = 11,
+		kParamFM            = 12,
+		kParamWaveSelect    = 13
+	};
+
+	static const int kOperatorCount  = 18; ///< Number of operators.
+	static const int kParamCount     = 14; ///< Number of operator parameters.
+	static const int kPitchStepCount = 25; ///< Number of pitch bend steps in a half tone.
+	static const int kOctaveCount    =  8; ///< Number of octaves we can play.
+	static const int kHalfToneCount  = 12; ///< Number of half tones in an octave.
+
+	static const int kOperatorsPerVoice = 2; ///< Number of operators per voice.
+
+	static const int kMelodyVoiceCount     =  9; ///< Number of melody voices.
+	static const int kPercussionVoiceCount =  5; ///< Number of percussion voices.
+	static const int kMaxVoiceCount        = 11; ///< Max number of voices.
+
+	/** Number of notes we can play. */
+	static const int kNoteCount = kHalfToneCount * kOctaveCount;
+
+	static const int kMaxVolume = 0x007F;
+	static const int kMaxPitch  = 0x3FFF;
+	static const int kMidPitch  = 0x2000;
+
+	static const int kStandardMidC = 60; ///< A mid C in standard MIDI.
+	static const int kOPLMidC      = 48; ///< A mid C for the OPL.
+
+
+	/** Return the number of samples per second. */
+	uint32 getSamplesPerSecond() const;
+
+	/** Write a value into an OPL register. */
+	void writeOPL(byte reg, byte val);
+
+	/** Signal that the playback ended.
+	 *
+	 *  @param killRepeat Explicitly request that the song is not to be looped.
+	 */
+	void end(bool killRepeat = false);
+
+	/** The callback function that's called for polling more AdLib commands.
+	 *
+	 *  @param  first Is this the first poll since the start of the song?
+	 *  @return The number of samples until the next poll.
+	 */
+	virtual uint32 pollMusic(bool first) = 0;
+
+	/** Rewind the song. */
+	virtual void rewind() = 0;
+
+	/** Return whether we're in percussion mode. */
+	bool isPercussionMode() const;
+
+	/** Set percussion or melody mode. */
+	void setPercussionMode(bool percussion);
+
+	/** Enable/Disable the wave select operator parameters.
+	 *
+	 *  When disabled, all operators use the sine wave, regardless of the parameter.
+	 */
+	void enableWaveSelect(bool enable);
+
+	/** Change the pitch bend range.
+	 *
+	 *  @param range The range in half tones from 1 to 12 inclusive.
+	 *         See bendVoicePitch() for how this works in practice.
+	 */
+	void setPitchRange(uint8 range);
+
+	/** Set the tremolo (amplitude vibrato) depth.
+	 *
+	 *  @param tremoloDepth false: 1.0dB, true: 4.8dB.
+	 */
+	void setTremoloDepth(bool tremoloDepth);
+
+	/** Set the frequency vibrato depth.
+	 *
+	 *  @param vibratoDepth false: 7 cent, true: 14 cent. 1 cent = 1/100 half tone.
+	 */
+	void setVibratoDepth(bool vibratoDepth);
+
+	/** Set the keyboard split point. */
+	void setKeySplit(bool keySplit);
+
+	/** Set the timbre of a voice.
+	 *
+	 *  Layout of the operator parameters is as follows:
+	 *  - First 13 parameter for the first operator
+	 *  - First 13 parameter for the second operator
+	 *  - 14th parameter (wave select) for the first operator
+	 *  - 14th parameter (wave select) for the second operator
+	 */
+	void setVoiceTimbre(uint8 voice, const uint16 *params);
+
+	/** Set a voice's volume. */
+	void setVoiceVolume(uint8 voice, uint8 volume);
+
+	/** Bend a voice's pitch.
+	 *
+	 *  The pitchBend parameter is a value between 0 (full down) and kMaxPitch (full up).
+	 *  The actual frequency depends on the pitch range set previously by setPitchRange(),
+	 *  with full down being -range half tones and full up range half tones.
+	 */
+	void bendVoicePitch(uint8 voice, uint16 pitchBend);
+
+	/** Switch a voice on.
+	 *
+	 *  Plays one of the kNoteCount notes. However, the valid range of a note is between
+	 *  0 and 127, of which only 12 to 107 are audible.
+	 */
+	void noteOn(uint8 voice, uint8 note);
+
+	/** Switch a voice off. */
+	void noteOff(uint8 voice);
+
+private:
+	static const uint8 kOperatorType  [kOperatorCount];
+	static const uint8 kOperatorOffset[kOperatorCount];
+	static const uint8 kOperatorVoice [kOperatorCount];
+
+	static const uint8 kVoiceMelodyOperator    [kOperatorsPerVoice][kMelodyVoiceCount];
+	static const uint8 kVoicePercussionOperator[kOperatorsPerVoice][kPercussionVoiceCount];
+
+	static const byte kPercussionMasks[kPercussionVoiceCount];
+
+	static const uint16 kPianoParams    [kOperatorsPerVoice][kParamCount];
+	static const uint16 kBaseDrumParams [kOperatorsPerVoice][kParamCount];
+
+	static const uint16 kSnareDrumParams[kParamCount];
+	static const uint16 kTomParams      [kParamCount];
+	static const uint16 kCymbalParams   [kParamCount];
+	static const uint16 kHihatParams    [kParamCount];
+
 
 	Audio::Mixer *_mixer;
 	Audio::SoundHandle _handle;
-	FM_OPL *_opl;
+	OPL::OPL *_opl;
 
 	Common::Mutex _mutex;
 
 	uint32 _rate;
 
-	byte *_data;
-	byte *_playPos;
-	uint32 _dataSize;
-
-	short _freqs[25][12];
-	byte _notes[11];
-	byte _notCol[11];
-	byte _notLin[11];
-	bool _notOn[11];
-	byte _pollNotes[16];
+	uint32 _toPoll;
 
-	int _samplesTillPoll;
 	int32 _repCount;
 
-	bool _playing;
 	bool _first;
+	bool _playing;
 	bool _ended;
 
-	bool _freeData;
+	bool _tremoloDepth;
+	bool _vibratoDepth;
+	bool _keySplit;
 
-	int _index;
+	bool _enableWaveSelect;
 
-	unsigned char _wait;
-	uint8 _tickBeat;
-	uint8 _beatMeasure;
-	uint32 _totalTick;
-	uint32 _nrCommand;
-	uint16 _pitchBendRangeStep;
-	uint16 _basicTempo, _tempo;
+	bool _percussionMode;
+	byte _percussionBits;
 
-	void writeOPL(byte reg, byte val);
-	void setFreqs();
-	void setKey(byte voice, byte note, bool on, bool spec);
-	void setVolume(byte voice, byte volume);
-	void pollMusic();
+	uint8  _pitchRange;
+	uint16 _pitchRangeStep;
 
-	virtual void interpret() = 0;
+	uint8 _voiceNote[kMaxVoiceCount]; // Last note of each voice
+	uint8 _voiceOn  [kMaxVoiceCount]; // Whether each voice is currently on
 
-	virtual void reset();
-	virtual void rewind() = 0;
-	virtual void setVoices() = 0;
+	uint8 _operatorVolume[kOperatorCount]; // Volume of each operator
 
-private:
-	void init();
-};
+	byte _operatorParams[kOperatorCount][kParamCount]; // All operator parameters
 
-class ADLPlayer : public AdLib {
-public:
-	ADLPlayer(Audio::Mixer &mixer);
-	~ADLPlayer();
+	uint16  _freqs[kPitchStepCount][kHalfToneCount];
+	uint16 *_freqPtr[kMaxVoiceCount];
 
-	bool load(const char *fileName);
-	bool load(byte *data, uint32 size, int index = -1);
+	int _halfToneOffset[kMaxVoiceCount];
 
-	void unload();
 
-protected:
-	void interpret();
+	void createOPL();
+	void initOPL();
 
 	void reset();
-	void rewind();
+	void allOff();
 
-	void setVoices();
-	void setVoice(byte voice, byte instr, bool set);
-};
+	// Write global parameters into the OPL
+	void writeTremoloVibratoDepthPercMode();
+	void writeKeySplit();
 
-class MDYPlayer : public AdLib {
-public:
-	MDYPlayer(Audio::Mixer &mixer);
-	~MDYPlayer();
-
-	bool loadMDY(const char *fileName);
-	bool loadMDY(Common::SeekableReadStream &stream);
-	bool loadTBR(const char *fileName);
-	bool loadTBR(Common::SeekableReadStream &stream);
-
-	void unload();
-
-protected:
-	byte _soundMode;
-
-	byte *_timbres;
-	uint16 _tbrCount;
-	uint16 _tbrStart;
-	uint32 _timbresSize;
+	// Write operator parameters into the OPL
+	void writeWaveSelect(uint8 oper);
+	void writeTremoloVibratoSustainingKeyScaleRateFreqMulti(uint8 oper);
+	void writeSustainRelease(uint8 oper);
+	void writeAttackDecay(uint8 oper);
+	void writeFeedbackFM(uint8 oper);
+	void writeKeyScaleLevelVolume(uint8 oper);
+	void writeAllParams(uint8 oper);
 
-	void interpret();
+	void initOperatorParams();
+	void initOperatorVolumes();
+	void setOperatorParams(uint8 oper, const uint16 *params, uint8 wave);
 
-	void reset();
-	void rewind();
+	void voiceOff(uint8 voice);
 
-	void setVoices();
-	void setVoice(byte voice, byte instr, bool set);
+	void initFreqs();
+	void setFreqs(uint16 *freqs, int32 num, int32 denom);
+	int32 calcFreq(int32 deltaDemiToneNum, int32 deltaDemiToneDenom);
 
-	void unloadTBR();
-	void unloadMDY();
+	void changePitch(uint8 voice, uint16 pitchBend);
 
-private:
-	void init();
+	void setFreq(uint8 voice, uint16 note, bool on);
 };
 
 } // End of namespace Gob
 
-#endif // GOB_SOUND_ADLIB_H
+#endif // GOB_SOUND_NEWADLIB_H
diff --git a/engines/gob/sound/adlplayer.cpp b/engines/gob/sound/adlplayer.cpp
new file mode 100644
index 0000000..ee23191
--- /dev/null
+++ b/engines/gob/sound/adlplayer.cpp
@@ -0,0 +1,257 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/memstream.h"
+#include "common/textconsole.h"
+
+#include "gob/sound/adlplayer.h"
+
+namespace Gob {
+
+ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer),
+	_songData(0), _songDataSize(0), _playPos(0) {
+
+}
+
+ADLPlayer::~ADLPlayer() {
+	unload();
+}
+
+void ADLPlayer::unload() {
+	stopPlay();
+
+	_timbres.clear();
+
+	delete[] _songData;
+
+	_songData     = 0;
+	_songDataSize = 0;
+
+	_playPos = 0;
+}
+
+uint32 ADLPlayer::pollMusic(bool first) {
+	if (_timbres.empty() || !_songData || !_playPos || (_playPos >= (_songData + _songDataSize))) {
+		end();
+		return 0;
+	}
+
+	// We'll ignore the first delay
+	if (first)
+		_playPos += (*_playPos & 0x80) ? 2 : 1;
+
+	byte cmd = *_playPos++;
+
+	// Song end marker
+	if (cmd == 0xFF) {
+		end();
+		return 0;
+	}
+
+	// Set the instrument that should be modified
+	if (cmd == 0xFE)
+		_modifyInstrument = *_playPos++;
+
+	if (cmd >= 0xD0) {
+		// Modify an instrument
+
+		if      (_modifyInstrument == 0xFF)
+			warning("ADLPlayer: No instrument to modify");
+		else if (_modifyInstrument >= _timbres.size())
+			warning("ADLPlayer: Can't modify invalid instrument %d (%d)", _modifyInstrument, _timbres.size());
+		else
+			_timbres[_modifyInstrument].params[_playPos[0]] = _playPos[1];
+
+		_playPos += 2;
+
+		// If we currently have that instrument loaded, reload it
+		for (int i = 0; i < kMaxVoiceCount; i++)
+			if (_currentInstruments[i] == _modifyInstrument)
+				setInstrument(i, _modifyInstrument);
+	} else {
+		// Voice command
+
+		uint8 voice = cmd & 0x0F;
+		uint8 note, volume;
+
+		switch (cmd & 0xF0) {
+		case 0x00: // Note on with volume
+			note   = *_playPos++;
+			volume = *_playPos++;
+
+			setVoiceVolume(voice, volume);
+			noteOn(voice, note);
+			break;
+
+		case 0xA0: // Pitch bend
+			bendVoicePitch(voice, ((uint16)*_playPos++) << 7);
+			break;
+
+		case 0xB0: // Set volume
+			setVoiceVolume(voice, *_playPos++);
+			break;
+
+		case 0xC0: // Set instrument
+			setInstrument(voice, *_playPos++);
+			break;
+
+		case 0x90: // Note on
+			noteOn(voice, *_playPos++);
+			break;
+
+		case 0x80: // Note off
+			noteOff(voice);
+			break;
+
+		default:
+			warning("ADLPlayer: Unsupported command: 0x%02X. Stopping playback.", cmd);
+			end(true);
+			return 0;
+		}
+	}
+
+	uint16 delay = *_playPos++;
+
+	if (delay & 0x80)
+		delay = ((delay & 3) << 8) | *_playPos++;
+
+	return getSampleDelay(delay);
+}
+
+uint32 ADLPlayer::getSampleDelay(uint16 delay) const {
+	if (delay == 0)
+		return 0;
+
+	return ((uint32)delay * getSamplesPerSecond()) / 1000;
+}
+
+void ADLPlayer::rewind() {
+	// Reset song data
+	_playPos = _songData;
+
+	// Set melody/percussion mode
+	setPercussionMode(_soundMode != 0);
+
+	// Reset instruments
+	for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t)
+		memcpy(t->params, t->startParams, kOperatorsPerVoice * kParamCount * sizeof(uint16));
+
+	for (int i = 0; i < kMaxVoiceCount; i++)
+		_currentInstruments[i] = 0;
+
+	// Reset voices
+	int numVoice = MIN<int>(_timbres.size(), _soundMode ? (int)kMaxVoiceCount : (int)kMelodyVoiceCount);
+	for (int i = 0; i < numVoice; i++) {
+		setInstrument(i, _currentInstruments[i]);
+		setVoiceVolume(i, kMaxVolume);
+	}
+
+	_modifyInstrument = 0xFF;
+}
+
+bool ADLPlayer::load(Common::SeekableReadStream &adl) {
+	unload();
+
+	int timbreCount;
+	if (!readHeader(adl, timbreCount)) {
+		unload();
+		return false;
+	}
+
+	if (!readTimbres(adl, timbreCount) || !readSongData(adl) || adl.err()) {
+		unload();
+		return false;
+	}
+
+	rewind();
+
+	return true;
+}
+
+bool ADLPlayer::readHeader(Common::SeekableReadStream &adl, int &timbreCount) {
+	// Sanity check
+	if (adl.size() < 60) {
+		warning("ADLPlayer::readHeader(): File too small (%d)", adl.size());
+		return false;
+	}
+
+	_soundMode  = adl.readByte();
+	timbreCount = adl.readByte() + 1;
+
+	adl.skip(1);
+
+	return true;
+}
+
+bool ADLPlayer::readTimbres(Common::SeekableReadStream &adl, int timbreCount) {
+	_timbres.resize(timbreCount);
+	for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) {
+		for (int i = 0; i < (kOperatorsPerVoice * kParamCount); i++)
+			t->startParams[i] = adl.readUint16LE();
+	}
+
+	if (adl.err()) {
+		warning("ADLPlayer::readTimbres(): Read failed");
+		return false;
+	}
+
+	return true;
+}
+
+bool ADLPlayer::readSongData(Common::SeekableReadStream &adl) {
+	_songDataSize = adl.size() - adl.pos();
+	_songData     = new byte[_songDataSize];
+
+	if (adl.read(_songData, _songDataSize) != _songDataSize) {
+		warning("ADLPlayer::readSongData(): Read failed");
+		return false;
+	}
+
+	return true;
+}
+
+bool ADLPlayer::load(const byte *data, uint32 dataSize, int index) {
+	unload();
+
+	Common::MemoryReadStream stream(data, dataSize);
+	if (!load(stream))
+		return false;
+
+	_index = index;
+	return true;
+}
+
+void ADLPlayer::setInstrument(int voice, int instrument) {
+	if ((voice >= kMaxVoiceCount) || ((uint)instrument >= _timbres.size()))
+		return;
+
+	_currentInstruments[voice] = instrument;
+
+	setVoiceTimbre(voice, _timbres[instrument].params);
+}
+
+int ADLPlayer::getIndex() const {
+	return _index;
+}
+
+} // End of namespace Gob
diff --git a/engines/gob/sound/adlplayer.h b/engines/gob/sound/adlplayer.h
new file mode 100644
index 0000000..9596447
--- /dev/null
+++ b/engines/gob/sound/adlplayer.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 GOB_SOUND_ADLPLAYER_H
+#define GOB_SOUND_ADLPLAYER_H
+
+#include "common/array.h"
+
+#include "gob/sound/adlib.h"
+
+namespace Common {
+	class SeekableReadStream;
+}
+
+namespace Gob {
+
+/** A player for Coktel Vision's ADL music format. */
+class ADLPlayer : public AdLib {
+public:
+	ADLPlayer(Audio::Mixer &mixer);
+	~ADLPlayer();
+
+	bool load(Common::SeekableReadStream &adl);
+	bool load(const byte *data, uint32 dataSize, int index = -1);
+	void unload();
+
+	int getIndex() const;
+
+protected:
+	// AdLib interface
+	uint32 pollMusic(bool first);
+	void rewind();
+
+private:
+	struct Timbre {
+		uint16 startParams[kOperatorsPerVoice * kParamCount];
+		uint16 params[kOperatorsPerVoice * kParamCount];
+	};
+
+	uint8 _soundMode;
+
+	Common::Array<Timbre> _timbres;
+
+	byte  *_songData;
+	uint32 _songDataSize;
+
+	const byte *_playPos;
+
+	int _index;
+
+	uint8  _modifyInstrument;
+	uint16 _currentInstruments[kMaxVoiceCount];
+
+
+	void setInstrument(int voice, int instrument);
+
+	bool readHeader  (Common::SeekableReadStream &adl, int &timbreCount);
+	bool readTimbres (Common::SeekableReadStream &adl, int  timbreCount);
+	bool readSongData(Common::SeekableReadStream &adl);
+
+	uint32 getSampleDelay(uint16 delay) const;
+};
+
+} // End of namespace Gob
+
+#endif // GOB_SOUND_ADLPLAYER_H
diff --git a/engines/gob/sound/musplayer.cpp b/engines/gob/sound/musplayer.cpp
new file mode 100644
index 0000000..3e41dc6
--- /dev/null
+++ b/engines/gob/sound/musplayer.cpp
@@ -0,0 +1,391 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "gob/sound/musplayer.h"
+
+namespace Gob {
+
+MUSPlayer::MUSPlayer(Audio::Mixer &mixer) : AdLib(mixer),
+	_songData(0), _songDataSize(0), _playPos(0), _songID(0) {
+
+}
+
+MUSPlayer::~MUSPlayer() {
+	unload();
+}
+
+void MUSPlayer::unload() {
+	stopPlay();
+
+	unloadSND();
+	unloadMUS();
+}
+
+uint32 MUSPlayer::getSampleDelay(uint16 delay) const {
+	if (delay == 0)
+		return 0;
+
+	uint32 freq = (_ticksPerBeat * _tempo) / 60;
+
+	return ((uint32)delay * getSamplesPerSecond()) / freq;
+}
+
+void MUSPlayer::skipToTiming() {
+	while (*_playPos < 0x80)
+		_playPos++;
+
+	if (*_playPos != 0xF8)
+		_playPos--;
+}
+
+uint32 MUSPlayer::pollMusic(bool first) {
+	if (_timbres.empty() || !_songData || !_playPos || (_playPos >= (_songData + _songDataSize))) {
+		end();
+		return 0;
+	}
+
+	if (first)
+		return getSampleDelay(*_playPos++);
+
+	uint16 delay = 0;
+	while (delay == 0) {
+		byte cmd = *_playPos;
+
+		// Delay overflow
+		if (cmd == 0xF8) {
+			_playPos++;
+			delay = 0xF8;
+			break;
+		}
+
+		// Song end marker
+		if (cmd == 0xFC) {
+			end();
+			return 0;
+		}
+
+		// Global command
+		if (cmd == 0xF0) {
+			_playPos++;
+
+			byte type1 = *_playPos++;
+			byte type2 = *_playPos++;
+
+			if ((type1 == 0x7F) && (type2 == 0)) {
+				// Tempo change, as a fraction of the base tempo
+
+				uint32 num   = *_playPos++;
+				uint32 denom = *_playPos++;
+
+				_tempo = _baseTempo * num + ((_baseTempo * denom) >> 7);
+
+				_playPos++;
+			} else {
+
+				// Unsupported global command, skip it
+				_playPos -= 2;
+				while(*_playPos++ != 0xF7)
+					;
+			}
+
+			delay = *_playPos++;
+			break;
+		}
+
+		// Voice command
+
+		if (cmd >= 0x80) {
+			_playPos++;
+
+			_lastCommand = cmd;
+		} else
+			cmd = _lastCommand;
+
+		uint8 voice = cmd & 0x0F;
+		uint8 note, volume;
+		uint16 pitch;
+
+		switch (cmd & 0xF0) {
+		case 0x80: // Note off
+			_playPos += 2;
+			noteOff(voice);
+			break;
+
+		case 0x90: // Note on
+			note   = *_playPos++;
+			volume = *_playPos++;
+
+			if (volume) {
+				setVoiceVolume(voice, volume);
+				noteOn(voice, note);
+			} else
+				noteOff(voice);
+			break;
+
+		case 0xA0: // Set volume
+			setVoiceVolume(voice, *_playPos++);
+			break;
+
+		case 0xB0:
+			_playPos += 2;
+			break;
+
+		case 0xC0: // Set instrument
+			setInstrument(voice, *_playPos++);
+			break;
+
+		case 0xD0:
+			_playPos++;
+			break;
+
+		case 0xE0: // Pitch bend
+			pitch  = *_playPos++;
+			pitch += *_playPos++ << 7;
+			bendVoicePitch(voice, pitch);
+			break;
+
+		default:
+			warning("MUSPlayer: Unsupported command: 0x%02X", cmd);
+			skipToTiming();
+			break;
+		}
+
+		delay = *_playPos++;
+	}
+
+	if (delay == 0xF8) {
+		delay = 240;
+
+		if (*_playPos != 0xF8)
+			delay += *_playPos++;
+	}
+
+	return getSampleDelay(delay);
+}
+
+void MUSPlayer::rewind() {
+	_playPos = _songData;
+	_tempo   = _baseTempo;
+
+	_lastCommand = 0;
+
+	setPercussionMode(_soundMode != 0);
+	setPitchRange(_pitchBendRange);
+}
+
+bool MUSPlayer::loadSND(Common::SeekableReadStream &snd) {
+	unloadSND();
+
+	int timbreCount, timbrePos;
+	if (!readSNDHeader(snd, timbreCount, timbrePos))
+		return false;
+
+	if (!readSNDTimbres(snd, timbreCount, timbrePos) || snd.err()) {
+		unloadSND();
+		return false;
+	}
+
+	return true;
+}
+
+bool MUSPlayer::readString(Common::SeekableReadStream &stream, Common::String &string, byte *buffer, uint size) {
+	if (stream.read(buffer, size) != size)
+		return false;
+
+	buffer[size] = '\0';
+
+	string = (char *) buffer;
+
+	return true;
+}
+
+bool MUSPlayer::readSNDHeader(Common::SeekableReadStream &snd, int &timbreCount, int &timbrePos) {
+	// Sanity check
+	if (snd.size() <= 6) {
+		warning("MUSPlayer::readSNDHeader(): File too small (%d)", snd.size());
+		return false;
+	}
+
+	// Version
+	const uint8 versionMajor = snd.readByte();
+	const uint8 versionMinor = snd.readByte();
+
+	if ((versionMajor != 1) && (versionMinor != 0)) {
+		warning("MUSPlayer::readSNDHeader(): Unsupported version %d.%d", versionMajor, versionMinor);
+		return false;
+	}
+
+	// Number of timbres and where they start
+	timbreCount = snd.readUint16LE();
+	timbrePos   = snd.readUint16LE();
+
+	const uint16 minTimbrePos = 6 + timbreCount * 9;
+
+	// Sanity check
+	if (timbrePos < minTimbrePos) {
+		warning("MUSPlayer::readSNDHeader(): Timbre offset too small: %d < %d", timbrePos, minTimbrePos);
+		return false;
+	}
+
+	const uint32 timbreParametersSize = snd.size() - timbrePos;
+	const uint32 paramSize            = kOperatorsPerVoice * kParamCount * sizeof(uint16);
+
+	// Sanity check
+	if (timbreParametersSize != (timbreCount * paramSize)) {
+		warning("MUSPlayer::loadSND(): Timbre parameters size mismatch: %d != %d",
+		        timbreParametersSize, timbreCount * paramSize);
+		return false;
+	}
+
+	return true;
+}
+
+bool MUSPlayer::readSNDTimbres(Common::SeekableReadStream &snd, int timbreCount, int timbrePos) {
+	_timbres.resize(timbreCount);
+
+	// Read names
+	byte nameBuffer[10];
+	for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) {
+		if (!readString(snd, t->name, nameBuffer, 9)) {
+			warning("MUSPlayer::readMUSTimbres(): Failed to read timbre name");
+			return false;
+		}
+	}
+
+	if (!snd.seek(timbrePos)) {
+		warning("MUSPlayer::readMUSTimbres(): Failed to seek to timbres");
+		return false;
+	}
+
+	// Read parameters
+	for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) {
+		for (int i = 0; i < (kOperatorsPerVoice * kParamCount); i++)
+			t->params[i] = snd.readUint16LE();
+	}
+
+	return true;
+}
+
+bool MUSPlayer::loadMUS(Common::SeekableReadStream &mus) {
+	unloadMUS();
+
+	if (!readMUSHeader(mus) || !readMUSSong(mus) || mus.err()) {
+		unloadMUS();
+		return false;
+	}
+
+	rewind();
+
+	return true;
+}
+
+bool MUSPlayer::readMUSHeader(Common::SeekableReadStream &mus) {
+	// Sanity check
+	if (mus.size() <= 6)
+		return false;
+
+	// Version
+	const uint8 versionMajor = mus.readByte();
+	const uint8 versionMinor = mus.readByte();
+
+	if ((versionMajor != 1) && (versionMinor != 0)) {
+		warning("MUSPlayer::readMUSHeader(): Unsupported version %d.%d", versionMajor, versionMinor);
+		return false;
+	}
+
+	_songID = mus.readUint32LE();
+
+	byte nameBuffer[31];
+	if (!readString(mus, _songName, nameBuffer, 30)) {
+		warning("MUSPlayer::readMUSHeader(): Failed to read the song name");
+		return false;
+	}
+
+	_ticksPerBeat    = mus.readByte();
+	_beatsPerMeasure = mus.readByte();
+
+	mus.skip(4); // Length of song in ticks
+
+	_songDataSize = mus.readUint32LE();
+
+	mus.skip(4); // Number of commands
+	mus.skip(8); // Unused
+
+	_soundMode      = mus.readByte();
+	_pitchBendRange = mus.readByte();
+	_baseTempo      = mus.readUint16LE();
+
+	mus.skip(8); // Unused
+
+	return true;
+}
+
+bool MUSPlayer::readMUSSong(Common::SeekableReadStream &mus) {
+	const uint32 realSongDataSize = mus.size() - mus.pos();
+
+	if (realSongDataSize < _songDataSize) {
+		warning("MUSPlayer::readMUSSong(): File too small for the song data: %d < %d", realSongDataSize, _songDataSize);
+		return false;
+	}
+
+	_songData = new byte[_songDataSize];
+
+	if (mus.read(_songData, _songDataSize) != _songDataSize) {
+		warning("MUSPlayer::readMUSSong(): Read failed");
+		return false;
+	}
+
+	return true;
+}
+
+void MUSPlayer::unloadSND() {
+	_timbres.clear();
+}
+
+void MUSPlayer::unloadMUS() {
+	delete[] _songData;
+
+	_songData     = 0;
+	_songDataSize = 0;
+
+	_playPos = 0;
+}
+
+uint32 MUSPlayer::getSongID() const {
+	return _songID;
+}
+
+const Common::String &MUSPlayer::getSongName() const {
+	return _songName;
+}
+
+void MUSPlayer::setInstrument(uint8 voice, uint8 instrument) {
+	if (instrument >= _timbres.size())
+		return;
+
+	setVoiceTimbre(voice, _timbres[instrument].params);
+}
+
+} // End of namespace Gob
diff --git a/engines/gob/sound/musplayer.h b/engines/gob/sound/musplayer.h
new file mode 100644
index 0000000..6cc2a2d
--- /dev/null
+++ b/engines/gob/sound/musplayer.h
@@ -0,0 +1,109 @@
+/* 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 GOB_SOUND_MUSPLAYER_H
+#define GOB_SOUND_MUSPLAYER_H
+
+#include "common/str.h"
+#include "common/array.h"
+
+#include "gob/sound/adlib.h"
+
+namespace Common {
+	class SeekableReadStream;
+}
+
+namespace Gob {
+
+/** A player for the AdLib MUS format, with the instrument information in SND files.
+ *
+ *  In the Gob engine, those files are usually named .MDY and .TBR instead.
+ */
+class MUSPlayer : public AdLib {
+public:
+	MUSPlayer(Audio::Mixer &mixer);
+	~MUSPlayer();
+
+	/** Load the instruments (.SND or .TBR) */
+	bool loadSND(Common::SeekableReadStream &snd);
+	/** Load the melody (.MUS or .MDY) */
+	bool loadMUS(Common::SeekableReadStream &mus);
+
+	void unload();
+
+	uint32 getSongID() const;
+	const Common::String &getSongName() const;
+
+protected:
+	// AdLib interface
+	uint32 pollMusic(bool first);
+	void rewind();
+
+private:
+	struct Timbre {
+		Common::String name;
+
+		uint16 params[kOperatorsPerVoice * kParamCount];
+	};
+
+	Common::Array<Timbre> _timbres;
+
+	byte  *_songData;
+	uint32 _songDataSize;
+
+	const byte *_playPos;
+
+	uint32 _songID;
+	Common::String _songName;
+
+	uint8 _ticksPerBeat;
+	uint8 _beatsPerMeasure;
+
+	uint8 _soundMode;
+	uint8 _pitchBendRange;
+
+	uint16 _baseTempo;
+
+	uint16 _tempo;
+
+	byte _lastCommand;
+
+
+	void unloadSND();
+	void unloadMUS();
+
+	bool readSNDHeader (Common::SeekableReadStream &snd, int &timbreCount, int &timbrePos);
+	bool readSNDTimbres(Common::SeekableReadStream &snd, int  timbreCount, int  timbrePos);
+
+	bool readMUSHeader(Common::SeekableReadStream &mus);
+	bool readMUSSong  (Common::SeekableReadStream &mus);
+
+	uint32 getSampleDelay(uint16 delay) const;
+	void setInstrument(uint8 voice, uint8 instrument);
+	void skipToTiming();
+
+	static bool readString(Common::SeekableReadStream &stream, Common::String &string, byte *buffer, uint size);
+};
+
+} // End of namespace Gob
+
+#endif // GOB_SOUND_MUSPLAYER_H
diff --git a/engines/gob/sound/sound.cpp b/engines/gob/sound/sound.cpp
index bfe0394..f14e9b1 100644
--- a/engines/gob/sound/sound.cpp
+++ b/engines/gob/sound/sound.cpp
@@ -30,7 +30,8 @@
 
 #include "gob/sound/pcspeaker.h"
 #include "gob/sound/soundblaster.h"
-#include "gob/sound/adlib.h"
+#include "gob/sound/adlplayer.h"
+#include "gob/sound/musplayer.h"
 #include "gob/sound/infogrames.h"
 #include "gob/sound/protracker.h"
 #include "gob/sound/cdrom.h"
@@ -131,10 +132,7 @@ void Sound::sampleFree(SoundDesc *sndDesc, bool noteAdLib, int index) {
 		if (noteAdLib) {
 			if (_adlPlayer)
 				if ((index == -1) || (_adlPlayer->getIndex() == index))
-					_adlPlayer->stopPlay();
-			if (_mdyPlayer)
-				if ((index == -1) || (_mdyPlayer->getIndex() == index))
-					_mdyPlayer->stopPlay();
+					_adlPlayer->unload();
 		}
 
 	} else {
@@ -235,7 +233,17 @@ bool Sound::adlibLoadADL(const char *fileName) {
 
 	debugC(1, kDebugSound, "AdLib: Loading ADL data (\"%s\")", fileName);
 
-	return _adlPlayer->load(fileName);
+	Common::SeekableReadStream *stream = _vm->_dataIO->getFile(fileName);
+	if (!stream) {
+		warning("Can't open ADL file \"%s\"", fileName);
+		return false;
+	}
+
+	bool loaded = _adlPlayer->load(*stream);
+
+	delete stream;
+
+	return loaded;
 }
 
 bool Sound::adlibLoadADL(byte *data, uint32 size, int index) {
@@ -267,7 +275,7 @@ bool Sound::adlibLoadMDY(const char *fileName) {
 		return false;
 
 	if (!_mdyPlayer)
-		_mdyPlayer = new MDYPlayer(*_vm->_mixer);
+		_mdyPlayer = new MUSPlayer(*_vm->_mixer);
 
 	debugC(1, kDebugSound, "AdLib: Loading MDY data (\"%s\")", fileName);
 
@@ -277,7 +285,7 @@ bool Sound::adlibLoadMDY(const char *fileName) {
 		return false;
 	}
 
-	bool loaded = _mdyPlayer->loadMDY(*stream);
+	bool loaded = _mdyPlayer->loadMUS(*stream);
 
 	delete stream;
 
@@ -289,7 +297,7 @@ bool Sound::adlibLoadTBR(const char *fileName) {
 		return false;
 
 	if (!_mdyPlayer)
-		_mdyPlayer = new MDYPlayer(*_vm->_mixer);
+		_mdyPlayer = new MUSPlayer(*_vm->_mixer);
 
 	Common::SeekableReadStream *stream = _vm->_dataIO->getFile(fileName);
 	if (!stream) {
@@ -299,7 +307,7 @@ bool Sound::adlibLoadTBR(const char *fileName) {
 
 	debugC(1, kDebugSound, "AdLib: Loading MDY instruments (\"%s\")", fileName);
 
-	bool loaded = _mdyPlayer->loadTBR(*stream);
+	bool loaded = _mdyPlayer->loadSND(*stream);
 
 	delete stream;
 
@@ -316,11 +324,8 @@ void Sound::adlibPlayTrack(const char *trackname) {
 	if (_adlPlayer->isPlaying())
 		return;
 
-	debugC(1, kDebugSound, "AdLib: Playing ADL track \"%s\"", trackname);
-
-	_adlPlayer->unload();
-	_adlPlayer->load(trackname);
-	_adlPlayer->startPlay();
+	if (adlibLoadADL(trackname))
+		adlibPlay();
 }
 
 void Sound::adlibPlayBgMusic() {
@@ -331,7 +336,7 @@ void Sound::adlibPlayBgMusic() {
 		_adlPlayer = new ADLPlayer(*_vm->_mixer);
 
 	static const char *const tracksMac[] = {
-//		"musmac1.adl", // TODO: This track isn't played correctly at all yet
+//		"musmac1.adl", // This track seems to be missing instruments...
 		"musmac2.adl",
 		"musmac3.adl",
 		"musmac4.adl",
@@ -398,13 +403,11 @@ int Sound::adlibGetIndex() const {
 
 	if (_adlPlayer)
 		return _adlPlayer->getIndex();
-	if (_mdyPlayer)
-		return _mdyPlayer->getIndex();
 
 	return -1;
 }
 
-bool Sound::adlibGetRepeating() const {
+int32 Sound::adlibGetRepeating() const {
 	if (!_hasAdLib)
 		return false;
 
diff --git a/engines/gob/sound/sound.h b/engines/gob/sound/sound.h
index 585cf36..ea7d639 100644
--- a/engines/gob/sound/sound.h
+++ b/engines/gob/sound/sound.h
@@ -32,7 +32,7 @@ class GobEngine;
 class PCSpeaker;
 class SoundBlaster;
 class ADLPlayer;
-class MDYPlayer;
+class MUSPlayer;
 class Infogrames;
 class Protracker;
 class CDROM;
@@ -92,7 +92,7 @@ public:
 	bool adlibIsPlaying() const;
 
 	int adlibGetIndex() const;
-	bool adlibGetRepeating() const;
+	int32 adlibGetRepeating() const;
 
 	void adlibSetRepeating(int32 repCount);
 
@@ -145,14 +145,23 @@ private:
 
 	SoundDesc _sounds[kSoundsCount];
 
+	// Speaker
 	PCSpeaker *_pcspeaker;
+
+	// PCM based
 	SoundBlaster *_blaster;
+	BackgroundAtmosphere *_bgatmos;
+
+	// AdLib
+	MUSPlayer *_mdyPlayer;
 	ADLPlayer *_adlPlayer;
-	MDYPlayer *_mdyPlayer;
+
+	// Amiga Paula
 	Infogrames *_infogrames;
 	Protracker *_protracker;
+
+	// Audio CD
 	CDROM *_cdrom;
-	BackgroundAtmosphere *_bgatmos;
 };
 
 } // End of namespace Gob


Commit: 8548dea13d56e1d69b2f4743361932cf14dd6eee
    https://github.com/scummvm/scummvm/commit/8548dea13d56e1d69b2f4743361932cf14dd6eee
Author: Sven Hesse (drmccoy at users.sourceforge.net)
Date: 2012-06-10T20:18:06-07:00

Commit Message:
GOB: Hook up the MDY player in Fascination

Changed paths:
    engines/gob/init_fascin.cpp
    engines/gob/inter_fascin.cpp



diff --git a/engines/gob/init_fascin.cpp b/engines/gob/init_fascin.cpp
index b87d816..e6d82fa 100644
--- a/engines/gob/init_fascin.cpp
+++ b/engines/gob/init_fascin.cpp
@@ -44,10 +44,10 @@ void Init_Fascination::updateConfig() {
 }
 
 void Init_Fascination::initGame() {
-// HACK - Suppress ADLIB_FLAG as the MDY/TBR player is not working. suppress
-// the PC Speaker too, as the script checks in the intro for it's presence
+// HACK - Suppress
+// the PC Speaker, as the script checks in the intro for it's presence
 // to play or not some noices.
-	_vm->_global->_soundFlags = MIDI_FLAG | BLASTER_FLAG;
+	_vm->_global->_soundFlags = MIDI_FLAG | BLASTER_FLAG | ADLIB_FLAG;
 
 	Init::initGame();
 }
diff --git a/engines/gob/inter_fascin.cpp b/engines/gob/inter_fascin.cpp
index 081b48f..001ec06 100644
--- a/engines/gob/inter_fascin.cpp
+++ b/engines/gob/inter_fascin.cpp
@@ -248,12 +248,11 @@ void Inter_Fascination::oFascin_playTira(OpGobParams &params) {
 void Inter_Fascination::oFascin_loadExtasy(OpGobParams &params) {
 	_vm->_sound->adlibLoadTBR("extasy.tbr");
 	_vm->_sound->adlibLoadMDY("extasy.mdy");
+	_vm->_sound->adlibSetRepeating(-1);
 }
 
 void Inter_Fascination::oFascin_adlibPlay(OpGobParams &params) {
-#ifdef ENABLE_FASCIN_ADLIB
 	_vm->_sound->adlibPlay();
-#endif
 }
 
 void Inter_Fascination::oFascin_adlibStop(OpGobParams &params) {


Commit: a64e8a6d30fbef1de52684b75bb6f4536b28d988
    https://github.com/scummvm/scummvm/commit/a64e8a6d30fbef1de52684b75bb6f4536b28d988
Author: Sven Hesse (drmccoy at users.sourceforge.net)
Date: 2012-06-10T20:18:07-07:00

Commit Message:
GOB: Hook up the MDY player in Geisha

Changed paths:
    engines/gob/init.h
    engines/gob/init_geisha.cpp
    engines/gob/inter_geisha.cpp



diff --git a/engines/gob/init.h b/engines/gob/init.h
index 946a3fa..ac460fd 100644
--- a/engines/gob/init.h
+++ b/engines/gob/init.h
@@ -62,7 +62,6 @@ public:
 	~Init_Geisha();
 
 	void initVideo();
-	void initGame();
 };
 
 class Init_v2 : public Init_v1 {
diff --git a/engines/gob/init_geisha.cpp b/engines/gob/init_geisha.cpp
index b5bbcff..01081a5 100644
--- a/engines/gob/init_geisha.cpp
+++ b/engines/gob/init_geisha.cpp
@@ -44,11 +44,4 @@ void Init_Geisha::initVideo() {
 	_vm->_draw->_transparentCursor =  1;
 }
 
-void Init_Geisha::initGame() {
-	// HACK - Since the MDY/TBR player is not working, claim we have no AdLib
-	_vm->_global->_soundFlags = 0;
-
-	Init::initGame();
-}
-
 } // End of namespace Gob
diff --git a/engines/gob/inter_geisha.cpp b/engines/gob/inter_geisha.cpp
index 75204a3..8a4d424 100644
--- a/engines/gob/inter_geisha.cpp
+++ b/engines/gob/inter_geisha.cpp
@@ -298,9 +298,8 @@ void Inter_Geisha::oGeisha_loadTitleMusic(OpGobParams &params) {
 }
 
 void Inter_Geisha::oGeisha_playMusic(OpGobParams &params) {
-	// TODO: The MDYPlayer is still broken!
-	warning("Geisha Stub: oGeisha_playMusic");
-	// _vm->_sound->adlibPlay();
+	_vm->_sound->adlibSetRepeating(-1);
+	_vm->_sound->adlibPlay();
 }
 
 void Inter_Geisha::oGeisha_stopMusic(OpGobParams &params) {


Commit: 385bd7c6a045b1a4c7b64ebef4471805020bdadd
    https://github.com/scummvm/scummvm/commit/385bd7c6a045b1a4c7b64ebef4471805020bdadd
Author: Sven Hesse (drmccoy at users.sourceforge.net)
Date: 2012-06-10T20:25:04-07:00

Commit Message:
NEWS: Mention Gob news for 1.5.0

Changed paths:
    NEWS



diff --git a/NEWS b/NEWS
index 7ce4f25..acff8b8 100644
--- a/NEWS
+++ b/NEWS
@@ -45,6 +45,11 @@ For a more comprehensive changelog of the latest experimental code, see:
  Cine:
    - Implemented Roland MT-32 output driver.
 
+ Gob:
+   - Fixed a crash in Lost in Time
+   - Rewrote the AdLib player. Enabled the now working MDY player in
+     Fascination and Geisha.
+
  SCUMM:
    - Added support for the Macintosh version of SPY Fox in Hold the Mustard.
    - Added a difficulty selection dialog for Loom FM-TOWNS.






More information about the Scummvm-git-logs mailing list