[Scummvm-cvs-logs] scummvm master -> c2161b95640779677f186786ed1bbfad34da074c

sev- sev at scummvm.org
Thu Sep 12 19:58:54 CEST 2013


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

Summary:
80ab4c5242 SCUMM: Implement original AD AdLib output.
c2161b9564 Merge pull request #360 from lordhoto/scumm-ad


Commit: 80ab4c524295ba2cdec3a297919653eda4133ed3
    https://github.com/scummvm/scummvm/commit/80ab4c524295ba2cdec3a297919653eda4133ed3
Author: Johannes Schickel (lordhoto at scummvm.org)
Date: 2013-07-23T18:28:47-07:00

Commit Message:
SCUMM: Implement original AD AdLib output.

This implements the original AD output and enables it for Indy3 and Loom DOS.
It is not enabled for Monkey Island DOS because it would break multi MIDI
support. However, there are also drawbacks for Indy3. In the catacombs
we were able to play sfx (Indy walking around) and the background music
at once. This was not supported in the original player and thus also does
not work with this reimplementation.

This fixes bug #2027877 "INDY3: Non-Looping Sound Effects".
This fixes bug #1159581 "ADLIB: Adlib Emulation doesn't Respect Volume Settings"
for Indy3 and Loom.

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



diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 28884d7..a377ad3 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -35,6 +35,7 @@ MODULE_OBJS := \
 	midiparser_ro.o \
 	object.o \
 	palette.o \
+	player_ad.o \
 	player_apple2.o \
 	player_mac.o \
 	player_mod.o \
