[Scummvm-git-logs] scummvm master -> a6a095716a44359832940ec5532e3dd81488bd55

NMIError noreply at scummvm.org
Sat Mar 1 20:31:48 UTC 2025


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

Summary:
a6a095716a GOT: Add music playback


Commit: a6a095716a44359832940ec5532e3dd81488bd55
    https://github.com/scummvm/scummvm/commit/a6a095716a44359832940ec5532e3dd81488bd55
Author: Coen Rampen (crampen at gmail.com)
Date: 2025-03-01T21:31:15+01:00

Commit Message:
GOT: Add music playback

Changed paths:
  A dists/engine-data/got.aud
  A engines/got/musicdriver.cpp
  A engines/got/musicdriver.h
  A engines/got/musicdriver_adlib.cpp
  A engines/got/musicdriver_adlib.h
  A engines/got/musicparser.cpp
  A engines/got/musicparser.h
    dists/engine-data/README
    dists/engine-data/engine_data.mk
    engines/got/game/back.cpp
    engines/got/game/init.cpp
    engines/got/got.cpp
    engines/got/module.mk
    engines/got/sound.cpp
    engines/got/sound.h
    engines/got/views/dialogs/main_menu.cpp
    engines/got/views/game.cpp
    engines/got/views/opening.cpp


diff --git a/dists/engine-data/README b/dists/engine-data/README
index 8cda4ec29f2..31fa1400a95 100644
--- a/dists/engine-data/README
+++ b/dists/engine-data/README
@@ -48,6 +48,9 @@ memory constraints.
 got.gfx:
 This is a set of graphics for the title screen and main menu that were embedded in the executable.
 
+got.aud:
+This is the title screen music that was embedded in the executable.
+
 grim-patch.lab:
 This file contains set of script patches for Grim Fandango game.
 
diff --git a/dists/engine-data/engine_data.mk b/dists/engine-data/engine_data.mk
index 3461515ad40..173d04da39b 100644
--- a/dists/engine-data/engine_data.mk
+++ b/dists/engine-data/engine_data.mk
@@ -18,6 +18,7 @@ DIST_FILES_LIST += dists/engine-data/drascula.dat
 endif
 ifdef ENABLE_GOT
 DIST_FILES_LIST += dists/engine-data/got.gfx
+DIST_FILES_LIST += dists/engine-data/got.aud
 endif
 ifdef ENABLE_HADESCH
 DIST_FILES_LIST += dists/engine-data/hadesch_translations.dat
diff --git a/dists/engine-data/got.aud b/dists/engine-data/got.aud
new file mode 100644
index 00000000000..c61d4da82bb
Binary files /dev/null and b/dists/engine-data/got.aud differ
diff --git a/engines/got/game/back.cpp b/engines/got/game/back.cpp
index 7fe56c2ac83..406ac84226f 100644
--- a/engines/got/game/back.cpp
+++ b/engines/got/game/back.cpp
@@ -103,9 +103,6 @@ void showLevel(const int newLevel) {
 	if (!_G(setup)._scrollFlag)
 		_G(currentLevel) = newLevel; // Force no scroll
 
-	if (_G(currentMusic) != _G(levelMusic))
-		_G(sound).musicPause();
-
 	switch (_G(newLevel) - _G(currentLevel)) {
 	case 0:
 		// Nothing to do
diff --git a/engines/got/game/init.cpp b/engines/got/game/init.cpp
index 4a2470bedee..55b016ddb7c 100644
--- a/engines/got/game/init.cpp
+++ b/engines/got/game/init.cpp
@@ -110,7 +110,6 @@ void initGame() {
 	_G(newLevel) = _G(currentLevel);
 	_G(scrn).load(_G(currentLevel));
 	showLevel(_G(currentLevel));
-	_G(sound).musicPlay(_G(levelMusic), true);
 
 	g_vars->resetEndGameFlags();
 	_G(startup) = false;
diff --git a/engines/got/got.cpp b/engines/got/got.cpp
index 337c40af1be..e7351194d7c 100644
--- a/engines/got/got.cpp
+++ b/engines/got/got.cpp
@@ -207,10 +207,7 @@ bool GotEngine::canSaveGameStateCurrently(Common::U32String *msg) {
 void GotEngine::syncSoundSettings() {
 	Engine::syncSoundSettings();
 
-	const bool allSoundIsMuted = ConfMan.getBool("mute");
-
-	_mixer->muteSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getBool("sfx_mute") || allSoundIsMuted);
-	_mixer->muteSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getBool("music_mute") || allSoundIsMuted);
+	_G(sound).syncSoundSettings();
 }
 
 void GotEngine::pauseEngineIntern(bool pause) {
@@ -231,6 +228,13 @@ void GotEngine::pauseEngineIntern(bool pause) {
 
 	_G(thunderSnakeCounter) = 0;
 
+	if (pause) {
+		_G(sound).musicPause();
+	}
+	else {
+		_G(sound).musicResume();
+	}
+
 	Engine::pauseEngineIntern(pause);
 }
 
diff --git a/engines/got/module.mk b/engines/got/module.mk
index 6343d4a1577..b98c688b6da 100644
--- a/engines/got/module.mk
+++ b/engines/got/module.mk
@@ -6,6 +6,9 @@ MODULE_OBJS = \
 	events.o \
 	messages.o \
 	metaengine.o \
+	musicdriver.o \
+	musicdriver_adlib.o \
+	musicparser.o \
 	sound.o \
 	vars.o \
 	data/actor.o \
diff --git a/engines/got/musicdriver.cpp b/engines/got/musicdriver.cpp
new file mode 100644
index 00000000000..46e13802d76
--- /dev/null
+++ b/engines/got/musicdriver.cpp
@@ -0,0 +1,88 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "got/musicdriver.h"
+
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/system.h"
+
+namespace Got {
+
+MusicDriver_Got::MusicDriver_Got(uint8 timerFrequency) : _isOpen(false), _timer_param(nullptr), _timer_proc(nullptr) {
+	setTimerFrequency(timerFrequency);
+}
+
+bool MusicDriver_Got::isOpen() const {
+	return _isOpen;
+}
+
+void MusicDriver_Got::setTimerFrequency(uint8 timerFrequency) {
+	assert(timerFrequency > 0);
+
+	_timerFrequency = timerFrequency;
+}
+
+void MusicDriver_Got::onTimer() {
+	if (_timer_proc && _timer_param)
+		_timer_proc(_timer_param);
+}
+
+void MusicDriver_Got::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+	_timer_param = timer_param;
+	_timer_proc = timer_proc;
+}
+
+int MusicDriver_Got_NULL::open() {
+	// There is no output device, so register a timer to trigger the callbacks.
+	g_system->getTimerManager()->installTimerProc(timerCallback, 1000000 / _timerFrequency, this, "MusicDriver_Got_NULL");
+
+	_isOpen = true;
+
+	return 0;
+}
+
+void MusicDriver_Got_NULL::close() {
+	if (!_isOpen)
+		return;
+
+	g_system->getTimerManager()->removeTimerProc(timerCallback);
+
+	_isOpen = false;
+}
+
+void MusicDriver_Got_NULL::setTimerFrequency(uint8 timerFrequency) {
+	if (timerFrequency == _timerFrequency)
+		return;
+
+	MusicDriver_Got::setTimerFrequency(timerFrequency);
+
+	// Update the timer frequency.
+	g_system->getTimerManager()->removeTimerProc(timerCallback);
+	g_system->getTimerManager()->installTimerProc(timerCallback, 1000000 / _timerFrequency, this, "MusicDriver_Got_NULL");
+}
+
+void MusicDriver_Got_NULL::timerCallback(void *data) {
+	MusicDriver_Got_NULL *driver = (MusicDriver_Got_NULL *)data;
+	driver->onTimer();
+}
+
+} // namespace Got
diff --git a/engines/got/musicdriver.h b/engines/got/musicdriver.h
new file mode 100644
index 00000000000..d7b9a4cef87
--- /dev/null
+++ b/engines/got/musicdriver.h
@@ -0,0 +1,77 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef GOT_MUSICDRIVER_H
+#define GOT_MUSICDRIVER_H
+
+#include "common/timer.h"
+
+namespace Got {
+
+class MusicDriver_Got {
+public:
+	MusicDriver_Got(uint8 timerFrequency);
+	virtual ~MusicDriver_Got() { };
+
+	virtual int open() = 0;
+	bool isOpen() const;
+	virtual void close() = 0;
+
+	virtual void syncSoundSettings() = 0;
+
+	virtual void send(uint16 b) = 0;
+	virtual void stopAllNotes() = 0;
+
+	virtual void setTimerFrequency(uint8 timerFrequency);
+	void onTimer();
+	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
+
+protected:
+	// True if the driver has been successfully opened.
+	bool _isOpen;
+	// The number of timer callbacks per second.
+	int _timerFrequency;
+
+	// External timer callback
+	void *_timer_param;
+	Common::TimerManager::TimerProc _timer_proc;
+};
+
+class MusicDriver_Got_NULL : public MusicDriver_Got {
+public:
+	MusicDriver_Got_NULL(uint8 timerFrequency) : MusicDriver_Got(timerFrequency) { };
+	~MusicDriver_Got_NULL() { };
+
+	int open() override;
+	void close() override;
+
+	void syncSoundSettings() override { };
+	void send(uint16 b) override { };
+	void stopAllNotes() override { };
+
+	void setTimerFrequency(uint8 timerFrequency) override;
+
+	static void timerCallback(void *data);
+};
+
+} // namespace Got
+
+#endif
diff --git a/engines/got/musicdriver_adlib.cpp b/engines/got/musicdriver_adlib.cpp
new file mode 100644
index 00000000000..3492a971670
--- /dev/null
+++ b/engines/got/musicdriver_adlib.cpp
@@ -0,0 +1,289 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "got/musicdriver_adlib.h"
+
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "audio/mididrv.h"
+
+
+namespace Got {
+
+MusicDriver_Got_AdLib::MusicDriver_Got_AdLib(uint8 timerFrequency) : MusicDriver_Got(timerFrequency), 
+		_opl(nullptr),
+		_userMusicVolume(256),
+		_userMute(false) {
+	Common::fill(_channelOp0LevelRegisterValues, _channelOp0LevelRegisterValues + ARRAYSIZE(_channelOp0LevelRegisterValues), 0);
+	Common::fill(_channelOp1LevelRegisterValues, _channelOp1LevelRegisterValues + ARRAYSIZE(_channelOp1LevelRegisterValues), 0);
+	Common::fill(_channelBxRegisterValues, _channelBxRegisterValues + ARRAYSIZE(_channelBxRegisterValues), 0);
+	Common::fill(_channelConnectionValues, _channelConnectionValues + ARRAYSIZE(_channelConnectionValues), 0);
+}
+
+MusicDriver_Got_AdLib::~MusicDriver_Got_AdLib() {
+	close();
+}
+
+int MusicDriver_Got_AdLib::open() {
+	if (_isOpen)
+		return MidiDriver::MERR_ALREADY_OPEN;
+
+	int8 detectResult = OPL::Config::detect(OPL::Config::kOpl2);
+	if (detectResult == -1)
+		return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
+
+	// Create the emulator / hardware interface.
+	_opl = OPL::Config::create(OPL::Config::kOpl2);
+
+	if (_opl == nullptr)
+		return MidiDriver::MERR_CANNOT_CONNECT;
+
+	// Initialize emulator / hardware interface.
+	if (!_opl->init())
+		return MidiDriver::MERR_CANNOT_CONNECT;
+
+	_isOpen = true;
+
+	// Set default OPL register values.
+	initOpl();
+
+	// Start the emulator / hardware interface. This will also start the timer
+	// callbacks.
+	_opl->start(new Common::Functor0Mem<void, MusicDriver_Got_AdLib>(this, &MusicDriver_Got_AdLib::onTimer), _timerFrequency);
+
+	return 0;
+}
+
+void MusicDriver_Got_AdLib::close() {
+	if (!_isOpen)
+		return;
+
+	_isOpen = false;
+
+	stopAllNotes();
+
+	if (_opl) {
+		_opl->stop();
+		delete _opl;
+		_opl = nullptr;
+	}
+}
+
+void MusicDriver_Got_AdLib::syncSoundSettings() {
+	// Get user volume settings.
+	_userMusicVolume = MIN(256, ConfMan.getInt("music_volume"));
+	_userMute = ConfMan.getBool("mute") || ConfMan.getBool("music_mute");
+
+	// Apply the user volume.
+	recalculateVolumes();
+}
+
+void MusicDriver_Got_AdLib::send(uint16 b) {
+	uint8 oplRegister = b >> 8;
+	uint8 value = b & 0xFF;
+
+	if (oplRegister >= OPL_REGISTER_BASE_LEVEL && oplRegister < OPL_REGISTER_BASE_LEVEL + 0x20) {
+		// Write to a level register.
+
+		// Determine the channel and operator from the register number.
+		uint8 regOffset = oplRegister - OPL_REGISTER_BASE_LEVEL;
+		uint8 oplChannel = ((regOffset / 8) * 3) + ((regOffset % 8) % 3);
+		uint8 oplOperator = (regOffset % 8) / 3;
+		assert(oplChannel < OPL2_NUM_CHANNELS);
+		assert(oplOperator < 2);
+
+		// Store the new register value.
+		if (oplOperator == 0) {
+			_channelOp0LevelRegisterValues[oplChannel] = value;
+		} else {
+			_channelOp1LevelRegisterValues[oplChannel] = value;
+		}
+
+		// Apply user volume settings to the level.
+		uint8 scaledLevel = calculateVolume(oplChannel, oplOperator);
+		// Add the KSL bits to the new level value.
+		value = (value & 0xC0) | scaledLevel;
+	} else if ((oplRegister & 0xF0) == OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON) {
+		// Write to an F-num high / block / key on register.
+
+		uint8 oplChannel = oplRegister & 0x0F;
+		assert(oplChannel < OPL2_NUM_CHANNELS);
+
+		// Store the value, but clear the key on bit.
+		_channelBxRegisterValues[oplChannel] = value & 0x1F;
+	} else if ((oplRegister & 0xF0) == OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING) {
+		// Write to a connection register.
+
+		uint8 oplChannel = oplRegister & 0x0F;
+		assert(oplChannel < OPL2_NUM_CHANNELS);
+
+		// Store the connection bit.
+		_channelConnectionValues[oplChannel] = value & 0x01;
+	}
+
+	// Write the new register value to the OPL chip.
+	writeRegister(oplRegister, value);
+}
+
+void MusicDriver_Got_AdLib::stopAllNotes() {
+	// Clear the key on bit on all OPL channels.
+	for (int i = 0; i < OPL2_NUM_CHANNELS; i++) {
+		writeRegister(0xB0 | i, _channelBxRegisterValues[i]);
+	}
+}
+
+void MusicDriver_Got_AdLib::initOpl() {
+	// Clear test flags and enable waveform select.
+	writeRegister(0x01, 0x20);
+
+	// Clear, stop and mask the timers and reset the interrupt.
+	writeRegister(0x02, 0);
+	writeRegister(0x03, 0);
+	writeRegister(0x04, 0x60);
+	writeRegister(0x04, 0x80);
+
+	// Set note select mode 0 and disable CSM mode.
+	writeRegister(0x08, 0);
+
+	// Clear operator registers.
+	for (int i = 0; i < 5; i++) {
+		uint8 baseReg = 0;
+		switch (i) {
+		case 0:
+			baseReg = OPL_REGISTER_BASE_FREQMULT_MISC;
+			break;
+		case 1:
+			baseReg = OPL_REGISTER_BASE_LEVEL;
+			break;
+		case 2:
+			baseReg = OPL_REGISTER_BASE_DECAY_ATTACK;
+			break;
+		case 3:
+			baseReg = OPL_REGISTER_BASE_RELEASE_SUSTAIN;
+			break;
+		case 4:
+			baseReg = OPL_REGISTER_BASE_WAVEFORMSELECT;
+			break;
+		}
+
+		for (int j = 0; j < OPL2_NUM_CHANNELS; j++) {
+			writeRegister(baseReg + determineOperatorRegisterOffset(j, 0), 0);
+			writeRegister(baseReg + determineOperatorRegisterOffset(j, 1), 0);
+		}
+	}
+
+	// Clear channel registers.
+	for (int i = 0; i < 3; i++) {
+		uint8 baseReg = 0;
+		switch (i) {
+		case 0:
+			baseReg = OPL_REGISTER_BASE_FNUMLOW;
+			break;
+		case 1:
+			baseReg = OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON;
+			break;
+		case 2:
+			baseReg = OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING;
+			break;
+		}
+
+		for (int j = 0; j < OPL2_NUM_CHANNELS; j++) {
+			writeRegister(baseReg + j, 0);
+		}
+	}
+
+	// Disable rhythm mode and set modulation and vibrato depth to low.
+	writeRegister(0xBD, 0);
+}
+
+void MusicDriver_Got_AdLib::recalculateVolumes() {
+	// Determine the value for all level registers and write them out.
+	for (int i = 0; i < OPL2_NUM_CHANNELS; i++) {
+		uint8 oplRegister = OPL_REGISTER_BASE_LEVEL + determineOperatorRegisterOffset(i, 0);
+		uint8 value = (_channelOp0LevelRegisterValues[i] & 0xC0) | calculateVolume(i, 0);
+		writeRegister(oplRegister, value);
+
+		oplRegister = OPL_REGISTER_BASE_LEVEL + determineOperatorRegisterOffset(i, 1);
+		value = (_channelOp1LevelRegisterValues[i] & 0xC0) | calculateVolume(i, 1);
+		writeRegister(oplRegister, value);
+	}
+}
+
+uint8 MusicDriver_Got_AdLib::calculateVolume(uint8 channel, uint8 operatorNum) {
+	// Get the last written level for this operator.
+	uint8 operatorDefVolume = (operatorNum == 0 ? _channelOp0LevelRegisterValues[channel] : _channelOp1LevelRegisterValues[channel]) & 0x3F;
+
+	// Determine if volume settings should be applied to this operator.
+	if (!isVolumeApplicableToOperator(channel, operatorNum))
+		// No need to apply volume settings; just use the level as written.
+		return operatorDefVolume;
+
+	uint8 invertedVolume = 0x3F - operatorDefVolume;
+
+	// Scale by user volume.
+	if (_userMute) {
+		invertedVolume = 0;
+	} else {
+		invertedVolume = (invertedVolume * _userMusicVolume) >> 8;
+	}
+	uint8 scaledVolume = 0x3F - invertedVolume;
+
+	return scaledVolume;
+}
+
+bool MusicDriver_Got_AdLib::isVolumeApplicableToOperator(uint8 channel, uint8 operatorNum) {
+	// 2 operator instruments have 2 different operator connections:
+	// additive (0x01) or FM (0x00) synthesis.  Carrier operators in FM
+	// synthesis and both operators in additive synthesis need to have
+	// volume settings applied; modulator operators just use the level
+	// as written. In FM synthesis connection, operator 1 is a carrier.
+	return _channelConnectionValues[channel] == 0x01 || operatorNum == 1;
+}
+
+uint16 MusicDriver_Got_AdLib::determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum) {
+	// 2 operator register offset for each channel and operator:
+	//
+	// Channel  | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | 3 | 4 | 5 | 6 | 7 | 8 | 6 | 7 | 8 |
+	// Operator | 0         | 1         | 0         | 1         | 0         | 1         |
+	// Register | 0 | 1 | 2 | 3 | 4 | 5 | 8 | 9 | A | B | C | D |10 |11 |12 |13 |14 |15 |
+	return (oplChannel / 3 * 8) + (oplChannel % 3) + (operatorNum * 3);
+}
+
+void MusicDriver_Got_AdLib::writeRegister(uint8 oplRegister, uint8 value) {
+	//debug("Writing register %X %X", oplRegister, value);
+
+	_opl->writeReg(oplRegister, value);
+}
+
+void MusicDriver_Got_AdLib::setTimerFrequency(uint8 timerFrequency) {
+	if (timerFrequency == _timerFrequency)
+		return;
+
+	MusicDriver_Got::setTimerFrequency(timerFrequency);
+
+	if (isOpen()) {
+		// Update OPL timer frequency.
+		_opl->stop();
+		_opl->start(new Common::Functor0Mem<void, MusicDriver_Got_AdLib>(this, &MusicDriver_Got_AdLib::onTimer), _timerFrequency);
+	}
+}
+
+} // namespace Got
diff --git a/engines/got/musicdriver_adlib.h b/engines/got/musicdriver_adlib.h
new file mode 100644
index 00000000000..74329134a26
--- /dev/null
+++ b/engines/got/musicdriver_adlib.h
@@ -0,0 +1,120 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef GOT_MUSICDRIVER_ADLIB_H
+#define GOT_MUSICDRIVER_ADLIB_H
+
+#include "got/musicdriver.h"
+
+#include "common/scummsys.h"
+#include "common/timer.h"
+#include "audio/fmopl.h"
+
+namespace Got {
+
+class MusicDriver_Got_AdLib : public MusicDriver_Got {
+public:
+	/**
+	 * The number of available channels on the OPL2 chip.
+	 */
+	static const uint8 OPL2_NUM_CHANNELS = 9;
+
+	/**
+	 * OPL operator base registers.
+	 */
+	static const uint8 OPL_REGISTER_BASE_FREQMULT_MISC = 0x20;
+	static const uint8 OPL_REGISTER_BASE_LEVEL = 0x40;
+	static const uint8 OPL_REGISTER_BASE_DECAY_ATTACK = 0x60;
+	static const uint8 OPL_REGISTER_BASE_RELEASE_SUSTAIN = 0x80;
+	static const uint8 OPL_REGISTER_BASE_WAVEFORMSELECT = 0xE0;
+
+	/**
+	 * OPL channel base registers.
+	 */
+	static const uint8 OPL_REGISTER_BASE_FNUMLOW = 0xA0;
+	static const uint8 OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON = 0xB0;
+	static const uint8 OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING = 0xC0;
+
+	MusicDriver_Got_AdLib(uint8 timerFrequency);
+	~MusicDriver_Got_AdLib();
+
+	int open() override;
+	void close() override;
+
+	void syncSoundSettings() override;
+
+	void send(uint16 b) override;
+	void stopAllNotes() override;
+
+	void setTimerFrequency(uint8 timerFrequency) override;
+
+protected:
+	/**
+	 * Initializes the OPL registers to their default values.
+	 */
+	void initOpl();
+
+	void recalculateVolumes();
+	uint8 calculateVolume(uint8 channel, uint8 operatorNum);
+	/**
+	 * Determines if volume settings should be applied to the operator level.
+	 * This depends on the type of the operator (carrier or modulator), which
+	 * depends on the type of connection specified on the channel.
+	 *
+	 * @param oplChannel The OPL channel
+	 * @param operatorNum The number of the operator (0-1)
+	 * @return True if volume should be applied, false otherwise
+	 */
+	bool isVolumeApplicableToOperator(uint8 oplChannel, uint8 operatorNum);
+	/**
+	 * Determines the offset from a base register for the specified operator of
+	 * the specified OPL channel.
+	 * Add the offset to the base register to get the correct register for this
+	 * operator and channel.
+	 *
+	 * @param oplChannel The OPL channel for which to determine the offset.
+	 * @param operatorNum The operator for which to determine the offset (0-1).
+	 * @return The offset to the base register for this operator.
+	 */
+	uint16 determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum);
+	void writeRegister(uint8 oplRegister, uint8 value);
+
+	// The OPL emulator / hardware interface.
+	OPL::OPL *_opl;
+
+	// Last written value to the operator 0 level register for each channel.
+	uint8 _channelOp0LevelRegisterValues[OPL2_NUM_CHANNELS];
+	// Last written value to the operator 1 level register for each channel.
+	uint8 _channelOp1LevelRegisterValues[OPL2_NUM_CHANNELS];
+	// Last written value to the F-num high / block / key on register for each
+	// channel, with the key on bit cleared.
+	uint8 _channelBxRegisterValues[OPL2_NUM_CHANNELS];
+	// Last written value of the connection bit for each channel.
+	uint8 _channelConnectionValues[OPL2_NUM_CHANNELS];
+
+	// User volume settings.
+	uint16 _userMusicVolume;
+	bool _userMute;
+};
+
+} // namespace Got
+
+#endif
diff --git a/engines/got/musicparser.cpp b/engines/got/musicparser.cpp
new file mode 100644
index 00000000000..cedfbf5a304
--- /dev/null
+++ b/engines/got/musicparser.cpp
@@ -0,0 +1,195 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "got/musicparser.h"
+
+#include "common/endian.h"
+#include "common/textconsole.h"
+
+namespace Got {
+
+MusicParser_Got::MusicParser_Got() :
+	_pause(false),
+	_abortParse(false),
+	_currentDelta(0),
+	_driver(nullptr),
+	_track(nullptr),
+	_trackLength(0),
+	_playPos(nullptr),
+	_loopStart(-1) { }
+
+MusicParser_Got::~MusicParser_Got() {
+	stopPlaying();
+}
+
+void MusicParser_Got::sendToDriver(uint16 b) {
+	if (_driver == nullptr)
+		return;
+
+	_driver->send(b);
+}
+
+void MusicParser_Got::sendToDriver(byte reg, byte value) {
+	// OPL register and value are sent as a uint16 to the driver.
+	sendToDriver((reg << 8) | value);
+}
+
+void MusicParser_Got::allNotesOff() {
+	if (_driver == nullptr)
+		return;
+
+	_driver->stopAllNotes();
+}
+
+void MusicParser_Got::resetTracking() {
+	_playPos = nullptr;
+}
+
+bool MusicParser_Got::startPlaying() {
+	if (_track == nullptr || _pause)
+		return false;
+
+	if (_playPos == nullptr) {
+		_playPos = _track;
+	}
+
+	return true;
+}
+
+void MusicParser_Got::pausePlaying() {
+	if (isPlaying() && !_pause) {
+		_pause = true;
+		allNotesOff();
+	}
+}
+
+void MusicParser_Got::resumePlaying() {
+	_pause = false;
+}
+
+void MusicParser_Got::stopPlaying() {
+	if (isPlaying())
+		allNotesOff();
+	resetTracking();
+	_pause = false;
+}
+
+bool MusicParser_Got::isPlaying() {
+	return _playPos != nullptr;
+}
+
+void MusicParser_Got::setMusicDriver(MusicDriver_Got *driver) {
+	_driver = driver;
+}
+
+bool MusicParser_Got::loadMusic(byte* data, uint32 size) {
+	assert(size >= 5);
+
+	// Data starts with a word defining the loop point.
+	uint16 loopHeader = READ_LE_UINT16(data);
+	data += 2;
+
+	// Rest of the data is music events.
+	_track = data;
+	_trackLength = size - 2;
+
+	if (loopHeader == 0) {
+		// No loop point defined.
+		_loopStart = -1;
+	} else {
+		// Loop point is defined as the number of words from the start of the
+		// data (including the header).
+		_loopStart = (loopHeader - 1) * 2;
+		if ((uint32)_loopStart >= _trackLength) {
+			warning("MusicParser_Got::loadMusic - Music data has loop start point after track end");
+			_loopStart = -1;
+		}
+	}
+
+	return true;
+}
+
+void MusicParser_Got::unloadMusic() {
+	if (_track == nullptr)
+		// No music data loaded
+		return;
+
+	stopPlaying();
+	_abortParse = true;
+	_track = nullptr;
+}
+
+void MusicParser_Got::onTimer() {
+	if (_playPos == nullptr || !_driver || _pause)
+		return;
+
+	if (_currentDelta > 0) {
+		_currentDelta--;
+		if (_currentDelta > 0)
+			// More ticks to go to the next event.
+			return;
+	}
+
+	_abortParse = false;
+	while (!_abortParse && isPlaying() && _currentDelta == 0) {
+		assert((_playPos + 3) < (_track + _trackLength));
+
+		// An event consists of a delta, followed by an OPL register / value pair.
+
+		// A delta consists of 1 or 2 bytes. Bit 7 in the first byte indicates
+		// if a second byte is used.
+		uint16 newDelta = *_playPos++;
+		if ((newDelta & 0x80) > 0) {
+			assert((_playPos + 3) < (_track + _trackLength));
+			newDelta &= 0x7F;
+			newDelta <<= 8;
+			newDelta |= *_playPos++;
+		}
+		// The delta is the number of ticks from this event to the next event,
+		// not the number of ticks preceding this event.
+		_currentDelta = newDelta;
+
+		// Next, read the OPL register / value pair.
+		byte oplRegister = *_playPos++;
+		byte oplRegValue = *_playPos++;
+
+		if (oplRegister == 0 && oplRegValue == 0) {
+			// End of track is indicated by an event with OPL register and value 0.
+			if (_loopStart >= 0) {
+				// Continue playback at the loop point.
+				_playPos = _track + _loopStart;
+			}
+			else {
+				stopPlaying();
+			}
+		}
+		else {
+			// Write the specified OPL register value.
+			sendToDriver(oplRegister, oplRegValue);
+		}
+	}
+}
+
+void MusicParser_Got::timerCallback(void *data) {
+	((MusicParser_Got *)data)->onTimer();
+}
+
+} // namespace Got
diff --git a/engines/got/musicparser.h b/engines/got/musicparser.h
new file mode 100644
index 00000000000..68dcf32fcf4
--- /dev/null
+++ b/engines/got/musicparser.h
@@ -0,0 +1,77 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef GOT_MUSICPARSER_H
+#define GOT_MUSICPARSER_H
+
+#include "got/musicdriver_adlib.h"
+
+#include "common/scummsys.h"
+
+namespace Got {
+
+class MusicParser_Got {
+public:
+	MusicParser_Got();
+	~MusicParser_Got();
+
+	bool startPlaying();
+	void pausePlaying();
+	void resumePlaying();
+	void stopPlaying();
+	bool isPlaying();
+
+	void setMusicDriver(MusicDriver_Got *driver);
+
+	bool loadMusic(byte *data, uint32 size);
+	void unloadMusic();
+
+	void onTimer();
+	static void timerCallback(void *data);
+
+protected:
+	void allNotesOff();
+	void sendToDriver(uint16 b);
+	void sendToDriver(byte reg, byte value);
+	void resetTracking();
+
+	bool _pause;
+	bool _abortParse;
+	// Number of ticks to the next event to process.
+	uint16 _currentDelta;
+
+	MusicDriver_Got *_driver;
+
+	// Points to start of current track data, or nullptr if no track is loaded.
+	byte *_track;
+	// The length of the track data.
+	uint32 _trackLength;
+	// The current play position in the track data, or nullptr if the parser is
+	// not playing.
+	byte *_playPos;
+	// The offset from the start of _track where playback should restart if the
+	// track has reached the end. -1 if there is no (valid) loop point defined.
+	int32 _loopStart;
+};
+
+} // namespace Got
+
+#endif
diff --git a/engines/got/sound.cpp b/engines/got/sound.cpp
index 4caaa9f5dbb..8fdbe0ab918 100644
--- a/engines/got/sound.cpp
+++ b/engines/got/sound.cpp
@@ -20,16 +20,23 @@
  */
 
 #include "got/sound.h"