diff --git a/engines/scumm/player_ad.cpp b/engines/scumm/player_ad.cpp
new file mode 100644
index 0000000..ed368af
--- /dev/null
+++ b/engines/scumm/player_ad.cpp
@@ -0,0 +1,959 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/player_ad.h"
+#include "scumm/imuse/imuse.h"
+#include "scumm/scumm.h"
+#include "scumm/resource.h"
+
+#include "audio/fmopl.h"
+
+#include "common/textconsole.h"
+#include "common/config-manager.h"
+
+namespace Scumm {
+
+#define AD_CALLBACK_FREQUENCY 472
+
+Player_AD::Player_AD(ScummEngine *scumm, Audio::Mixer *mixer)
+	: _vm(scumm), _mixer(mixer), _rate(mixer->getOutputRate()) {
+	_opl2 = OPL::Config::create();
+	if (!_opl2->init(_rate)) {
+		error("Could not initialize OPL2 emulator");
+	}
+
+	_samplesPerCallback = _rate / AD_CALLBACK_FREQUENCY;
+	_samplesPerCallbackRemainder = _rate % AD_CALLBACK_FREQUENCY;
+	_samplesTillCallback = 0;
+	_samplesTillCallbackRemainder = 0;
+
+	memset(_registerBackUpTable, 0, sizeof(_registerBackUpTable));
+	writeReg(0x01, 0x00);
+	writeReg(0xBD, 0x00);
+	writeReg(0x08, 0x00);
+	writeReg(0x01, 0x20);
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+	_engineMusicTimer = 0;
+	_soundPlaying = -1;
+
+	_curOffset = 0;
+
+	_sfxTimer = 4;
+	_rndSeed = 1;
+
+	memset(_channels, 0, sizeof(_channels));
+	memset(_sfxResource, 0, sizeof(_sfxResource));
+	memset(_sfxPriority, 0, sizeof(_sfxPriority));
+}
+
+Player_AD::~Player_AD() {
+	_mixer->stopHandle(_soundHandle);
+
+	stopAllSounds();
+	Common::StackLock lock(_mutex);
+	delete _opl2;
+	_opl2 = 0;
+}
+
+void Player_AD::setMusicVolume(int vol) {
+	// HACK: We ignore the parameter and set up the volume specified in the
+	// config manager. This allows us to differentiate between music and sfx
+	// volume changes.
+	setupVolume();
+}
+
+void Player_AD::startSound(int sound) {
+	Common::StackLock lock(_mutex);
+
+	// Query the sound resource
+	const byte *res = _vm->getResourceAddress(rtSound, sound);
+
+	if (res[2] == 0x80) {
+		// Stop the current sounds
+		stopAllSounds();
+
+		// Lock the new music resource
+		_soundPlaying = sound;
+		_vm->_res->lock(rtSound, _soundPlaying);
+
+		// Start the new music resource
+		_resource = res;
+		startMusic();
+	} else {
+		// Only try to start a sfx when no music is playing.
+		if (_soundPlaying == -1) {
+			const byte priority = res[0];
+			const byte channel  = res[1];
+
+			// Check for out of bounds access
+			if (channel >= 3) {
+				warning("AdLib sfx resource %d uses channel %d", sound, channel);
+				return;
+			}
+
+			// Check whether the channel is free or the priority of the new
+			// sfx resource is above the old one.
+			if (_channels[channel * 3 + 0].state
+			    || _channels[channel * 3 + 1].state
+			    || _channels[channel * 3 + 2].state) {
+				if (_sfxPriority[channel] > priority) {
+					return;
+				}
+			}
+
+			// Lock the new resource
+			_sfxResource[channel] = sound;
+			_sfxPriority[channel] = priority;
+			_vm->_res->lock(rtSound, sound);
+
+			// Start the actual sfx resource
+			_resource = res;
+			startSfx();
+		}
+	}
+
+	// Setup the sound volume
+	setupVolume();
+}
+
+void Player_AD::stopSound(int sound) {
+	Common::StackLock lock(_mutex);
+
+	if (sound == _soundPlaying) {
+		stopAllSounds();
+	} else {
+		for (int i = 0; i < 3; ++i) {
+			if (_sfxResource[i] == sound) {
+				if (_channels[i * 3 + 0].state
+				    || _channels[i * 3 + 1].state
+				    || _channels[i * 3 + 2].state) {
+					// Unlock the sound resource
+					_vm->_res->unlock(rtSound, sound);
+
+					// Stop the actual sfx playback
+					_channels[i * 3 + 0].state = 0;
+					_channels[i * 3 + 1].state = 0;
+					_channels[i * 3 + 2].state = 0;
+					clearChannel(i * 3 + 0);
+					clearChannel(i * 3 + 1);
+					clearChannel(i * 3 + 2);
+				}
+			}
+		}
+	}
+}
+
+void Player_AD::stopAllSounds() {
+	Common::StackLock lock(_mutex);
+
+	// Unlock the music resource if present
+	if (_soundPlaying != -1) {
+		_vm->_res->unlock(rtSound, _soundPlaying);
+		_soundPlaying = -1;
+	}
+
+	// Stop the music playback
+	_curOffset = 0;
+
+	// Unloack all used sfx resources
+	for (int i = 0; i < 3; ++i) {
+		if (_channels[i * 3 + 0].state || _channels[i * 3 + 1].state || _channels[i * 3 + 2].state) {
+			_vm->_res->unlock(rtSound, _sfxResource[i]);
+		}
+	}
+
+	// Reset all the sfx channels
+	for (int i = 0; i < 9; ++i) {
+		_channels[i].state = 0;
+		clearChannel(i);
+	}
+
+	writeReg(0xBD, 0x00);
+}
+
+int Player_AD::getMusicTimer() {
+	return _engineMusicTimer;
+}
+
+int Player_AD::getSoundStatus(int sound) const {
+	return (sound == _soundPlaying);
+}
+
+void Player_AD::saveLoadWithSerializer(Serializer *ser) {
+	Common::StackLock lock(_mutex);
+
+	if (ser->getVersion() < VER(95)) {
+		IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL);
+		dummyImuse->save_or_load(ser, _vm, false);
+		delete dummyImuse;
+		return;
+	}
+
+	// TODO: Be nicer than the original and save the data to continue the
+	// currently played sound resources on load?
+}
+
+int Player_AD::readBuffer(int16 *buffer, const int numSamples) {
+	Common::StackLock lock(_mutex);
+
+	int len = numSamples;
+
+	while (len > 0) {
+		if (!_samplesTillCallback) {
+			// Run the update callback for music or sfx depending on which is
+			// active.
+			if (_curOffset) {
+				updateMusic();
+			} else {
+				updateSfx();
+			}
+
+			_samplesTillCallback = _samplesPerCallback;
+			_samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
+			if (_samplesTillCallbackRemainder >= AD_CALLBACK_FREQUENCY) {
+				++_samplesTillCallback;
+				_samplesTillCallbackRemainder -= AD_CALLBACK_FREQUENCY;
+			}
+		}
+
+		const int samplesToRead = MIN(len, _samplesTillCallback);
+		_opl2->readBuffer(buffer, samplesToRead);
+
+		buffer += samplesToRead;
+		len -= samplesToRead;
+		_samplesTillCallback -= samplesToRead;
+	}
+
+	return numSamples;
+}
+
+void Player_AD::setupVolume() {
+	// Setup the correct volume
+	int soundVolumeMusic = CLIP<int>(ConfMan.getInt("music_volume"), 0, Audio::Mixer::kMaxChannelVolume);
+	int soundVolumeSfx = CLIP<int>(ConfMan.getInt("sfx_volume"), 0, Audio::Mixer::kMaxChannelVolume);
+	if (ConfMan.hasKey("mute")) {
+		if (ConfMan.getBool("mute")) {
+			soundVolumeMusic = 0;
+			soundVolumeSfx = 0;
+		}
+	}
+
+	// In case a music is being played set the music volume. Set the sfx
+	// volume otherwise. This is safe because in the latter case either
+	// sfx are playing or there is no sound being played at all.
+	if (_soundPlaying != -1) {
+		_mixer->setChannelVolume(_soundHandle, soundVolumeMusic);
+	} else {
+		_mixer->setChannelVolume(_soundHandle, soundVolumeSfx);
+	}
+}
+
+void Player_AD::writeReg(int r, int v) {
+	if (r >= 0 && r < ARRAYSIZE(_registerBackUpTable)) {
+		_registerBackUpTable[r] = v;
+	}
+	_opl2->writeReg(r, v);
+}
+
+uint8 Player_AD::readReg(int r) const {
+	if (r >= 0 && r < ARRAYSIZE(_registerBackUpTable)) {
+		return _registerBackUpTable[r];
+	} else {
+		return 0;
+	}
+}
+
+void Player_AD::setupChannel(const uint channel, const byte *instrOffset) {
+	instrOffset += 2;
+	writeReg(0xC0 + channel, *instrOffset++);
+	setupOperator(_operatorOffsetTable[channel * 2 + 0], instrOffset);
+	setupOperator(_operatorOffsetTable[channel * 2 + 1], instrOffset);
+}
+
+void Player_AD::setupOperator(const uint opr, const byte *&instrOffset) {
+	writeReg(0x20 + opr, *instrOffset++);
+	writeReg(0x40 + opr, *instrOffset++);
+	writeReg(0x60 + opr, *instrOffset++);
+	writeReg(0x80 + opr, *instrOffset++);
+	writeReg(0xE0 + opr, *instrOffset++);
+}
+
+const int Player_AD::_operatorOffsetTable[18] = {
+	 0,  3,  1,  4,
+	 2,  5,  8, 11,
+	 9, 12, 10, 13,
+	16, 19, 17, 20,
+	18, 21
+};
+
+// Music
+
+void Player_AD::startMusic() {
+	memset(_instrumentOffset, 0, sizeof(_instrumentOffset));
+	memset(_channelLastEvent, 0, sizeof(_channelLastEvent));
+	memset(_channelFrequency, 0, sizeof(_channelFrequency));
+	memset(_channelB0Reg, 0, sizeof(_channelB0Reg));
+
+	_voiceChannels = 0;
+	uint instruments = _resource[10];
+	for (uint i = 0; i < instruments; ++i) {
+		const int instrIndex = _resource[11 + i] - 1;
+		if (0 <= instrIndex && instrIndex < 16) {
+			_instrumentOffset[instrIndex] = i * 16 + 16 + 3;
+			_voiceChannels |= _resource[_instrumentOffset[instrIndex] + 13];
+		}
+	}
+
+	if (_voiceChannels) {
+		_mdvdrState = 0x20;
+		_voiceChannels = 6;
+	} else {
+		_mdvdrState = 0;
+		_voiceChannels = 9;
+	}
+
+	_curOffset = 0x93;
+	// TODO: is this the same for Loom?
+	_nextEventTimer = 40;
+	_engineMusicTimer = 0;
+	_internalMusicTimer = 0;
+	_musicTimer = 0;
+
+	writeReg(0xBD, _mdvdrState);
+
+	const bool isLoom = (_vm->_game.id == GID_LOOM);
+	_timerLimit = isLoom ? 473 : 256;
+	_musicTicks = _resource[3] * (isLoom ? 2 : 1);
+	_loopFlag = (_resource[4] == 0);
+	_musicLoopStart = READ_LE_UINT16(_resource + 5);
+}
+
+void Player_AD::updateMusic() {
+	_musicTimer += _musicTicks;
+	if (_musicTimer < _timerLimit) {
+		return;
+	}
+	_musicTimer -= _timerLimit;
+
+	++_internalMusicTimer;
+	if (_internalMusicTimer > 120) {
+		_internalMusicTimer = 0;
+		++_engineMusicTimer;
+	}
+
+	--_nextEventTimer;
+	if (_nextEventTimer) {
+		return;
+	}
+
+	while (true) {
+		uint command = _resource[_curOffset++];
+		if (command == 0xFF) {
+			// META EVENT
+			// Get the command number.
+			command = _resource[_curOffset++];
+			if (command == 47) {
+				// End of track
+				if (_loopFlag) {
+					// In case the track is looping jump to the start.
+					_curOffset = _musicLoopStart;
+					_nextEventTimer = 0;
+				} else {
+					// Otherwise completely stop playback.
+					stopAllSounds();
+				}
+				return;
+			} else if (command == 88) {
+				// This is proposedly a debug information insertion. The CMS
+				// player code handles this differently, but is still using
+				// the same resources...
+				_curOffset += 5;
+			} else if (command == 81) {
+				// Change tempo. This is used exclusively in Loom.
+				const uint timing = _resource[_curOffset + 2] | (_resource[_curOffset + 1] << 8);
+				_musicTicks = 0x73000 / timing;
+				command = _resource[_curOffset++];
+				_curOffset += command;
+			} else {
+				// In case an unknown meta event occurs just skip over the
+				// data by using the length supplied.
+				command = _resource[_curOffset++];
+				_curOffset += command;
+			}
+		} else {
+			if (command >= 0x90) {
+				// NOTE ON
+				// Extract the channel number and save it in command.
+				command -= 0x90;
+
+				const uint instrOffset = _instrumentOffset[command];
+				if (instrOffset) {
+					if (_resource[instrOffset + 13] != 0) {
+						setupRhythm(_resource[instrOffset + 13], instrOffset);
+					} else {
+						int channel = findFreeChannel();
+						if (channel != -1) {
+							noteOff(channel);
+							setupChannel(channel, instrOffset);
+							_channelLastEvent[channel] = command + 0x90;
+							_channelFrequency[channel] = _resource[_curOffset];
+							setupFrequency(channel, _resource[_curOffset]);
+						}
+					}
+				}
+			} else {
+				// NOTE OFF
+				const uint note = _resource[_curOffset];
+				command += 0x10;
+
+				// Find the output channel which plays the note.
+				uint channel = 0xFF;
+				for (uint i = 0; i < _voiceChannels; ++i) {
+					if (_channelFrequency[i] == note && _channelLastEvent[i] == command) {
+						channel = i;
+						break;
+					}
+				}
+
+				if (channel != 0xFF) {
+					// In case a output channel playing the note was found,
+					// stop it.
+					noteOff(channel);
+				} else {
+					// In case there is no such note this will disable the
+					// rhythm instrument played on the channel.
+					command -= 0x90;
+					const uint instrOffset = _instrumentOffset[command];
+					if (instrOffset && _resource[instrOffset + 13] != 0) {
+						const uint rhythmInstr = _resource[instrOffset + 13];
+						if (rhythmInstr < 6) {
+							_mdvdrState &= _mdvdrTable[rhythmInstr] ^ 0xFF;
+							writeReg(0xBD, _mdvdrState);
+						}
+					}
+				}
+			}
+
+			_curOffset += 2;
+		}
+
+		// In case there is a delay till the next event stop handling.
+		if (_resource[_curOffset] != 0) {
+			break;
+		}
+		++_curOffset;
+	}
+
+	_nextEventTimer = _resource[_curOffset++];
+	if (_nextEventTimer & 0x80) {
+		_nextEventTimer -= 0x80;
+		_nextEventTimer <<= 7;
+		_nextEventTimer |= _resource[_curOffset++];
+	}
+
+	_nextEventTimer >>= (_vm->_game.id == GID_LOOM) ? 2 : 1;
+	if (!_nextEventTimer) {
+		_nextEventTimer = 1;
+	}
+}
+
+void Player_AD::noteOff(uint channel) {
+	_channelLastEvent[channel] = 0;
+	writeReg(0xB0 + channel, _channelB0Reg[channel] & 0xDF);
+}
+
+int Player_AD::findFreeChannel() {
+	for (uint i = 0; i < _voiceChannels; ++i) {
+		if (!_channelLastEvent[i]) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+void Player_AD::setupFrequency(uint channel, int8 frequency) {
+	frequency -= 31;
+	if (frequency < 0) {
+		frequency = 0;
+	}
+
+	uint octave = 0;
+	while (frequency >= 12) {
+		frequency -= 12;
+		++octave;
+	}
+
+	const uint noteFrequency = _noteFrequencies[frequency];
+	octave <<= 2;
+	octave |= noteFrequency >> 8;
+	octave |= 0x20;
+	writeReg(0xA0 + channel, noteFrequency & 0xFF);
+	_channelB0Reg[channel] = octave;
+	writeReg(0xB0 + channel, octave);
+}
+
+void Player_AD::setupRhythm(uint rhythmInstr, uint instrOffset) {
+	if (rhythmInstr == 1) {
+		setupChannel(6, instrOffset);
+		writeReg(0xA6, _resource[instrOffset++]);
+		writeReg(0xB6, _resource[instrOffset] & 0xDF);
+		_mdvdrState |= 0x10;
+		writeReg(0xBD, _mdvdrState);
+	} else if (rhythmInstr < 6) {
+		const byte *secondOperatorOffset = _resource + instrOffset + 8;
+		setupOperator(_rhythmOperatorTable[rhythmInstr], secondOperatorOffset);
+		writeReg(0xA0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset++]);
+		writeReg(0xB0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset++] & 0xDF);
+		writeReg(0xC0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset]);
+		_mdvdrState |= _mdvdrTable[rhythmInstr];
+		writeReg(0xBD, _mdvdrState);
+	}
+}
+
+const uint Player_AD::_noteFrequencies[12] = {
+	0x200, 0x21E, 0x23F, 0x261,
+	0x285, 0x2AB, 0x2D4, 0x300,
+	0x32E, 0x35E, 0x390, 0x3C7
+};
+
+const uint Player_AD::_mdvdrTable[6] = {
+	0x00, 0x10, 0x08, 0x04, 0x02, 0x01
+};
+
+const uint Player_AD::_rhythmOperatorTable[6] = {
+	0x00, 0x00, 0x14, 0x12, 0x15, 0x11
+};
+
+const uint Player_AD::_rhythmChannelTable[6] = {
+	0x00, 0x00, 0x07, 0x08, 0x08, 0x07
+};
+
+// SFX
+
+void Player_AD::startSfx() {
+	writeReg(0xBD, 0x00);
+
+	// The second byte of the resource defines the logical channel where
+	// the sound effect should be played.
+	const int startChannel = _resource[1] * 3;
+
+	// Clear the channel.
+	_channels[startChannel + 0].state = 0;
+	_channels[startChannel + 1].state = 0;
+	_channels[startChannel + 2].state = 0;
+
+	clearChannel(startChannel + 0);
+	clearChannel(startChannel + 1);
+	clearChannel(startChannel + 2);
+
+	// Set up the first channel to pick up playback.
+	_channels[startChannel].currentOffset = _channels[startChannel].startOffset = _resource + 2;
+	_channels[startChannel].state = 1;
+
+	// Scan for the start of the other channels and set them up if required.
+	int curChannel = startChannel + 1;
+	const byte *bufferPosition = _resource + 2;
+	uint8 command = 0;
+	while ((command = *bufferPosition) != 0xFF) {
+		switch (command) {
+		case 1:
+			// INSTRUMENT DEFINITION
+			bufferPosition += 15;
+			break;
+
+		case 2:
+			// NOTE DEFINITION
+			bufferPosition += 11;
+			break;
+
+		case 0x80:
+			// LOOP
+			bufferPosition += 1;
+			break;
+
+		default:
+			// START OF CHANNEL
+			bufferPosition += 1;
+			_channels[curChannel].currentOffset = bufferPosition;
+			_channels[curChannel].startOffset = bufferPosition;
+			_channels[curChannel].state = 1;
+			++curChannel;
+			break;
+		}
+	}
+}
+
+void Player_AD::updateSfx() {
+	if (--_sfxTimer) {
+		return;
+	}
+	_sfxTimer = 4;
+
+	for (int i = 0; i <= 9; ++i) {
+		if (!_channels[i].state) {
+			continue;
+		}
+
+		updateChannel(i);
+	}
+}
+
+void Player_AD::clearChannel(int channel) {
+	writeReg(0xA0 + channel, 0x00);
+	writeReg(0xB0 + channel, 0x00);
+}
+
+void Player_AD::updateChannel(int channel) {
+	if (_channels[channel].state == 1) {
+		parseSlot(channel);
+	} else {
+		updateSlot(channel);
+	}
+}
+
+void Player_AD::parseSlot(int channel) {
+	while (true) {
+		const byte *curOffset = _channels[channel].currentOffset;
+
+		switch (*curOffset) {
+		case 1:
+			// INSTRUMENT DEFINITION
+			++curOffset;
+			_channels[channel].instrumentData[0] = *(curOffset + 0);
+			_channels[channel].instrumentData[1] = *(curOffset + 2);
+			_channels[channel].instrumentData[2] = *(curOffset + 9);
+			_channels[channel].instrumentData[3] = *(curOffset + 8);
+			_channels[channel].instrumentData[4] = *(curOffset + 4);
+			_channels[channel].instrumentData[5] = *(curOffset + 3);
+			_channels[channel].instrumentData[6] = 0;
+
+			setupChannel(channel, curOffset);
+
+			writeReg(0xA0 + channel, *(curOffset + 0));
+			writeReg(0xB0 + channel, *(curOffset + 1) & 0xDF);
+
+			_channels[channel].currentOffset += 15;
+			break;
+
+		case 2:
+			// NOTE DEFINITION
+			++curOffset;
+			_channels[channel].state = 2;
+			noteOffOn(channel);
+			parseNote(channel, 0, curOffset);
+			parseNote(channel, 1, curOffset);
+			return;
+
+		case 0x80:
+			// LOOP
+			_channels[channel].currentOffset = _channels[channel].startOffset;
+			break;
+
+		default:
+			// START OF CHANNEL
+			// When we encounter a start of another channel while playback
+			// it means that the current channel is finished. Thus, we will
+			// stop it.
+			clearChannel(channel);
+			_channels[channel].state = 0;
+
+			// If no channel of the sound effect is playing anymore, unlock
+			// the resource.
+			channel /= 3;
+			if (!_channels[channel + 0].state
+			    && !_channels[channel + 1].state
+			    && !_channels[channel + 2].state) {
+				_vm->_res->unlock(rtSound, _sfxResource[channel]);
+			}
+			return;
+		}
+	}
+}
+
+void Player_AD::updateSlot(int channel) {
+	const byte *curOffset = _channels[channel].currentOffset + 1;
+
+	for (int num = 0; num <= 1; ++num, curOffset += 5) {
+		if (!(*curOffset & 0x80)) {
+			continue;
+		}
+
+		const int note = channel * 2 + num;
+		bool updateNote = false;
+
+		if (_notes[note].state == 2) {
+			if (!--_notes[note].sustainTimer) {
+				updateNote = true;
+			}
+		} else {
+			updateNote = processNoteEnvelope(note, _notes[note].instrumentValue);
+
+			if (_notes[note].bias) {
+				writeRegisterSpecial(note, _notes[note].bias - _notes[note].instrumentValue, *curOffset & 0x07);
+			} else {
+				writeRegisterSpecial(note, _notes[note].instrumentValue, *curOffset & 0x07);
+			}
+		}
+
+		if (updateNote) {
+			if (processNote(note, curOffset)) {
+				if (!(*curOffset & 0x08)) {
+					_channels[channel].currentOffset += 11;
+					_channels[channel].state = 1;
+					continue;
+				} else if (*curOffset & 0x10) {
+					noteOffOn(channel);
+				}
+
+				_notes[note].state = -1;
+				processNote(note, curOffset);
+			}
+		}
+
+		if ((*curOffset & 0x20) && !--_notes[note].playTime) {
+			_channels[channel].currentOffset += 11;
+			_channels[channel].state = 1;
+		}
+	}
+}
+
+void Player_AD::parseNote(int channel, int num, const byte *offset) {
+	if (num) {
+		offset += 5;
+	}
+
+	if (*offset & 0x80) {
+		const int note = channel * 2 + num;
+		_notes[note].state = -1;
+		processNote(note, offset);
+		_notes[note].playTime = 0; 
+
+		if (*offset & 0x20) {
+			_notes[note].playTime = (*(offset + 4) >> 4) * 118;
+			_notes[note].playTime += (*(offset + 4) & 0x0F) * 8;
+		}
+	}
+}
+
+bool Player_AD::processNote(int note, const byte *offset) {
+	if (++_notes[note].state == 4) {
+		return true;
+	}
+
+	const int instrumentDataOffset = *offset & 0x07;
+	_notes[note].bias = _noteBiasTable[instrumentDataOffset];
+
+	uint8 instrumentDataValue = 0;
+	if (_notes[note].state == 0) {
+		instrumentDataValue = _channels[note / 2].instrumentData[instrumentDataOffset];
+	}
+
+	uint8 noteInstrumentValue = readRegisterSpecial(note, instrumentDataValue, instrumentDataOffset);
+	if (_notes[note].bias) {
+		noteInstrumentValue = _notes[note].bias - noteInstrumentValue;
+	}
+	_notes[note].instrumentValue = noteInstrumentValue;
+
+	if (_notes[note].state == 2) {
+		_notes[note].sustainTimer = _numStepsTable[*(offset + 3) >> 4];
+
+		if (*offset & 0x40) {
+			_notes[note].sustainTimer = (((getRnd() << 8) * _notes[note].sustainTimer) >> 16) + 1;
+		}
+	} else {
+		int timer1, timer2;
+		if (_notes[note].state == 3) {
+			timer1 = *(offset + 3) & 0x0F;
+			timer2 = 0;
+		} else {
+			timer1 = *(offset + _notes[note].state + 1) >> 4;
+			timer2 = *(offset + _notes[note].state + 1) & 0x0F;
+		}
+
+		int adjustValue = ((_noteAdjustTable[timer2] * _noteAdjustScaleTable[instrumentDataOffset]) >> 16) - noteInstrumentValue;
+		setupNoteEnvelopeState(note, _numStepsTable[timer1], adjustValue);
+	}
+
+	return false;
+}
+
+void Player_AD::noteOffOn(int channel) {
+	const uint8 regValue = readReg(0xB0 | channel);
+	writeReg(0xB0 | channel, regValue & 0xDF);
+	writeReg(0xB0 | channel, regValue | 0x20);
+}
+
+void Player_AD::writeRegisterSpecial(int note, uint8 value, int offset) {
+	if (offset == 6) {
+		return;
+	}
+
+	// Division by 2 extracts the channel number out of the note.
+	note /= 2;
+
+	uint8 regNum;
+	if (_useOperatorTable[offset]) {
+		regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + note * 2];
+	} else {
+		regNum = _channelOffsetTable[note];
+	}
+
+	regNum += _baseRegisterTable[offset];
+
+	uint8 regValue = readReg(regNum) & (~_registerMaskTable[offset]);
+	regValue |= value << _registerShiftTable[offset];
+
+	writeReg(regNum, regValue);
+}
+
+uint8 Player_AD::readRegisterSpecial(int note, uint8 defaultValue, int offset) {
+	if (offset == 6) {
+		return 0;
+	}
+
+	// Division by 2 extracts the channel number out of the note.
+	note /= 2;
+
+	uint8 regNum;
+	if (_useOperatorTable[offset]) {
+		regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + note * 2];
+	} else {
+		regNum = _channelOffsetTable[note];
+	}
+
+	regNum += _baseRegisterTable[offset];
+
+	uint8 regValue;
+	if (defaultValue) {
+		regValue = defaultValue;
+	} else {
+		regValue = readReg(regNum);
+	}
+
+	regValue &= _registerMaskTable[offset];
+	regValue >>= _registerShiftTable[offset];
+
+	return regValue;
+}
+
+void Player_AD::setupNoteEnvelopeState(int note, int steps, int adjust) {
+	_notes[note].preIncrease = 0;
+	if (ABS(adjust) > steps) {
+		_notes[note].preIncrease = 1;
+		_notes[note].adjust = adjust / steps;
+		_notes[note].envelope.stepIncrease = ABS(adjust % steps);
+	} else {
+		_notes[note].adjust = adjust;
+		_notes[note].envelope.stepIncrease = ABS(adjust);
+	}
+
+	_notes[note].envelope.step = steps;
+	_notes[note].envelope.stepCounter = 0;
+	_notes[note].envelope.timer = steps;
+}
+
+bool Player_AD::processNoteEnvelope(int note, int &instrumentValue) {
+	if (_notes[note].preIncrease) {
+		instrumentValue += _notes[note].adjust;
+	}
+
+	_notes[note].envelope.stepCounter += _notes[note].envelope.stepIncrease;
+	if (_notes[note].envelope.stepCounter >= _notes[note].envelope.step) {
+		_notes[note].envelope.stepCounter -= _notes[note].envelope.step;
+
+		if (_notes[note].adjust < 0) {
+			--instrumentValue;
+		} else {
+			++instrumentValue;
+		}
+	}
+
+	if (--_notes[note].envelope.timer) {
+		return false;
+	} else {
+		return true;
+	}
+}
+
+uint8 Player_AD::getRnd() {
+	if (_rndSeed & 1) {
+		_rndSeed >>= 1;
+		_rndSeed ^= 0xB8;
+	} else {
+		_rndSeed >>= 1;
+	}
+
+	return _rndSeed;
+}
+
+const uint Player_AD::_noteBiasTable[7] = {
+	0x00, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x00
+};
+
+const uint Player_AD::_numStepsTable[16] = {
+	    1,    4,    6,    8,
+	   10,   14,   18,   24,
+	   36,   64,  100,  160,
+	  240,  340,  600, 1200
+};
+
+const uint Player_AD::_noteAdjustScaleTable[7] = {
+	255,   7,  63,  15,  63,  15,  63
+};
+
+const uint Player_AD::_noteAdjustTable[16] = {
+	    0,  4369,  8738, 13107,
+	17476, 21845, 26214, 30583,
+	34952, 39321, 43690, 48059,
+	52428, 46797, 61166, 65535
+};
+
+const bool Player_AD::_useOperatorTable[7] = {
+	false, false, true, true, true, true, false
+};
+
+const uint Player_AD::_channelOffsetTable[11] = {
+	 0,  1,  2,  3,
+	 4,  5,  6,  7,
+	 8,  8,  7
+};
+
+const uint Player_AD::_channelOperatorOffsetTable[7] = {
+	0, 0, 1, 1, 0, 0, 0
+};
+
+const uint Player_AD::_baseRegisterTable[7] = {
+	0xA0, 0xC0, 0x40, 0x20, 0x40, 0x20, 0x00
+};
+
+const uint Player_AD::_registerMaskTable[7] = {
+	0xFF, 0x0E, 0x3F, 0x0F, 0x3F, 0x0F, 0x00
+};
+
+const uint Player_AD::_registerShiftTable[7] = {
+	0, 1, 0, 0, 0, 0, 0
+};
+
+} // End of namespace Scumm
diff --git a/engines/scumm/player_ad.h b/engines/scumm/player_ad.h
new file mode 100644
index 0000000..da6c717
--- /dev/null
+++ b/engines/scumm/player_ad.h
@@ -0,0 +1,190 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCUMM_PLAYER_AD_H
+#define SCUMM_PLAYER_AD_H
+
+#include "scumm/music.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+#include "common/mutex.h"
+
+namespace OPL {
+class OPL;
+}
+
+namespace Scumm {
+
+class ScummEngine;
+
+/**
+ * Sound output for v3/v4 AdLib data.
+ */
+class Player_AD : public MusicEngine, public Audio::AudioStream {
+public:
+	Player_AD(ScummEngine *scumm, Audio::Mixer *mixer);
+	virtual ~Player_AD();
+
+	// MusicEngine API
+	virtual void setMusicVolume(int vol);
+	virtual void startSound(int sound);
+	virtual void stopSound(int sound);
+	virtual void stopAllSounds();
+	virtual int  getMusicTimer();
+	virtual int  getSoundStatus(int sound) const;
+
+	virtual void saveLoadWithSerializer(Serializer *ser);
+
+	// AudioStream API
+	virtual int readBuffer(int16 *buffer, const int numSamples);
+	virtual bool isStereo() const { return false; }
+	virtual bool endOfData() const { return false; }
+	virtual int getRate() const { return _rate; }
+
+private:
+	ScummEngine *const _vm;
+	Common::Mutex _mutex;
+	Audio::Mixer *const _mixer;
+	const int _rate;
+	Audio::SoundHandle _soundHandle;
+	void setupVolume();
+
+	OPL::OPL *_opl2;
+
+	int _samplesPerCallback;
+	int _samplesPerCallbackRemainder;
+	int _samplesTillCallback;
+	int _samplesTillCallbackRemainder;
+
+	int _soundPlaying;
+	int _engineMusicTimer;
+
+	// AdLib register utilities
+	uint8 _registerBackUpTable[256];
+	void writeReg(int r, int v);
+	uint8 readReg(int r) const;
+
+	// Instrument setup
+	void setupChannel(const uint channel, uint instrOffset) {
+		setupChannel(channel, _resource + instrOffset);
+	}
+	void setupChannel(const uint channel, const byte *instrOffset);
+	void setupOperator(const uint opr, const byte *&instrOffset);
+	static const int _operatorOffsetTable[18];
+
+	// Sound data
+	const byte *_resource;
+
+	// Music handling
+	void startMusic();
+	void updateMusic();
+	void noteOff(uint channel);
+	int findFreeChannel();
+	void setupFrequency(uint channel, int8 frequency);
+	void setupRhythm(uint rhythmInstr, uint instrOffset);
+
+	uint _timerLimit;
+	uint _musicTicks;
+	uint _musicTimer;
+	uint _internalMusicTimer;
+	bool _loopFlag;
+	uint _musicLoopStart;
+	uint _instrumentOffset[16];
+	uint _channelLastEvent[9];
+	uint _channelFrequency[9];
+	uint _channelB0Reg[9];
+
+	uint _mdvdrState;
+	uint _voiceChannels;
+	
+	uint _curOffset;
+	uint _nextEventTimer;
+
+	static const uint _noteFrequencies[12];
+	static const uint _mdvdrTable[6];
+	static const uint _rhythmOperatorTable[6];
+	static const uint _rhythmChannelTable[6];
+
+	// SFX handling
+	void startSfx();
+	void updateSfx();
+	void clearChannel(int channel);
+	void updateChannel(int channel);
+	void parseSlot(int channel);
+	void updateSlot(int channel);
+	void parseNote(int channel, int num, const byte *offset);
+	bool processNote(int note, const byte *offset);
+	void noteOffOn(int channel);
+	void writeRegisterSpecial(int note, uint8 value, int offset);
+	uint8 readRegisterSpecial(int note, uint8 defaultValue, int offset);
+	void setupNoteEnvelopeState(int note, int steps, int adjust);
+	bool processNoteEnvelope(int note, int &instrumentValue);
+
+	int _sfxTimer;
+
+	int _sfxResource[3];
+	int _sfxPriority[3];
+
+	struct Channel {
+		int state;
+		const byte *currentOffset;
+		const byte *startOffset;
+		uint8 instrumentData[7];
+	} _channels[11];
+
+	uint8 _rndSeed;
+	uint8 getRnd();
+
+	struct Note {
+		int state;
+		int playTime;
+		int sustainTimer;
+		int instrumentValue;
+		int bias;
+		int preIncrease;
+		int adjust;
+		
+		struct Envelope {
+			int stepIncrease;
+			int step;
+			int stepCounter;
+			int timer;
+		} envelope;
+	} _notes[22];
+
+	static const uint _noteBiasTable[7];
+	static const uint _numStepsTable[16];
+	static const uint _noteAdjustScaleTable[7];
+	static const uint _noteAdjustTable[16];
+	static const bool _useOperatorTable[7];
+	static const uint _channelOffsetTable[11];
+	static const uint _channelOperatorOffsetTable[7];
+	static const uint _baseRegisterTable[7];
+	static const uint _registerMaskTable[7];
+	static const uint _registerShiftTable[7];
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h
index 7b2ff91..c34be58 100644
--- a/engines/scumm/saveload.h
+++ b/engines/scumm/saveload.h
@@ -47,7 +47,7 @@ namespace Scumm {
  * only saves/loads those which are valid for the version of the savegame
  * which is being loaded/saved currently.
  */
-#define CURRENT_VER 94
+#define CURRENT_VER 95
 
 /**
  * An auxillary macro, used to specify savegame versions. We use this instead
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 2a14673..d1a3de9 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -52,6 +52,7 @@
 #include "scumm/he/logic_he.h"
 #include "scumm/he/sound_he.h"
 #include "scumm/object.h"
+#include "scumm/player_ad.h"
 #include "scumm/player_nes.h"
 #include "scumm/player_sid.h"
 #include "scumm/player_pce.h"
@@ -1841,6 +1842,15 @@ void ScummEngine::setupMusic(int midi) {
 		_musicEngine = _townsPlayer = new Player_Towns_v1(this, _mixer);
 		if (!_townsPlayer->init())
 			error("Failed to initialize FM-Towns audio driver");
+	} else if (_game.platform == Common::kPlatformDOS && (_sound->_musicType == MDT_ADLIB) && (_game.id == GID_LOOM || _game.id == GID_INDY3)) {
+		// For Indy3 DOS and Loom DOS we use an implementation of the original
+		// AD player when AdLib is selected. This fixes sound effects in those
+		// games (see for example bug #2027877 "INDY3: Non-Looping Sound
+		// Effects"). The player itself is also used in Monkey Island DOS
+		// EGA/VGA. However, we support multi MIDI for that game and we cannot
+		// support this with the Player_AD code at the moment. The reason here
+		// is that multi MIDI is supported internally by our iMuse output.
+		_musicEngine = new Player_AD(this, _mixer);
 	} else if (_game.version >= 3 && _game.heversion <= 62) {
 		MidiDriver *nativeMidiDriver = 0;
 		MidiDriver *adlibMidiDriver = 0;
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index 3f6290f..5e5e804 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -1930,7 +1930,6 @@ int ScummEngine::readSoundResourceSmallHeader(ResId idx) {
 		//   8 bytes MTrk header
 		//   7 bytes MIDI tempo sysex
 		//     + some default instruments
-		byte *ptr;
 		if (_game.features & GF_OLD_BUNDLE) {
 			ad_size -= 4;
 			_fileHandle->seek(ad_offs + 4, SEEK_SET);
@@ -1938,10 +1937,17 @@ int ScummEngine::readSoundResourceSmallHeader(ResId idx) {
 			ad_size -= 6;
 			_fileHandle->seek(ad_offs, SEEK_SET);
 		}
-		ptr = (byte *) calloc(ad_size, 1);
-		_fileHandle->read(ptr, ad_size);
-		convertADResource(_res, _game, idx, ptr, ad_size);
-		free(ptr);
+		// For games using AD except Indy3 and Loom we are using our iMuse
+		// implementation. See output initialization in
+		// ScummEngine::setupMusic for more information.
+		if (_game.id != GID_INDY3 && _game.id != GID_LOOM) {
+			byte *ptr = (byte *)calloc(ad_size, 1);
+			_fileHandle->read(ptr, ad_size);
+			convertADResource(_res, _game, idx, ptr, ad_size);
+			free(ptr);
+		} else {
+			_fileHandle->read(_res->createResource(rtSound, idx, ad_size), ad_size);
+		}
 		return 1;
 	} else if (ro_offs != 0) {
 		_fileHandle->seek(ro_offs - 2, SEEK_SET);


Commit: c2161b95640779677f186786ed1bbfad34da074c
    https://github.com/scummvm/scummvm/commit/c2161b95640779677f186786ed1bbfad34da074c
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2013-09-12T10:58:08-07:00

Commit Message:
Merge pull request #360 from lordhoto/scumm-ad

SCUMM: Implement original AD AdLib output.

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









More information about the Scummvm-git-logs mailing list