-#include "audio/decoders/raw.h"
-#include "audio/decoders/voc.h"
-#include "common/memstream.h"
+
 #include "got/got.h"
+#include "got/musicdriver_adlib.h"
 #include "got/utils/file.h"
 
+#include "common/config-manager.h"
+#include "common/memstream.h"
+#include "audio/mididrv.h"
+#include "audio/decoders/raw.h"
+#include "audio/decoders/voc.h"
+
 namespace Got {
 
 static const byte SOUND_PRIORITY[] = {1, 2, 3, 3, 3, 1, 4, 4, 4, 5, 4, 3, 1, 2, 2, 5, 1, 3, 1};
 
+static const char *MUSIC_MENU_DATA_FILENAME = "GOT.AUD";
+
 Sound::Sound() {
 	for (int i = 0; i < 3; i++)
 		_bossSounds[i] = nullptr;
@@ -40,6 +47,23 @@ Sound::~Sound() {
 	for (int i = 0; i < 3; i++) {
 		delete(_bossSounds[i]);
 	}
+
+	musicStop();
+
+	if (_musicDriver != nullptr) {
+		_musicDriver->setTimerCallback(nullptr, nullptr);
+		_musicDriver->close();
+	}
+
+	if (_musicParser != nullptr) {
+		delete _musicParser;
+	}
+	if (_musicDriver != nullptr) {
+		delete _musicDriver;
+	}
+
+	if (_musicData != nullptr)
+		delete[] _musicData;
 }
 
 void Sound::load() {
@@ -52,6 +76,40 @@ void Sound::load() {
 	// Allocate memory and load sound data
 	_soundData = new byte[f.size() - 16 * 8];
 	f.read(_soundData, f.size() - 16 * 8);
+
+	// Initialize music.
+
+	// Check the type of music device that the user has configured.
+	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_ADLIB);
+	MusicType deviceType = MidiDriver::getMusicType(dev);
+
+	// Initialize the appropriate driver.
+	switch (deviceType) {
+		case MT_ADLIB:
+			_musicDriver = new MusicDriver_Got_AdLib(MUSIC_TIMER_FREQUENCY_GAME);
+			break;
+		default:
+			// No support for music other than AdLib.
+			_musicDriver = new MusicDriver_Got_NULL(MUSIC_TIMER_FREQUENCY_GAME);
+			break;
+	}
+
+	// Initialize the parser.
+	_musicParser = new MusicParser_Got();
+
+	// Open the driver.
+	int returnCode = _musicDriver->open();
+	if (returnCode != 0) {
+		warning("Sound::load - Failed to open music driver - error code %d.", returnCode);
+		return;
+	}
+
+	// Apply user volume settings.
+	syncSoundSettings();
+
+	// Connect the driver and the parser.
+	_musicParser->setMusicDriver(_musicDriver);
+	_musicDriver->setTimerCallback(_musicParser, &_musicParser->timerCallback);
 }
 
 void Sound::setupBoss(const int num) {
@@ -84,14 +142,18 @@ void Sound::playSound(const int index, const bool override) {
 	if (index >= NUM_SOUNDS)
 		return;
 
+	byte newPriority = SOUND_PRIORITY[index];
+
 	// If a sound is playing, stop it unless there is a priority override
 	if (soundPlaying()) {
-		if (!override && _currentPriority < SOUND_PRIORITY[index])
+		if (!override && _currentPriority < newPriority)
 			return;
 
 		g_engine->_mixer->stopHandle(_soundHandle);
 	}
 
+	_currentPriority = newPriority;
+
 	Common::MemoryReadStream *stream;
 	if (index >= 16) {
 		// Boss sounds are not stored in the normal sound data, it's in 3 buffers in _bossSounds.
@@ -122,71 +184,60 @@ bool Sound::soundPlaying() const {
 }
 
 void Sound::musicPlay(const char *name, bool override) {
-	if (name != _currentMusic || override) {
-		g_engine->_mixer->stopHandle(_musicHandle);
+	if (_currentMusic == nullptr || strcmp(name, _currentMusic) || override) {
+		_musicParser->stopPlaying();
+		_musicParser->unloadMusic();
+		delete[] _musicData;
+
 		_currentMusic = name;
 
-		File file(name);
-
-#ifdef TODO
-		// FIXME: Completely wrong. Don't know music format yet
-		// Open it up for access
-		Common::SeekableReadStream *f = file.readStream(file.size());
-		Audio::AudioStream *audioStream = Audio::makeRawStream(
-			f, 11025, 0, DisposeAfterUse::YES);
-		g_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType,
-									 &_musicHandle, audioStream);
-
-#else
-		warning("TODO: play_music %s", name);
-
-#if 0
-		Common::DumpFile *outFile = new Common::DumpFile();
-		const Common::String outName = Common::String::format("%s.dump", name);
-		outFile->open(Common::Path(outName));
-		byte *buffer = new byte[file.size()];
-		file.read(buffer, file.size());
-		outFile->write(buffer, file.size());
-		outFile->finalize();
-		outFile->close();
-#endif
-
-		const int startLoop = file.readUint16LE();
-
-		while (!file.eos()) {
-			int delayAfter = file.readByte();
-			if (delayAfter & 0x80)
-				delayAfter = ((delayAfter & 0x7f) << 8) | file.readByte();
-
-			const int reg = file.readByte();
-			const int value = file.readByte();
-			if (reg == 0 && value == 0) {
-				debug(1, "End of song");
-				break;
+		File file;
+		if (!strcmp(name, "MENU")) {
+			// Title menu music is embedded in the executable.
+			// It has been extracted and included with ScummVM.
+			if (!file.exists(MUSIC_MENU_DATA_FILENAME)) {
+				warning("Could not find %s", MUSIC_MENU_DATA_FILENAME);
+				return;
 			}
+			file.open(MUSIC_MENU_DATA_FILENAME);
+
+			// Title music uses an alternate timer frequency.
+			_musicDriver->setTimerFrequency(MUSIC_TIMER_FREQUENCY_TITLE);
+		} else {
+			file.open(name);
+
+			_musicDriver->setTimerFrequency(MUSIC_TIMER_FREQUENCY_GAME);
+		}
+
+		// Copy music data to local buffer and load it into the parser.
+		_musicData = new byte[file.size()];
+		file.read(_musicData, file.size());
 
-			debug(1, "DelayAfter %d, OPL reg 0x%X, value %d", delayAfter, reg, value);
+		if (!_musicParser->loadMusic(_musicData, file.size())) {
+			warning("Could not load music track %s", name);
+			return;
 		}
-		debug(1, "looping at pos %d", startLoop);
-#endif
+
+		//debug("Playing music track %s", name);
+		_musicParser->startPlaying();
 	}
 }
 
 void Sound::musicPause() {
-	g_engine->_mixer->pauseHandle(_musicHandle, true);
+	_musicParser->pausePlaying();
 }
 
 void Sound::musicResume() {
-	g_engine->_mixer->pauseHandle(_musicHandle, false);
+	_musicParser->resumePlaying();
 }
 
 void Sound::musicStop() {
-	musicPause();
+	_musicParser->stopPlaying();
 	_currentMusic = nullptr;
 }
 
 bool Sound::musicIsOn() const {
-	return g_engine->_mixer->isSoundHandleActive(_musicHandle);
+	return _musicParser->isPlaying();
 }
 
 const char *Sound::getMusicName(const int num) const {
@@ -290,6 +341,13 @@ const char *Sound::getMusicName(const int num) const {
 	return name;
 }
 
+void Sound::syncSoundSettings() {
+	g_engine->_mixer->muteSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getBool("sfx_mute") || ConfMan.getBool("mute"));
+
+	if (_musicDriver)
+		_musicDriver->syncSoundSettings();
+}
+
 void playSound(const int index, const bool override) {
 	_G(sound).playSound(index, override);
 }
diff --git a/engines/got/sound.h b/engines/got/sound.h
index b65742a2119..a99faf79bbb 100644
--- a/engines/got/sound.h
+++ b/engines/got/sound.h
@@ -22,12 +22,13 @@
 #ifndef GOT_SOUND_H
 #define GOT_SOUND_H
 
-#include "audio/fmopl.h"
-
-#include "audio/mixer.h"
+#include "got/musicdriver.h"
+#include "got/musicparser.h"
 #include "got/data/defines.h"
 #include "got/gfx/gfx_chunks.h"
 
+#include "audio/mixer.h"
+
 namespace Got {
 
 enum {
@@ -56,15 +57,20 @@ enum {
 
 class Sound {
 private:
+	static const uint8 MUSIC_TIMER_FREQUENCY_GAME = 120;
+	static const uint8 MUSIC_TIMER_FREQUENCY_TITLE = 140;
+
 	byte *_soundData = nullptr;
 	byte *_bossSounds[3];
 	Header _digiSounds[NUM_SOUNDS];
 	Audio::SoundHandle _soundHandle;
-	int _currentPriority = 0;
+	byte _currentPriority = 0;
 	int8 _currentBossLoaded = 0;
 
 	const char *_currentMusic = nullptr;
-	Audio::SoundHandle _musicHandle;
+	byte *_musicData = nullptr;
+	MusicDriver_Got *_musicDriver = nullptr;
+	MusicParser_Got *_musicParser = nullptr;
 
 	const char *getMusicName(int num) const;
 
@@ -87,6 +93,8 @@ public:
 	void musicResume();
 	void musicStop();
 	bool musicIsOn() const;
+
+	void syncSoundSettings();
 };
 
 extern void playSound(int index, bool override);
diff --git a/engines/got/views/dialogs/main_menu.cpp b/engines/got/views/dialogs/main_menu.cpp
index 2798730a60e..752af9944cc 100644
--- a/engines/got/views/dialogs/main_menu.cpp
+++ b/engines/got/views/dialogs/main_menu.cpp
@@ -36,6 +36,8 @@ MainMenu::MainMenu() : SelectOption("MainMenu", "God of Thunder Menu",
 
 bool MainMenu::msgFocus(const FocusMessage &msg) {
 	g_vars->resetEndGameFlags();
+	musicPlay("MENU", false);
+
 	return SelectOption::msgFocus(msg);
 }
 
diff --git a/engines/got/views/game.cpp b/engines/got/views/game.cpp
index 41d19a6db3d..d646774a155 100644
--- a/engines/got/views/game.cpp
+++ b/engines/got/views/game.cpp
@@ -40,6 +40,7 @@ Game::Game() : View("Game") {
 
 bool Game::msgFocus(const FocusMessage &msg) {
 	Gfx::loadPalette();
+	musicPlay(_G(levelMusic), false);
 	return View::msgFocus(msg);
 }
 
diff --git a/engines/got/views/opening.cpp b/engines/got/views/opening.cpp
index 6a1f3ec6b76..49c7533fa5a 100644
--- a/engines/got/views/opening.cpp
+++ b/engines/got/views/opening.cpp
@@ -100,7 +100,9 @@ bool Opening::tick() {
 	} else if (_frameCtr == 41) {
 		_shakeX = 0;
 		redraw();
-	} else if (_frameCtr == 150) {
+	} else if (_frameCtr == 100) {
+		musicPlay("MENU", true);
+	} else if (_frameCtr == 200) {
 		fadeOut();
 		replaceView("Credits", true);
 	}




More information about the Scummvm-git-logs mailing list