[Scummvm-git-logs] scummvm master -> 2fc6dba3329cb4dde2cb2f3aaf2ef3ba0876cdb9

athrxx noreply at scummvm.org
Fri Jun 2 15:04:53 UTC 2023


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

Summary:
320c2f6983 KYRA: (EOB II/PC98) - fix some more text glitches
0d206f65a1 KYRA: (EOB) - fix minor menu graphics glitch
79ae9ec1d3 KYRA: (EOB/SegaCD) - minor gfx improvement
ec3885a0c5 KYRA: (EOB II/PC98) - fix minor gfx glitch
861d0008b5 KYRA: (EOB II/PC98) - fix game over load dialog font
2fc6dba332 KYRA: (EOB II/PC98) - add sound support


Commit: 320c2f698318023764993b2c742172979b014f21
    https://github.com/scummvm/scummvm/commit/320c2f698318023764993b2c742172979b014f21
Author: athrxx (athrxx at scummvm.org)
Date: 2023-06-02T17:04:19+02:00

Commit Message:
KYRA: (EOB II/PC98) - fix some more text glitches

Changed paths:
    engines/kyra/engine/chargen.cpp
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/engine/eobcommon.h
    engines/kyra/engine/items_eob.cpp
    engines/kyra/graphics/screen_eob_pc98.cpp
    engines/kyra/gui/gui_eob.cpp


diff --git a/engines/kyra/engine/chargen.cpp b/engines/kyra/engine/chargen.cpp
index 11dada2c191..169c7b25bb8 100644
--- a/engines/kyra/engine/chargen.cpp
+++ b/engines/kyra/engine/chargen.cpp
@@ -1966,6 +1966,7 @@ private:
 	void giveKhelbensCoin();
 
 	Common::String convertFromJISX0201(const Common::String &src);
+	Common::String makeTwoByteString(const Common::String &src);
 
 	EoBCoreEngine *_vm;
 	Screen_EoB *_screen;
@@ -2227,8 +2228,6 @@ int TransferPartyWiz::selectCharactersMenu() {
 	return selection;
 }
 
-
-
 void TransferPartyWiz::drawCharPortraitWithStats(int charIndex, bool enabled) {
 	int16 x = (charIndex % 2) * 159;
 	int16 y = (charIndex / 2) * 40;
@@ -2296,7 +2295,8 @@ void TransferPartyWiz::convertStats() {
 		if (_vm->_flags.lang == Common::JA_JPN && _vm->_flags.platform == Common::kPlatformPC98) {
 			Common::String cname(c->name);
 			cname = convertFromJISX0201(cname);
-			Common::strlcpy(c->name, cname.c_str(), cname.size() + 1);
+			cname = makeTwoByteString(cname);
+			Common::strlcpy(c->name, cname.c_str(), sizeof(c->name));
 		}
 
 		for (int ii = 0; ii < 25; ii++) {
@@ -2553,6 +2553,29 @@ Common::String TransferPartyWiz::convertFromJISX0201(const Common::String &src)
 	return tmp;
 }
 
+Common::String TransferPartyWiz::makeTwoByteString(const Common::String &src) {
+	Common::String n;
+	for (const uint8 *s = (const uint8*)src.c_str(); *s; ++s) {
+		if (*s < 32 || *s == 127) {
+			n += (char)*s;
+		} else if (*s < 127) {
+			uint8 c = (*s - 32) * 2;
+			assert(c < 190);
+			n += _vm->_ascii2SjisTables2[0][c];
+			n += _vm->_ascii2SjisTables2[0][c + 1];
+		} else if (*s < 212) {
+			n += '\x83';
+			n += (char)(*s - 64);
+		} else {
+			uint8 c = (*s - 212) * 2;
+			assert(c < 8);
+			n += _vm->_ascii2SjisTables2[1][c];
+			n += _vm->_ascii2SjisTables2[1][c + 1];
+		}
+	}
+	return n;
+}
+
 // Start functions
 
 bool EoBCoreEngine::startCharacterGeneration(bool defaultParty) {
diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index ab7b40d7ba0..f53943e2dfa 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -2046,7 +2046,7 @@ bool EoBCoreEngine::checkPassword() {
 	return true;
 }
 
-Common::String EoBCoreEngine::convertAsciiToSjis(const Common::String &str) {
+Common::String EoBCoreEngine::makeTwoByteString(const Common::String &str) {
 	if (_flags.platform != Common::kPlatformFMTowns)
 		return str;
 
diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h
index ecce2f54990..57e16cc59e5 100644
--- a/engines/kyra/engine/eobcommon.h
+++ b/engines/kyra/engine/eobcommon.h
@@ -915,7 +915,7 @@ protected:
 	virtual void seq_segaPausePlayer(bool pause) {}
 	bool checkPassword();
 
-	Common::String convertAsciiToSjis(const Common::String &str);
+	Common::String makeTwoByteString(const Common::String &str);
 
 	virtual int resurrectionSelectDialogue() = 0;
 	virtual void useHorn(int charIndex, int weaponSlot) {}
diff --git a/engines/kyra/engine/items_eob.cpp b/engines/kyra/engine/items_eob.cpp
index 7cc4f33a152..008476006a9 100644
--- a/engines/kyra/engine/items_eob.cpp
+++ b/engines/kyra/engine/items_eob.cpp
@@ -500,7 +500,7 @@ void EoBCoreEngine::printFullItemName(Item item) {
 
 	int cs = (_flags.platform == Common::kPlatformSegaCD && _flags.lang == Common::JA_JPN && _screen->getNumberOfCharacters((tmpString).c_str()) >= 17) ? _screen->setFontStyles(_screen->_currentFont, Font::kStyleNarrow2) : -1;
 
-	_txt->printMessage(convertAsciiToSjis(tmpString).c_str());
+	_txt->printMessage(makeTwoByteString(tmpString).c_str());
 
 	if (cs != -1)
 		_screen->setFontStyles(_screen->_currentFont, cs);
diff --git a/engines/kyra/graphics/screen_eob_pc98.cpp b/engines/kyra/graphics/screen_eob_pc98.cpp
index 4f4f7735ad1..93a2b08ab1e 100644
--- a/engines/kyra/graphics/screen_eob_pc98.cpp
+++ b/engines/kyra/graphics/screen_eob_pc98.cpp
@@ -319,7 +319,7 @@ uint16 Font12x12PC98::convert(uint16 c) const {
 }
 
 PC98Font::PC98Font(uint8 shadowColor, bool useOverlay, int scaleV, const uint8 *convTable1, const char *convTable2, const char *convTable3) : OldDOSFont(Common::kRenderVGA, shadowColor),
-	_convTable1(convTable1), _convTable2(convTable2), _convTable3(convTable3), _outputWidth(0), _outputHeight(0), _type(convTable1 && convTable2 && convTable3 ? kJIS_X0201 : kASCII) {
+	_convTable1(convTable1), _convTable2(convTable2), _convTable3(convTable3), _outputWidth(0), _outputHeight(0), _type(convTable1 && convTable2 && convTable3 ? kSJIS : kASCII) {
 	_numGlyphsMax = 256;
 	_useOverlay = useOverlay;
 	_scaleV = scaleV;
@@ -341,7 +341,7 @@ bool PC98Font::load(Common::SeekableReadStream &file) {
 }
 
 uint16 PC98Font::convert(uint16 c) const {
-	if (_type == kJIS_X0201)
+	if (_type == kSJIS)
 		c = makeTwoByte(c);
 
 	if (!_convTable1 || c < 128)
diff --git a/engines/kyra/gui/gui_eob.cpp b/engines/kyra/gui/gui_eob.cpp
index 85fac61261e..0f314d44d93 100644
--- a/engines/kyra/gui/gui_eob.cpp
+++ b/engines/kyra/gui/gui_eob.cpp
@@ -2904,7 +2904,7 @@ int GUI_EoB::getTextInput(char *dest, int x, int y, int destMaxLen, int textColo
 					if (_vm->_flags.platform == Common::kPlatformFMTowns && _keyPressed.ascii > 31 && _keyPressed.ascii < 123) {
 						Common::String s;
 						s.insertChar(in & 0xff, 0);
-						s = _vm->convertAsciiToSjis(s);
+						s = _vm->makeTwoByteString(s);
 						if (s.empty()) {
 							in = 0;
 						} else {
@@ -3966,7 +3966,8 @@ bool GUI_EoB::restParty() {
 	for (int l = 0; !res && restLoop && !_vm->shouldQuit();) {
 		l++;
 
-		int cs = (_vm->gameFlags().platform == Common::kPlatformSegaCD && _vm->gameFlags().lang == Common::JA_JPN) ? _screen->setFontStyles(_screen->_currentFont, Font::kStyleNarrow1) : -1;
+		int cs = (_vm->gameFlags().platform == Common::kPlatformSegaCD && _vm->gameFlags().lang == Common::JA_JPN) ? _screen->setFontStyles(_screen->_currentFont, Font::kStyleNarrow1) :
+			((_vm->gameFlags().platform == Common::kPlatformPC98 && !_vm->gameFlags().use16ColorMode) ? _screen->setFontStyles(_menuFont, Font::kStyleNone) : -1);
 
 		// Regenerate spells
 		for (int i = 0; i < 6; i++) {


Commit: 0d206f65a1c35da9f07d4150a93628a213b503d1
    https://github.com/scummvm/scummvm/commit/0d206f65a1c35da9f07d4150a93628a213b503d1
Author: athrxx (athrxx at scummvm.org)
Date: 2023-06-02T17:04:19+02:00

Commit Message:
KYRA: (EOB) - fix minor menu graphics glitch

Changed paths:
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/gui/gui_eob.cpp


diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index f53943e2dfa..50865482e26 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -702,7 +702,7 @@ void EoBCoreEngine::writeSettings() {
 	if (_sound) {
 		if (_flags.platform == Common::kPlatformPC98 || _flags.platform == Common::kPlatformSegaCD) {
 			if (!_configMusic)
-				snd_playSong(0);
+				snd_stopSound();
 		} else if (!_configSounds) {
 			_sound->haltTrack();
 		}
diff --git a/engines/kyra/gui/gui_eob.cpp b/engines/kyra/gui/gui_eob.cpp
index 0f314d44d93..d80f7c58db0 100644
--- a/engines/kyra/gui/gui_eob.cpp
+++ b/engines/kyra/gui/gui_eob.cpp
@@ -2322,12 +2322,17 @@ void GUI_EoB::runCampMenu() {
 	Button *buttonList = 0;
 
 	for (bool runLoop = true; runLoop && !_vm->shouldQuit();) {
+		bool buttonsUnchanged = true;
+
 		if (newMenu != -1) {
 			drawCampMenu();
+
 			if (newMenu == 2) {
 				updateOptionsStrings();
 				if (_vm->gameFlags().platform == Common::kPlatformSegaCD)
 					keepButtons = false;
+				else
+					buttonsUnchanged = false;
 			}
 			if (!keepButtons) {
 				releaseButtons(buttonList);
@@ -2569,8 +2574,11 @@ void GUI_EoB::runCampMenu() {
 		} else {
 			Common::Point p = _vm->getMousePos();
 			for (Button *b = buttonList; b; b = b->nextButton) {
-				if ((b->arg & 2) && _vm->posWithinRect(p.x, p.y, b->x, b->y, b->x + b->width, b->y + b->height))
+				if ((b->arg & 2) && _vm->posWithinRect(p.x, p.y, b->x, b->y, b->x + b->width, b->y + b->height)) {
+					if (highlightButton && highlightButton != b && !prevHighlightButton)
+						prevHighlightButton = highlightButton;
 					highlightButton = b;
+				}
 			}
 		}
 
@@ -2584,7 +2592,7 @@ void GUI_EoB::runCampMenu() {
 		_charSelectRedraw = redrawPortraits = false;
 
 		if (prevHighlightButton != highlightButton && newMenu == -1 && runLoop) {
-			drawMenuButton(prevHighlightButton, false, false, true);
+			drawMenuButton(prevHighlightButton, false, false, buttonsUnchanged);
 			drawMenuButton(highlightButton, false, true, false);
 			_screen->updateScreen();
 			prevHighlightButton = highlightButton;


Commit: 79ae9ec1d362ed08f11de463c4c914d29b503b09
    https://github.com/scummvm/scummvm/commit/79ae9ec1d362ed08f11de463c4c914d29b503b09
Author: athrxx (athrxx at scummvm.org)
Date: 2023-06-02T17:04:19+02:00

Commit Message:
KYRA: (EOB/SegaCD) - minor gfx improvement

(button behaviour in character generator)

Changed paths:
    engines/kyra/engine/chargen.cpp


diff --git a/engines/kyra/engine/chargen.cpp b/engines/kyra/engine/chargen.cpp
index 169c7b25bb8..f67e1e0db4e 100644
--- a/engines/kyra/engine/chargen.cpp
+++ b/engines/kyra/engine/chargen.cpp
@@ -548,7 +548,8 @@ void CharacterGenerator::drawButton(int index, int buttonState) {
 		const uint8 *bt = &_chargenSegaButtonCoords[index * 5];
 		_screen->sega_getRenderer()->fillRectWithTiles(0, bt[0], bt[1], bt[2], bt[3], (index > 9 ? 0x24BC : 0x2411) + bt[4] + (buttonState ? (bt[2] * bt[3]) : 0), true);
 		_screen->sega_getRenderer()->render(0, bt[0], bt[1], bt[2], bt[3]);
-		_screen->updateScreen();
+		if (buttonState)
+			_screen->updateScreen();
 		return;
 	}
 
@@ -588,7 +589,7 @@ void CharacterGenerator::drawButton(int index, int buttonState) {
 
 	_screen->copyRegion(160, 0, c->destX << 3, c->destY, p->w << 3, p->h, 2, 0, Screen::CR_NO_P_CHECK);
 	_screen->updateScreen();
-}
+ }
 
 void CharacterGenerator::processButtonClick(int index) {
 	drawButton(index, 1);


Commit: ec3885a0c579c9d4973c39c66d4a9cd4bba6d989
    https://github.com/scummvm/scummvm/commit/ec3885a0c579c9d4973c39c66d4a9cd4bba6d989
Author: athrxx (athrxx at scummvm.org)
Date: 2023-06-02T17:04:20+02:00

Commit Message:
KYRA: (EOB II/PC98) - fix minor gfx glitch

(completely clear Japanese text when leaving party
transfer dialog)

Changed paths:
    engines/kyra/engine/chargen.cpp


diff --git a/engines/kyra/engine/chargen.cpp b/engines/kyra/engine/chargen.cpp
index f67e1e0db4e..0266005fdf0 100644
--- a/engines/kyra/engine/chargen.cpp
+++ b/engines/kyra/engine/chargen.cpp
@@ -2005,8 +2005,11 @@ TransferPartyWiz::~TransferPartyWiz() {
 bool TransferPartyWiz::start() {
 	_screen->copyPage(0, 12);
 
-	if (!selectAndLoadTransferFile())
+	if (!selectAndLoadTransferFile()) {
+		_screen->clearPage(0);
+		_screen->clearPage(2);
 		return false;
+	}
 
 	convertStats();
 


Commit: 861d0008b51fc8f5a3da884e6f3d81d6528ab1fb
    https://github.com/scummvm/scummvm/commit/861d0008b51fc8f5a3da884e6f3d81d6528ab1fb
Author: athrxx (athrxx at scummvm.org)
Date: 2023-06-02T17:04:20+02:00

Commit Message:
KYRA: (EOB II/PC98) - fix game over load dialog font

Changed paths:
    engines/kyra/engine/eobcommon.cpp


diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index 50865482e26..96eb9e67bda 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -777,7 +777,7 @@ bool EoBCoreEngine::checkPartyStatus(bool handleDeath) {
 	gui_drawAllCharPortraitsWithStats();
 
 	if (checkPartyStatusExtra()) {
-		Screen::FontId of = _screen->setFont(_flags.use16ColorMode ? Screen::FID_SJIS_FNT : Screen::FID_8_FNT);
+		Screen::FontId of = _screen->setFont(_titleFont);
 		gui_updateControls();
 		int x = 0;
 		int y = 0;


Commit: 2fc6dba3329cb4dde2cb2f3aaf2ef3ba0876cdb9
    https://github.com/scummvm/scummvm/commit/2fc6dba3329cb4dde2cb2f3aaf2ef3ba0876cdb9
Author: athrxx (athrxx at scummvm.org)
Date: 2023-06-02T17:04:20+02:00

Commit Message:
KYRA: (EOB II/PC98) - add sound support

(still with minor issues, but mostly finished)

Changed paths:
    engines/kyra/engine/chargen.cpp
    engines/kyra/engine/darkmoon.cpp
    engines/kyra/engine/darkmoon.h
    engines/kyra/engine/eobcommon.h
    engines/kyra/engine/kyra_rpg.cpp
    engines/kyra/gui/gui_eob.cpp
    engines/kyra/resource/staticres_eob.cpp
    engines/kyra/sequence/sequences_darkmoon.cpp
    engines/kyra/sound/drivers/capcom98.cpp
    engines/kyra/sound/drivers/capcom98.h
    engines/kyra/sound/sound_intern.h
    engines/kyra/sound/sound_pc98_darkmoon.cpp
    engines/kyra/sound/sound_pc98_eob.cpp


diff --git a/engines/kyra/engine/chargen.cpp b/engines/kyra/engine/chargen.cpp
index 0266005fdf0..3cd03201112 100644
--- a/engines/kyra/engine/chargen.cpp
+++ b/engines/kyra/engine/chargen.cpp
@@ -89,6 +89,7 @@ private:
 	uint16 _chargenMaxStats[7];
 
 	const uint8 _menuColor1, _menuColor2, _menuColor3;
+	const uint8 _trackNo;
 
 	const char *const *_chargenStrings1;
 	const char *const *_chargenStrings2;
@@ -141,7 +142,9 @@ CharacterGenerator::CharacterGenerator(EoBCoreEngine *vm, Screen_EoB *screen) :
 	_updateBoxShapesIndex(0), _lastUpdateBoxShapesIndex(0), _magicShapesBox(6), _activeBox(0),
 	_menuColor1(vm->gameFlags().platform == Common::kPlatformSegaCD ? 0xFF : (vm->_configRenderMode == Common::kRenderCGA ? 1 : vm->guiSettings()->colors.guiColorWhite)),
 	_menuColor2(vm->gameFlags().platform == Common::kPlatformSegaCD ? 0x55 : vm->guiSettings()->colors.guiColorLightRed),
-	_menuColor3(vm->gameFlags().platform == Common::kPlatformSegaCD ? 0x99 : vm->guiSettings()->colors.guiColorBlack) {
+	_menuColor3(vm->gameFlags().platform == Common::kPlatformSegaCD ? 0x99 : vm->guiSettings()->colors.guiColorBlack),
+	_trackNo(vm->game() == GI_EOB1 ? (vm->gameFlags().platform == Common::kPlatformPC98 ? 1 :
+		(vm->gameFlags().platform == Common::kPlatformSegaCD ? 8: 20)) : (vm->gameFlags().platform == Common::kPlatformPC98 ? 53 : 13)) {
 
 	_chargenStatStrings = _vm->_chargenStatStrings;
 	_chargenRaceSexStrings = _vm->_chargenRaceSexStrings;
@@ -352,7 +355,7 @@ bool CharacterGenerator::createCustomParty(const uint8 ***faceShapes) {
 	checkForCompleteParty();
 	initButtonsFromList(0, 5);
 
-	_vm->snd_playSong(_vm->game() == GI_EOB1 ? (_vm->gameFlags().platform == Common::kPlatformPC98 ? 1 : (_vm->gameFlags().platform == Common::kPlatformSegaCD ? 8: 20)) : 13);
+	_vm->snd_playSong(_trackNo);
 	_activeBox = 0;
 
 	for (bool loop = true; loop && (!_vm->shouldQuit());) {
@@ -894,13 +897,13 @@ int CharacterGenerator::getInput(Button *buttonList) {
 
 	if (_vm->game() == GI_EOB1 && _vm->sound()->checkTrigger()) {
 		_vm->sound()->resetTrigger();
-		_vm->snd_playSong(20);
+		_vm->snd_playSong(_trackNo);
 	} else if (_vm->game() == GI_EOB2 && !_vm->sound()->isPlaying()) {
 		// WORKAROUND for EOB II: The original implements the same sound trigger check as in EOB I.
 		// However, Westwood seems to have forgotten to set the trigger at the end of the AdLib song,
 		// so that the music will not loop. We simply check whether the sound driver is still playing.
 		_vm->delay(3 * _vm->_tickLength);
-		_vm->snd_playSong(13);
+		_vm->snd_playSong(_trackNo);
 	}
 
 	return _vm->checkInput(buttonList, false, 0);
diff --git a/engines/kyra/engine/darkmoon.cpp b/engines/kyra/engine/darkmoon.cpp
index 0928387df79..4aea8b53b01 100644
--- a/engines/kyra/engine/darkmoon.cpp
+++ b/engines/kyra/engine/darkmoon.cpp
@@ -606,6 +606,11 @@ bool DarkMoonEngine::restParty_extraAbortCondition() {
 	return true;
 }
 
+void DarkMoonEngine::snd_playLevelScore() {
+	if (_flags.platform == Common::kPlatformPC98)
+		snd_playSong(0);
+}
+
 void DarkMoonEngine::snd_loadAmigaSounds(int level, int sub) {
 	if (_flags.platform != Common::kPlatformAmiga)
 		return;
@@ -691,9 +696,9 @@ void DarkMoonEngine::snd_loadAmigaSounds(int level, int sub) {
 	_amigaCurSoundIndex = sndIndex;
 }
 
-void DarkMoonEngine::snd_playLevelScore() {
-	if (_flags.platform == Common::kPlatformPC98)
-		snd_playSong(0);
+void DarkMoonEngine::snd_updateLevelScore() {
+	if (_flags.platform == Common::kPlatformPC98 && !_sound->isPlaying())
+		snd_playLevelScore();
 }
 
 void DarkMoonEngine::useHorn(int charIndex, int weaponSlot) {
diff --git a/engines/kyra/engine/darkmoon.h b/engines/kyra/engine/darkmoon.h
index 743459e3a40..372af40ee50 100644
--- a/engines/kyra/engine/darkmoon.h
+++ b/engines/kyra/engine/darkmoon.h
@@ -120,8 +120,9 @@ private:
 	bool restParty_extraAbortCondition() override;
 
 	// Sound
-	void snd_loadAmigaSounds(int level, int sub) override;
 	void snd_playLevelScore() override;
+	void snd_loadAmigaSounds(int level, int sub) override;
+	void snd_updateLevelScore() override;
 
 	const char *const *_amigaSoundFiles2;
 	const char *const *_amigaSoundMapExtra;
diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h
index 57e16cc59e5..506292bb127 100644
--- a/engines/kyra/engine/eobcommon.h
+++ b/engines/kyra/engine/eobcommon.h
@@ -1270,7 +1270,7 @@ protected:
 	void snd_stopSound();
 	void snd_fadeOut(int del = 160);
 	virtual void snd_loadAmigaSounds(int level, int sub) = 0;
-	virtual void snd_updateLevelScore() {}
+	virtual void snd_updateLevelScore() = 0;
 
 	const char **_amigaSoundMap;
 	const char *const *_amigaLevelSoundList1;
diff --git a/engines/kyra/engine/kyra_rpg.cpp b/engines/kyra/engine/kyra_rpg.cpp
index df0fa5c9bf6..1e40a8fdc3d 100644
--- a/engines/kyra/engine/kyra_rpg.cpp
+++ b/engines/kyra/engine/kyra_rpg.cpp
@@ -218,7 +218,7 @@ Common::Error KyraRpgEngine::init() {
 
 	initStaticResource();
 
-	_envSfxDistThreshold = ((_flags.gameID == GI_EOB2 && _sound->getSfxType() == Sound::kTowns) || _sound->getSfxType() == Sound::kAdLib || _sound->getSfxType() == Sound::kPCSpkr) ? 15 : (_sound->getSfxType() == Sound::kAmiga ? 4 : 3);
+	_envSfxDistThreshold = ((_flags.gameID == GI_EOB2 && (_sound->getSfxType() == Sound::kTowns || _sound->getSfxType() == Sound::kPC98)) || _sound->getSfxType() == Sound::kAdLib || _sound->getSfxType() == Sound::kPCSpkr) ? 15 : (_sound->getSfxType() == Sound::kAmiga ? 4 : 3);
 
 	_dialogueButtonLabelColor1 = guiSettings()->buttons.labelColor1;
 	_dialogueButtonLabelColor2 = guiSettings()->buttons.labelColor2;
@@ -405,6 +405,8 @@ bool KyraRpgEngine::snd_processEnvironmentalSoundEffect(int soundId, int block)
 
 	if (_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns)
 		_environmentSfxVol = dist ? (16 - dist) * 8 - 1 : 127;
+	else if (_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformPC98)
+		_environmentSfxVol = (15 - dist) * 6 + 37;
 	else if (_flags.platform == Common::kPlatformAmiga)
 		_environmentSfxVol = dist ? (soundId != 13 ? dist : (dist >= 4) ? 4 : dist) : 1;
 	else if (_flags.platform == Common::kPlatformSegaCD)
diff --git a/engines/kyra/gui/gui_eob.cpp b/engines/kyra/gui/gui_eob.cpp
index d80f7c58db0..962c4597805 100644
--- a/engines/kyra/gui/gui_eob.cpp
+++ b/engines/kyra/gui/gui_eob.cpp
@@ -2526,7 +2526,7 @@ void GUI_EoB::runCampMenu() {
 					else if (_vm->_configMusic)
 						_vm->snd_playSong(11);
 					else
-						_vm->snd_playSong(0);
+						_vm->snd_stopSound();
 				} else {
 					_vm->_configSounds ^= true;
 					_vm->_configMusic = _vm->_configSounds ? 1 : 0;
diff --git a/engines/kyra/resource/staticres_eob.cpp b/engines/kyra/resource/staticres_eob.cpp
index 8d02a530f2f..585e082dd6e 100644
--- a/engines/kyra/resource/staticres_eob.cpp
+++ b/engines/kyra/resource/staticres_eob.cpp
@@ -537,7 +537,7 @@ void EoBCoreEngine::initStaticResource() {
 		_sound->initAudioResourceInfo(kMusicIntro, &intro);
 		_sound->initAudioResourceInfo(kMusicFinale, &finale);
 
-	} else if (_flags.platform != Common::kPlatformPC98) {
+	} else if (_flags.platform != Common::kPlatformPC98 || _flags.gameID == GI_EOB2) {
 		const char *const *files = _staticres->loadStrings(kEoBBaseSoundFilesIngame, temp);
 		SoundResourceInfo_PC ingame(files, temp);
 		files = _staticres->loadStrings(kEoBBaseSoundFilesIntro, temp);
diff --git a/engines/kyra/sequence/sequences_darkmoon.cpp b/engines/kyra/sequence/sequences_darkmoon.cpp
index 960d525beb3..a64d2e1ccd2 100644
--- a/engines/kyra/sequence/sequences_darkmoon.cpp
+++ b/engines/kyra/sequence/sequences_darkmoon.cpp
@@ -273,7 +273,7 @@ void DarkMoonEngine::seq_playIntro() {
 	// PC-98 --- SFX 0
 
 	if (!skipFlag() && !shouldQuit())
-		snd_playSong(12);
+		snd_playSong(_flags.platform == Common::kPlatformPC98 ? 54 : 12);
 
 	_screen->copyRegion(0, 0, 8, 8, 304, 128, 2, 0, Screen::CR_NO_P_CHECK);
 	sq.setPalette(9);
@@ -346,7 +346,8 @@ void DarkMoonEngine::seq_playIntro() {
 
 	sq.printText(3, textColor1);    // The message was urgent.
 
-	// PC-98 --- SFX 1
+	if (_flags.platform == Common::kPlatformPC98)
+		snd_playSong(55);
 
 	sq.loadScene(1, 2);
 	sq.waitForSongNotifier(++songCurPos);
@@ -446,7 +447,8 @@ void DarkMoonEngine::seq_playIntro() {
 
 	sq.animCommand(16);
 
-	// PC-98 --- SFX 2
+	if (_flags.platform == Common::kPlatformPC98)
+		snd_playSong(56);
 
 	sq.printText(7, textColor2);    // Thank you for coming so quickly
 	sq.animCommand(16);
@@ -592,7 +594,8 @@ void DarkMoonEngine::seq_playIntro() {
 	sq.animCommand(20);
 	sq.animCommand(18);
 
-	// PC-98 --- SFX 3
+	if (_flags.platform == Common::kPlatformPC98)
+		snd_playSong(57);
 
 	sq.fadeText();
 	sq.animCommand(29);
@@ -618,7 +621,7 @@ void DarkMoonEngine::seq_playIntro() {
 	else {
 		_screen->setScreenDim(17);
 		_screen->clearCurDim();
-		snd_playSoundEffect(14);
+		snd_playSoundEffect(_flags.platform == Common::kPlatformPC98 ? 12 : 14);
 
 		if (_configRenderMode != Common::kRenderEGA)
 			sq.fadePalette(10, 1);
@@ -659,7 +662,7 @@ void DarkMoonEngine::seq_playFinale() {
 	sq.delay(18);
 
 	if (!skipFlag() && !shouldQuit() && _flags.platform != Common::kPlatformAmiga)
-		snd_playSong(1);
+		snd_playSong(_flags.platform == Common::kPlatformPC98 ? 52 : 1);
 	sq.update(2);
 
 	sq.loadScene(1, 2);
@@ -784,8 +787,10 @@ void DarkMoonEngine::seq_playFinale() {
 
 	sq.waitForSongNotifier(3);
 
+	int sfxOffset = (_flags.platform == Common::kPlatformPC98) ? -1 : 0;
+
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(7);
+		snd_playSoundEffect(7 + sfxOffset);
 	sq.delay(8);
 
 	sq.animCommand(10);
@@ -819,7 +824,7 @@ void DarkMoonEngine::seq_playFinale() {
 	sq.waitForSongNotifier(4);
 
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(7);
+		snd_playSoundEffect(7 + sfxOffset);
 	sq.delay(8);
 
 	sq.animCommand(10);
@@ -856,7 +861,7 @@ void DarkMoonEngine::seq_playFinale() {
 	sq.delay(36);
 
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(11);
+		snd_playSoundEffect(11 + sfxOffset);
 
 	sq.delay(54);
 	sq.fadeText();
@@ -865,7 +870,7 @@ void DarkMoonEngine::seq_playFinale() {
 	sq.waitForSongNotifier(5);
 
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(6);
+		snd_playSoundEffect(6 + sfxOffset);
 
 	if (_flags.platform == Common::kPlatformAmiga)
 		sq.copyPalette(6, 0);
@@ -882,7 +887,7 @@ void DarkMoonEngine::seq_playFinale() {
 	sq.animCommand(19);
 	sq.animCommand(19, 36);
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(12);
+		snd_playSoundEffect(12 + sfxOffset);
 	sq.fadeText();
 
 	sq.printText(17, textColor2);           // Thank you
@@ -892,12 +897,12 @@ void DarkMoonEngine::seq_playFinale() {
 
 	sq.printText(18, textColor2);           // You have earned my deepest respect
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(11);
+		snd_playSoundEffect(11 + sfxOffset);
 	sq.animCommand(20);
 	sq.animCommand(19);
 	sq.animCommand(19);
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(11);
+		snd_playSoundEffect(11 + sfxOffset);
 	sq.delay(36);
 	sq.fadeText();
 
@@ -905,13 +910,13 @@ void DarkMoonEngine::seq_playFinale() {
 	sq.animCommand(19);
 	sq.animCommand(19, 18);
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(11);
+		snd_playSoundEffect(11 + sfxOffset);
 	sq.animCommand(20, 18);
 	sq.fadeText();
 
 	sq.delay(28);
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(12);
+		snd_playSoundEffect(12 + sfxOffset);
 	sq.delay(3);
 
 	sq.loadScene(5, 2);
@@ -919,26 +924,26 @@ void DarkMoonEngine::seq_playFinale() {
 		_screen->copyRegion(0, 0, 8, 8, 304, 128, 2, 0, Screen::CR_NO_P_CHECK);
 	} else {
 		sq.updateAmigaSound();
-		snd_playSoundEffect(6);
+		snd_playSoundEffect(6 + sfxOffset);
 		if (_configRenderMode != Common::kRenderEGA)
 			sq.setPaletteWithoutTextColor(0);
 		_screen->crossFadeRegion(0, 0, 8, 8, 304, 128, 2, 0);
 	}
 
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(12);
+		snd_playSoundEffect(12 + sfxOffset);
 	sq.delay(5);
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(11);
+		snd_playSoundEffect(11 + sfxOffset);
 	sq.delay(11);
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(12);
+		snd_playSoundEffect(12 + sfxOffset);
 	sq.delay(7);
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(11);
+		snd_playSoundEffect(11 + sfxOffset);
 	sq.delay(12);
 	if (!skipFlag() && !shouldQuit())
-		snd_playSoundEffect(12);
+		snd_playSoundEffect(12 + sfxOffset);
 	sq.updateAmigaSound();
 
 	removeInputTop();
@@ -958,7 +963,7 @@ void DarkMoonEngine::seq_playFinale() {
 
 	sq.delay(18);
 	if (!skipFlag() && !shouldQuit() && _flags.platform != Common::kPlatformAmiga)
-		snd_playSong(_flags.platform == Common::kPlatformFMTowns ? 16 : 1);
+		snd_playSong(_flags.platform == Common::kPlatformFMTowns ? 16 : (_flags.platform == Common::kPlatformPC98 ? 52 : 1));
 
 	int temp = 0;
 
diff --git a/engines/kyra/sound/drivers/capcom98.cpp b/engines/kyra/sound/drivers/capcom98.cpp
index b8600b54916..5ac992da59b 100644
--- a/engines/kyra/sound/drivers/capcom98.cpp
+++ b/engines/kyra/sound/drivers/capcom98.cpp
@@ -22,31 +22,1376 @@
 #ifdef ENABLE_EOB
 
 #include "kyra/sound/drivers/capcom98.h"
+#include "audio/softsynth/fmtowns_pc98/pc98_audio.h"
+#include "common/func.h"
+#include "common/system.h"
 
 namespace Kyra {
 
+class CapcomPC98Player {
+public:
+	enum Flags {
+		kStdPlay	= 1 << 0,
+		kPrioPlay	= 1 << 1,
+		kPrioClaim	= 1 << 2,
+		kFadeOut	= 1 << 3,
+		kStdStop	= 1 << 8,
+		kPrioStop	= 1 << 9,
+		kSavedState	= 1 << 10
+	};
+
+	CapcomPC98Player(bool playerPrio, uint16 playerFlag, uint16 reservedChanFlags);
+	virtual ~CapcomPC98Player() {}
+
+	virtual bool init() = 0;
+	virtual void deinit() = 0;
+	virtual void reset() = 0;
+	virtual void loadInstruments(const uint8 *data, uint16 number) {}
+
+	void startSound(const uint8 *data, uint8 volume, bool loop);
+	void stopSound();
+	uint8 getMarker(uint8 id) const { return _soundMarkers[id & 0x0F]; }
+	static uint16 getPlayerStatus() { return _flags; }
+
+	virtual void setMasterVolume (int vol) = 0;
+
+	void nextTick();
+	virtual void processSounds() {}
+
+protected:
+	uint16 _soundMarkers[16];
+	const uint16 _reservedChanFlags;
+
+private:
+	virtual void send(uint32 evt) = 0;
+	virtual PC98AudioCore::MutexLock lockMutex() = 0;
+	void storeEvent(uint32 evt);
+	void restorePlayer();
+	virtual void restoreStateIntern() {}
+	uint16 playFlag() const { return _playerFlag & (kStdPlay | kPrioPlay); }
+	uint16 extraFlag() const { return _playerFlag & kPrioClaim; }
+	uint16 stopFlag() const { return (_playerFlag & (kStdPlay | kPrioPlay)) << 8; }
+
+	const uint8 *_data;
+	const uint8 *_curPos;
+	uint16 _numEventsTotal;
+	uint16 _numEventsLeft;
+	uint8 _volume;
+	uint16 _midiTicker;
+
+	static uint16 _flags;
+	bool _loop;
+
+	Common::Array<uint32> _storedEvents;
+
+	const bool _playerPrio;
+	const uint16 _playerFlag;
+};
+
+class CapcomPC98_MIDI final : public CapcomPC98Player {
+public:
+	typedef Common::Functor0Mem<PC98AudioCore::MutexLock, CapcomPC98AudioDriverInternal> MutexProc;
+
+	CapcomPC98_MIDI(MidiDriver::DeviceHandle dev, bool isMT32, MutexProc &mutexProc);
+	~CapcomPC98_MIDI();
+
+	bool init() override;
+	void deinit() override;
+	void reset() override;
+
+	void setMasterVolume (int vol) override;
+
+private:
+	void send(uint32 evt) override;
+	PC98AudioCore::MutexLock lockMutex() override;
+
+	MidiDriver *_midi;
+	const bool _isMT32;
+	const uint8 *_programMapping;
+
+	static const uint8 _programMapping_mt32ToGM[128];
+
+	MutexProc &_mproc;
+};
+
+class CapcomPC98_FM_Channel {
+public:
+	CapcomPC98_FM_Channel(uint8 id, PC98AudioCore *&ac, const Common::Array<const uint8*>&instruments);
+	~CapcomPC98_FM_Channel();
+
+	void reset();
+
+	void keyOff();
+	void noteOff(uint8 note);
+	void noteOn(uint8 note, uint8 velo);
+	void programChange(uint8 prg);
+	void pitchBend(uint16 pb);
+	void restore();
+
+	void modWheel(uint8 mw);
+	void breathControl(uint8 bc);
+	void pitchBendSensitivity(uint8 pbs);
+	void portamentoTime(uint8 pt);
+	void volume(uint8 vol);
+	void togglePortamento(uint8 enable);
+	void allNotesOff();
+
+	void processSounds();
+
+private:
+	typedef Common::Functor0Mem<void, CapcomPC98_FM_Channel> VbrHandlerProc;
+
+	const uint8 _regOffset;
+	uint8 _program;
+	bool _isPlaying;
+	uint8 _note;
+	uint8 _carrier;
+	uint8 _volume;
+	uint8 _velocity;
+	uint8 _noteEffCur;
+	uint8 _noteEffPrev;
+	uint8 _breathControl;
+	uint16 _frequency;
+	uint8 _modWheel;
+	uint16 _pitchBendSensitivity;
+	int16 _pitchBendPara;
+	int16 _pitchBendEff;
+	int32 _vbrState;
+	uint32 _vbrStep;
+	uint16 _vbrCycleTicker;
+	uint16 _vbrDelayTicker;
+	VbrHandlerProc *_vbrHandler;
+	bool _prtEnable;
+	uint8 _prtTime;
+	int32 _prtState;
+	int32 _prtStep;
+	uint16 _prtCycleLength;
+
+	struct Instrument {
+		char name[9];
+		uint8 fbl_alg;
+		uint8 vbrType;
+		uint8 vbrCycleLength;
+		uint8 vbrSensitivity;
+		uint8 resetEffect;
+		uint8 vbrDelay;
+		/*uint8 ff;
+		uint8 f10;
+		uint8 f11;
+		uint8 f12;
+		uint8 f13;
+		uint8 f14;*/
+		uint8 *regData;
+	} _instrument;
+
+private:
+	void loadInstrument(const uint8 *in);
+	void updatePitchBend();
+	void updateVolume();
+	void updatePortamento();
+	void updateFrequency();
+	void setupPortamento();
+	void setupVibrato();
+
+	void dummyProc() {}
+	void vbrHandler0();
+	void vbrHandler1();
+	void vbrHandler2();
+	void vbrHandler3();
+
+	VbrHandlerProc *_vbrHandlers[5];
+	PC98AudioCore *&_ac;
+	const Common::Array<const uint8*>&_instruments;
+
+	static const uint16 _freqMSBTable[12];
+	static const uint8 _freqLSBTables[12][64];
+	static const uint8 _volTablesInst[4][128];
+	static const uint8 _volTableOut[128];
+	static const uint8 _volTablePara[128];
+};
+
+class CapcomPC98_FM final : public CapcomPC98Player, PC98AudioPluginDriver {
+public:
+	typedef Common::Functor0Mem<void, CapcomPC98AudioDriverInternal> CBProc;
+
+	CapcomPC98_FM(Audio::Mixer *mixer, CBProc &cbproc, bool playerPrio, uint16 playerFlag, uint8 reservedChanFlags, bool needsTimer);
+	~CapcomPC98_FM() override;
+
+	bool init() override;
+	void deinit() override;
+	void reset() override;
+	void loadInstruments(const uint8 *data, uint16 number) override;
+
+	void setMasterVolume (int vol) override;
+
+	PC98AudioCore::MutexLock lockMutex() override;
+
+private:
+	void send(uint32 evt) override;
+	void timerCallbackB() override;
+	void processSounds() override;
+
+	void controlChange(uint8 ch, uint8 control, uint8 val);
+
+	void restoreStateIntern() override;
+
+	PC98AudioCore *_ac;
+	CapcomPC98_FM_Channel **_chan;
+	Common::Array<const uint8*>_instruments;
+
+	CBProc &_cbProc;
+
+	bool _ready;
+
+	static const uint8 _initData[72];
+};
+
 class CapcomPC98AudioDriverInternal {
 public:
-	CapcomPC98AudioDriverInternal();
+	CapcomPC98AudioDriverInternal(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev);
 	~CapcomPC98AudioDriverInternal();
 
+	static CapcomPC98AudioDriverInternal *open(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev);
+	static void close();
+
+	bool isUsable() const { return _ready; }
+
+	void reset();
+	void loadFMInstruments(const uint8 *data);
+	void startSong(const uint8 *data, uint8 volume, bool loop);
+	void stopSong();
+	void startSoundEffect(const uint8 *data, uint8 volume);
+	void stopSoundEffect();
+	int checkSoundMarker() const;
+	bool songIsPlaying() const;
+	bool soundEffectIsPlaying() const;
+
+	void setMusicVolume(int volume);
+	void setSoundEffectVolume(int volume);
+
+	void timerCallback();
+	PC98AudioCore::MutexLock lockMutex();
+
 private:
+	void updateMasterVolume();
+
+	CapcomPC98Player *_players[2];
+	CapcomPC98_FM *_fmDevice;
+	CapcomPC98_FM::CBProc *_timerProc;
+	CapcomPC98_MIDI::MutexProc *_mutexProc;
+
+	static CapcomPC98AudioDriverInternal *_refInstance;
+	static int _refCount;
+
+	int _musicVolume;
+	int _sfxVolume;
+
+	int _marker;
+	bool _ready;
+};
+
+uint16 CapcomPC98Player::_flags = 0;
+
+CapcomPC98Player::CapcomPC98Player(bool playerPrio, uint16 playerFlag, uint16 reservedChanFlags) : _playerPrio(playerPrio), _playerFlag(playerFlag), _reservedChanFlags(reservedChanFlags), _data(nullptr), _curPos(nullptr), _numEventsTotal(0), _numEventsLeft(0), _volume(0), _midiTicker(0), _loop(false) {
+	memset(_soundMarkers, 0, sizeof(_soundMarkers));
+	_flags = 0;
+}
+
+void CapcomPC98Player::startSound(const uint8 *data, uint8 volume, bool loop) {
+	stopSound();
+
+	PC98AudioCore::MutexLock lock = lockMutex();
+	_numEventsTotal = _numEventsLeft = READ_LE_UINT16(data);
+	_data = _curPos = data + 2;
+	_volume = volume & 0x7f;
+	_loop = loop;
+	_midiTicker = 0;
+
+	if (_volume != 0x7f) {
+		for (int i = 0; i < 16; ++i) {
+			if ((1 << i) & _reservedChanFlags)
+				send(0x0007B0 | i | (_volume << 16));
+		}
+	}
+
+	_flags &= ~(stopFlag() | kFadeOut);
+	_flags |= (playFlag() | extraFlag());
+}
+
+void CapcomPC98Player::stopSound() {
+	while (_flags & playFlag()) {
+		g_system->delayMillis(5);
+		PC98AudioCore::MutexLock lock = lockMutex();
+		_flags |= stopFlag();
+	}
+}
+
+void CapcomPC98Player::nextTick() {
+	if (_flags & playFlag()) {
+		if (!_playerPrio) {
+			if (_flags & kPrioClaim) {
+				_flags &= ~kPrioClaim;
+				_flags |= kSavedState;
+				for (int i = 0; i < 16; ++i) {
+					if ((1 << i) & ~_reservedChanFlags)
+						send(0x007BB0 | i);
+				}
+			} else if (((_flags & kPrioStop) || !(_flags & kPrioPlay)) && (_flags & kSavedState)) {
+				_flags &= ~kSavedState;
+				restorePlayer();
+			}
+		}
+
+		if (_flags & stopFlag()) {
+			for (int i = 0; i < 16; ++i) {
+				if ((1 << i) & _reservedChanFlags)
+					send(0x007BB0 | i);
+			}
+			_flags &= ~playFlag();
+
+		} else if (_numEventsLeft) {
+			bool eot = false;
+
+			while (_numEventsLeft && !eot) {
+				eot = false;
+
+				uint32 in = READ_LE_UINT32(_curPos);
+				if ((in & 0xFF) > _midiTicker)
+					break;
+
+				_midiTicker -= (in & 0xFF);
+				if (_playerPrio || !(_flags & kPrioPlay) || ((_flags & kPrioPlay) && ((1 << ((in >> 8) & 0x0f)) & _reservedChanFlags))) {
+					if (_volume == 0x7f || ((in >> 12) & 0xfff) != 0x70B)
+						send(in >> 8);
+				} else {
+					storeEvent(in >> 8);
+				}
+				_curPos += 4;
+				eot = (--_numEventsLeft == 0);
+			}
+
+			if (eot) {
+				if (_loop) {
+					_numEventsLeft = _numEventsTotal;
+					_curPos = _data;
+				} else if (_playerPrio || !(_flags & kPrioPlay)) {
+					for (int i = 0; i < 16; ++i) {
+						if ((1 << i) & _reservedChanFlags)
+							send(0x007BB0 | i);
+					}
+
+					_flags &= ~playFlag();
+				}
+			}
+		}
+	}
+
+	processSounds();
+
+	_midiTicker++;
+}
+
+void CapcomPC98Player::storeEvent(uint32 evt) {
+	if ((1 << (evt & 0x0f)) & _reservedChanFlags)
+		return;
+
+	uint8 st = evt & 0xff;
+
+	for (Common::Array<uint32>::iterator i = _storedEvents.begin(); i != _storedEvents.end(); ++i) {
+		if ((*i & 0xff) == st) {
+			*i = evt;
+			return;
+		}
+	}
+
+	st >>= 4;
+
+	if (st == 0x0b || st == 0x0c || st == 0x0e)
+		_storedEvents.push_back(evt);
+}
+
+void CapcomPC98Player::restorePlayer() {
+	restoreStateIntern();
+	for (Common::Array<uint32>::iterator i = _storedEvents.begin(); i != _storedEvents.end(); ++i)
+		send(*i);
+	_storedEvents.clear();
+}
+
+CapcomPC98_MIDI::CapcomPC98_MIDI(MidiDriver::DeviceHandle dev, bool isMT32, MutexProc &mutexProc) : CapcomPC98Player(true, kStdPlay, 0xffff), _isMT32(isMT32), _mproc(mutexProc), _midi(nullptr), _programMapping(nullptr) {
+	_midi = MidiDriver::createMidi(dev);
+	uint8 *map = new uint8[128];
+	assert(map);
+
+	if (isMT32) {		
+		memcpy(map, _programMapping_mt32ToGM, 128);
+	} else {
+		for (uint8 i = 0; i < 128; ++i)
+			map[i] = i;
+	}
+
+	_programMapping = map;
+}
+
+CapcomPC98_MIDI::~CapcomPC98_MIDI() {
+	_midi->close();
+	delete _midi;
+	delete[] _programMapping;
+}
+
+bool CapcomPC98_MIDI::init() {
+	if (!_midi || !_programMapping)
+		return false;
+
+	if (_midi->open())
+		return false;
+
+	if (_isMT32) {
+		_midi->sendMT32Reset();
+	} else {
+		static const byte gmResetSysEx[] = { 0x7E, 0x7F, 0x09, 0x01 };
+		_midi->sysEx(gmResetSysEx, sizeof(gmResetSysEx));
+		g_system->delayMillis(100);
+	}
+
+	reset();
+
+	return true;
+}
+
+void CapcomPC98_MIDI::deinit() {
+
+}
+
+void CapcomPC98_MIDI::reset() {
+
+}
+
+void CapcomPC98_MIDI::send(uint32 evt) {
+	if ((evt & 0xF0) == 0xC0) {
+		evt = (evt & 0xFF) | (_programMapping[(evt >> 8) & 0xFF] << 8);
+	} else if ((evt & 0xF0) == 0xB0 && ((evt >> 8) & 0xFF) == 3) {
+		_soundMarkers[evt & 0x0F] = (evt >> 16) & 0xFF;
+		return;
+	}
+	_midi->send(evt);
+}
+
+void CapcomPC98_MIDI::setMasterVolume (int vol) {
+
+}
+
+PC98AudioCore::MutexLock CapcomPC98_MIDI::lockMutex() {
+	if (!_mproc.isValid())
+		error("CapcomPC98_MIDI::lockMutex(): Invalid call");
+	return _mproc();
+}
+
+// This is not identical to the one we have in our common code (not even similar).
+const uint8 CapcomPC98_MIDI::_programMapping_mt32ToGM[128] = {
+	0x00, 0x02, 0x01, 0x03, 0x04, 0x05, 0x10, 0x13, 0x16, 0x65, 0x0a, 0x00, 0x68, 0x67, 0x2e, 0x25,
+	0x08, 0x09, 0x0a, 0x0c, 0x0d, 0x0e, 0x57, 0x38, 0x3b, 0x3c, 0x3d, 0x3e, 0x3b, 0x3b, 0x3b, 0x1f,
+	0x3d, 0x1c, 0x1c, 0x1c, 0x1c, 0x1e, 0x1f, 0x1f, 0x35, 0x38, 0x37, 0x38, 0x36, 0x33, 0x39, 0x70,
+	0x30, 0x30, 0x31, 0x22, 0x22, 0x22, 0x22, 0x7a, 0x58, 0x5a, 0x5e, 0x59, 0x5b, 0x60, 0x60, 0x1a,
+	0x51, 0x4f, 0x4e, 0x50, 0x54, 0x55, 0x56, 0x52, 0x4a, 0x49, 0x4c, 0x4d, 0x6e, 0x6b, 0x6d, 0x6c,
+	0x2f, 0x2f, 0x5e, 0x52, 0x57, 0x22, 0x56, 0x38, 0x20, 0x24, 0x5d, 0x22, 0x21, 0x5d, 0x4d, 0x5d,
+	0x29, 0x24, 0x66, 0x39, 0x22, 0x65, 0x22, 0x5c, 0x57, 0x69, 0x6a, 0x69, 0x6c, 0x6d, 0x0f, 0x35,
+	0x70, 0x71, 0x72, 0x76, 0x75, 0x74, 0x73, 0x77, 0x78, 0x79, 0x7a, 0x7c, 0x7b, 0x7d, 0x7e, 0x7f
 };
 
-CapcomPC98AudioDriverInternal::CapcomPC98AudioDriverInternal() {
+CapcomPC98_FM_Channel::CapcomPC98_FM_Channel(uint8 id, PC98AudioCore *&ac, const Common::Array<const uint8*>&instruments) : _regOffset(id), _ac(ac), _instruments(instruments),
+	_isPlaying(false), _note(0), _carrier(0), _volume(0), _velocity(0), _noteEffCur(0), _program(0), _noteEffPrev(0), _breathControl(0), _frequency(0), _modWheel(0), _pitchBendSensitivity(0),
+	_pitchBendPara(0), _pitchBendEff(0), _vbrState(0), _vbrStep(0), _vbrCycleTicker(0), _vbrDelayTicker(0), _vbrHandler(nullptr), _prtEnable(false),
+	_prtTime(0), _prtState(0), _prtStep(0), _prtCycleLength(0) {
+	typedef void (CapcomPC98_FM_Channel::*Proc)();
+	static const Proc procs[] = {
+		&CapcomPC98_FM_Channel::vbrHandler0,
+		&CapcomPC98_FM_Channel::vbrHandler1,
+		&CapcomPC98_FM_Channel::vbrHandler2,
+		&CapcomPC98_FM_Channel::vbrHandler3,
+		&CapcomPC98_FM_Channel::dummyProc
+	};
+
+	assert(ARRAYSIZE(_vbrHandlers) == ARRAYSIZE(procs));
+	for (int i = 0; i < ARRAYSIZE(_vbrHandlers); ++i) {
+		_vbrHandlers[i] = new VbrHandlerProc(this, procs[i]);
+		assert(_vbrHandlers[i]);
+	}
+
+	memset(&_instrument, 0, sizeof(_instrument));
+	_instrument.regData = new uint8[52];
+	memset(_instrument.regData, 0, 52);
+
+	reset();
+}
+
+CapcomPC98_FM_Channel::~CapcomPC98_FM_Channel() {
+	for (int i = 0; i < ARRAYSIZE(_vbrHandlers); ++i)
+		delete _vbrHandlers[i];
+
+	delete[] _instrument.regData;
+}
+
+void CapcomPC98_FM_Channel::reset() {
+	_vbrHandler = _vbrHandlers[4];
+	_frequency = 0xffff;
+	_vbrState = 0;
+	_prtState = 0;
+	_prtCycleLength = 0;
+	_prtEnable = false;
+	_pitchBendSensitivity = 3072;
+	_pitchBendEff = 0;
+	_pitchBendPara = 0;
+	_isPlaying = false;
+	_note = 0xff;
+	_volume = 0x7f;
+	_velocity = _breathControl = 0x40;
+	_noteEffCur = 60;
+	_modWheel = 0;
+}
+
+void CapcomPC98_FM_Channel::keyOff() {
+	_ac->writeReg(0, 0x28, _regOffset);
+}
+
+void CapcomPC98_FM_Channel::noteOff(uint8 note) {
+	if (!_isPlaying || _note != note)
+		return;
+	keyOff();
+	_isPlaying = false;
+}
+
+void CapcomPC98_FM_Channel::noteOn(uint8 note, uint8 velo) {
+	_noteEffPrev = _noteEffCur;
+	_note = note;
+	_velocity = velo;
+
+	if (note > 11)
+		note -= 12;
+	if (note > 127)
+		note += 12;
+
+	_noteEffCur = note;
+
+	if (!_isPlaying && _instrument.resetEffect)
+		setupVibrato();
+
+	setupPortamento();
+	updateFrequency();
+
+	if (!_isPlaying) {
+		updateVolume();
+		_ac->writeReg(0, 0x28, _regOffset | 0xf0);
+	}
+
+	_isPlaying = true;	
+}
+
+void CapcomPC98_FM_Channel::programChange(uint8 prg) {
+	if (prg >= _instruments.size())
+		return;
+
+	_program = prg;
+
+	loadInstrument(_instruments[prg]);
+
+	_ac->writeReg(0, 0xb0 + _regOffset, _instrument.fbl_alg);
 
+	static const uint8 carriers[] = { 0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F };
+	_carrier = carriers[_instrument.fbl_alg & 7];
+
+	const uint8 *s = _instrument.regData;
+	for (int i = _regOffset; i < 16 + _regOffset; i += 4) {
+		_ac->writeReg(0, 0x30 + i, s[0]);
+		_ac->writeReg(0, 0x50 + i, s[2]);
+		_ac->writeReg(0, 0x60 + i, s[3]);
+		_ac->writeReg(0, 0x70 + i, s[4]);
+		_ac->writeReg(0, 0x80 + i, s[5]);
+		_ac->writeReg(0, 0x90 + i, s[6]);
+		s += 13;
+	}
+
+	setupVibrato();
+}
+
+void CapcomPC98_FM_Channel::pitchBend(uint16 pb) {
+	_pitchBendPara = (pb - 0x2000) << 2;
+	updatePitchBend();
+}
+
+void CapcomPC98_FM_Channel::restore() {
+	programChange(_program);
+}
+
+void CapcomPC98_FM_Channel::modWheel(uint8 mw) {
+	_modWheel = mw;
+}
+
+void CapcomPC98_FM_Channel::breathControl(uint8 bc) {
+	_breathControl = bc;
+	updateVolume();
+}
+
+void CapcomPC98_FM_Channel::pitchBendSensitivity(uint8 pbs) {
+	_pitchBendSensitivity = pbs;
+	pitchBend(_pitchBendPara);
+}
+
+void CapcomPC98_FM_Channel::portamentoTime(uint8 pt) {
+	_prtTime = pt;
+}
+
+void CapcomPC98_FM_Channel::volume(uint8 vol) {
+	_volume = vol;
+	updateVolume();
+}
+
+void CapcomPC98_FM_Channel::togglePortamento(uint8 enable) {
+	_prtEnable = (enable >= 0x40);
+}
+
+void CapcomPC98_FM_Channel::allNotesOff() {
+	_isPlaying = false;
+	for (int i = _regOffset; i < 16 + _regOffset; i += 4) {
+		_ac->writeReg(0, 0x40 + i, 0x7f);
+		_ac->writeReg(0, 0x80 + i, 0xff);
+	}
+	_ac->writeReg(0, 0x28, _regOffset);
+}
+
+void CapcomPC98_FM_Channel::processSounds() {
+	if (!_isPlaying)
+		return;
+
+	if (_vbrHandler->isValid())
+		(*_vbrHandler)();
+	updatePortamento();
+	updateFrequency();
+}
+
+void CapcomPC98_FM_Channel::loadInstrument(const uint8 *in) {
+	memcpy(_instrument.name, in, 8);
+	in += 8;
+	_instrument.fbl_alg = *in++;
+	_instrument.vbrType = *in++;
+	_instrument.vbrCycleLength = *in++;
+	_instrument.vbrSensitivity = *in++;
+	_instrument.resetEffect = *in++;
+	_instrument.vbrDelay = *in++;
+	/*_instrument.ff = **/in++;
+	/*_instrument.f10 = **/in++;
+	/*_instrument.f11 = **/in++;
+	/*_instrument.f12 = **/in++;
+	/*_instrument.f13 = **/in++;
+	/*_instrument.f14 = **/in++;
+	assert(_instrument.regData);
+	memcpy(_instrument.regData, in, 52);
+}
+
+void CapcomPC98_FM_Channel::updatePitchBend() {
+	_pitchBendEff = (_pitchBendPara * (_pitchBendSensitivity << 1)) >> 16;
+	updateFrequency();
+}
+
+void CapcomPC98_FM_Channel::updateVolume() {
+	uint8 cr = _carrier;
+
+	const uint8 *s = _instrument.regData;
+	for (int i = _regOffset; i < 16 + _regOffset; i += 4) {
+		uint16 vol = 0;
+		if (cr & 1) {
+			vol += _volTableOut[_volume];
+			//vol += _fadeState;
+		}
+
+		uint8 a = _breathControl;
+		uint8 b = s[10];
+		if (b & 0x80) {
+			a = ~a & 0x7f;
+			b &= 0x7f;
+		}
+		vol += (((_volTablePara[a] * b) & 0x7fff) >> 7);
+
+		a = _velocity;
+		b = s[7];
+		if (b & 0x80) {
+			a = ~a & 0x7f;
+			b &= 0x7f;
+		}
+		vol += (((_volTablePara[a] * b) & 0x7fff) >> 7);
+
+		a = _volTablesInst[s[8] & 3][_noteEffCur];
+		b = s[9];
+		if (b & 0x80) {
+			a = ~a & 0x7f;
+			b &= 0x7f;
+		}
+		vol += (((a * b) & 0x7fff) >> 7);
+		vol += s[1];
+		vol = MIN<uint16>(vol, 0x7f);
+
+		_ac->writeReg(0, 0x40 + i, vol & 0xff);
+		s += 13;
+		cr >>= 1;
+	}
+}
+
+void CapcomPC98_FM_Channel::updatePortamento() {
+	if (_prtCycleLength) {
+		_prtCycleLength--;
+		_prtState += _prtStep;
+	} else {
+		_prtState = 0;
+	}
+}
+
+void CapcomPC98_FM_Channel::updateFrequency() {
+	int16 tone = (MIN<int16>(_modWheel + _instrument.vbrSensitivity, 255) * (_vbrState >> 16)) >> 8;
+	tone = CLIP<int16>(tone + (_noteEffCur << 8), 0, 0x5fff);
+	tone = CLIP<int16>(tone + _pitchBendEff, 0, 0x5fff);
+	tone = CLIP<int16>(tone + (_prtState >> 16), 0, 0x5fff);
+
+	uint8 block = ((tone >> 8) / 12) & 7;
+	uint8 msb = (tone >> 8) % 12;
+	uint8 lsb = (tone & 0xff) >> 2;
+
+	uint16 freq = (block << 11) + _freqMSBTable[msb] + _freqLSBTables[msb][lsb];
+	if (_frequency == freq)
+		return;
+
+	_frequency = freq;
+	_ac->writeReg(0, 0xa4 + _regOffset, freq >> 8);
+	_ac->writeReg(0, 0xa0 + _regOffset, freq & 0xff);
+}
+
+void CapcomPC98_FM_Channel::setupPortamento() {
+	if (!_prtTime || !_prtEnable) {
+		_prtCycleLength = 0;
+		_prtState = 0;
+		return;
+	}
+
+	int16 diff = (_noteEffCur << 8) - CLIP<int16>((_prtState >> 16) | (_noteEffPrev << 8), 0, 0x5fff);
+	_prtCycleLength = _prtTime;
+	_prtStep = (diff << 16) / _prtTime;
+	_prtState = (-diff << 16);
+}
+
+void CapcomPC98_FM_Channel::setupVibrato() {
+	_vbrHandler = _vbrHandlers[4];
+
+	if (_instrument.vbrCycleLength == 0 || _instrument.vbrType > 4)
+		return;
+
+	_vbrDelayTicker = _instrument.vbrDelay;
+
+	switch (_instrument.vbrType) {
+	case 0:
+	case 4:
+		_vbrStep = ((_instrument.vbrType == 4 ? 512 : 3072) << 16) / _instrument.vbrCycleLength;
+		_vbrCycleTicker = 0;
+		_vbrState = 0;
+		_vbrHandler = _vbrHandlers[0];
+		break;
+
+	case 1:
+		_vbrState = _vbrDelayTicker ? 0 : 3072 << 16;
+		_vbrCycleTicker = 0;
+		_vbrHandler = _vbrHandlers[1];
+		break;
+
+	case 2:
+	case 3:
+		_vbrStep = (6144 << 16) / _instrument.vbrCycleLength;
+		_vbrState = _vbrDelayTicker ? 0 : (_instrument.vbrType == 2 ? -3072 : 3072) << 16;
+		_vbrCycleTicker = _instrument.vbrCycleLength - 1;
+		_vbrHandler = _vbrHandlers[_instrument.vbrType];
+		
+		break;
+
+	default:
+		break;
+	}
+}
+
+void CapcomPC98_FM_Channel::vbrHandler0() {
+	if (_vbrDelayTicker) {
+		_vbrDelayTicker--;
+		return;
+	}
+
+	if ((_vbrCycleTicker < _instrument.vbrCycleLength) || (_vbrCycleTicker >= _instrument.vbrCycleLength * 3))
+		_vbrState += _vbrStep;
+	else
+		_vbrState -= _vbrStep;
+
+	if (++_vbrCycleTicker >= _instrument.vbrCycleLength * 4) {
+		_vbrCycleTicker = 0;
+		_vbrState = 0;
+	}
+}
+
+void CapcomPC98_FM_Channel::vbrHandler1() {
+	if (_vbrDelayTicker) {
+		_vbrDelayTicker--;
+		return;
+	}
+
+	_vbrState = ((_vbrCycleTicker >= _instrument.vbrCycleLength) ? -3072 : 3072) << 16;
+
+	if (++_vbrCycleTicker >= (_instrument.vbrCycleLength << 1))
+		_vbrCycleTicker = 0;
+}
+
+void CapcomPC98_FM_Channel::vbrHandler2() {
+	if (_vbrDelayTicker) {
+		_vbrDelayTicker--;
+		return;
+	}
+
+	_vbrState += _vbrStep;
+
+	if (++_vbrCycleTicker >= _instrument.vbrCycleLength) {
+		_vbrCycleTicker = 0;
+		_vbrState = -(3072 << 16);
+	}
+}
+
+void CapcomPC98_FM_Channel::vbrHandler3() {
+	if (_vbrDelayTicker) {
+		_vbrDelayTicker--;
+		return;
+	}
+
+	_vbrState -= _vbrStep;
+
+	if (++_vbrCycleTicker >= _instrument.vbrCycleLength) {
+		_vbrCycleTicker = 0;
+		_vbrState = 3072 << 16;
+	}
+}
+
+const uint16 CapcomPC98_FM_Channel::_freqMSBTable[] = {
+	0x026a, 0x028f, 0x02b6, 0x02df, 0x030b, 0x0339, 0x036a, 0x039e, 0x03d5, 0x0410, 0x044e, 0x048f
+};
+
+const uint8 CapcomPC98_FM_Channel::_freqLSBTables[12][64] = {
+	{
+		0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09,
+		0x09, 0x0a, 0x0a, 0x0b, 0x0c, 0x0c, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, 0x0f, 0x10, 0x11, 0x11, 0x12,
+		0x12, 0x13, 0x14, 0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1a, 0x1a, 0x1b,
+		0x1c, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x1f, 0x20, 0x20, 0x21, 0x21, 0x22, 0x23, 0x23, 0x24, 0x24
+	},
+	{
+		0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x07, 0x07, 0x08, 0x08, 0x09,
+		0x0a, 0x0a, 0x0b, 0x0b, 0x0c, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x13,
+		0x13, 0x14, 0x14, 0x15, 0x16, 0x16, 0x17, 0x17, 0x18, 0x19, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1c,
+		0x1d, 0x1e, 0x1e, 0x1f, 0x1f, 0x20, 0x21, 0x21, 0x22, 0x23, 0x23, 0x24, 0x24, 0x25, 0x26, 0x26
+	},
+	{
+		0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x09,
+		0x0a, 0x0b, 0x0b, 0x0c, 0x0d, 0x0d, 0x0e, 0x0f, 0x0f, 0x10, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14,
+		0x14, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e,
+		0x1f, 0x1f, 0x20, 0x21, 0x21, 0x22, 0x23, 0x23, 0x24, 0x25, 0x25, 0x26, 0x27, 0x27, 0x28, 0x29
+	},
+	{
+		0x00, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0a,
+		0x0b, 0x0c, 0x0c, 0x0d, 0x0e, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14, 0x14, 0x15,
+		0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x1f, 0x20,
+		0x21, 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2b
+	},
+	{
+		0x00, 0x01, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b,
+		0x0b, 0x0c, 0x0d, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14, 0x15, 0x15, 0x16,
+		0x17, 0x17, 0x18, 0x19, 0x1a, 0x1a, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x1f, 0x20, 0x21, 0x22,
+		0x22, 0x23, 0x24, 0x25, 0x25, 0x26, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c, 0x2d, 0x2e
+	},
+	{
+		0x00, 0x01, 0x02, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+		0x0c, 0x0d, 0x0e, 0x0f, 0x0f, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14, 0x15, 0x15, 0x16, 0x17, 0x18,
+		0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1c, 0x1d, 0x1e, 0x1f, 0x1f, 0x20, 0x21, 0x22, 0x22, 0x23, 0x24,
+		0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2d, 0x2e, 0x2f, 0x30, 0x31
+	},
+	{
+		0x00, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0b, 0x0c,
+		0x0d, 0x0e, 0x0f, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x19,
+		0x1a, 0x1b, 0x1c, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x25, 0x26,
+		0x27, 0x28, 0x29, 0x2a, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x33
+	},
+	{
+		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0a, 0x0b, 0x0c, 0x0d,
+		0x0e, 0x0f, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+		0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x28,
+		0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37
+	},
+	{
+		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+		0x0f, 0x10, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1a, 0x1b, 0x1c,
+		0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+		0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a
+	},
+	{
+		0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+		0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+		0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
+		0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d
+	},
+	{
+		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+		0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+		0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+		0x30, 0x31, 0x32, 0x33, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
+	},
+	{
+		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
+		0x22, 0x23, 0x24, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x32, 0x33,
+		0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44
+	}
+};
+
+const uint8 CapcomPC98_FM_Channel::_volTablesInst[4][128] = {
+	{
+		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+		0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+		0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+		0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+		0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+		0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+		0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+		0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
+	},
+	{
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x05, 0x0a, 0x0f, 0x14, 0x19, 0x1e, 0x23, 0x28, 0x2d, 0x32, 0x37, 0x3c,
+		0x41, 0x46, 0x4b, 0x50, 0x55, 0x5a, 0x5f, 0x64, 0x69, 0x6e, 0x73, 0x78, 0x7f, 0x7f, 0x7f, 0x7f,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
+	},
+	{
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
+	},
+	{
+		0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
+		0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
+		0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
+		0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
+		0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
+		0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
+		0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f,
+		0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f, 0x00, 0x7f
+	}
+};
+
+const uint8 CapcomPC98_FM_Channel::_volTableOut[] = {
+	0x2a, 0x2a, 0x29, 0x29, 0x29, 0x28, 0x28, 0x28, 0x27, 0x27, 0x27, 0x26, 0x26, 0x26, 0x25, 0x25,
+	0x25, 0x24, 0x24, 0x24, 0x23, 0x23, 0x23, 0x22, 0x22, 0x22, 0x21, 0x21, 0x21, 0x20, 0x20, 0x20,
+	0x1f, 0x1f, 0x1f, 0x1e, 0x1e, 0x1e, 0x1d, 0x1d, 0x1d, 0x1c, 0x1c, 0x1c, 0x1b, 0x1b, 0x1b, 0x1a,
+	0x1a, 0x1a, 0x19, 0x19, 0x19, 0x18, 0x18, 0x18, 0x17, 0x17, 0x17, 0x16, 0x16, 0x16, 0x15, 0x15,
+	0x15, 0x14, 0x14, 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, 0x10, 0x10,
+	0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0a,
+	0x0a, 0x0a, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, 0x05, 0x05,
+	0x05, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00
+};
+
+const uint8 CapcomPC98_FM_Channel::_volTablePara[] = {
+	0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70,
+	0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60,
+	0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50,
+	0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40,
+	0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30,
+	0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20,
+	0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
+	0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00
+};
+
+CapcomPC98_FM::CapcomPC98_FM(Audio::Mixer *mixer, CBProc &proc, bool playerPrio, uint16 playerFlag, uint8 reservedChanFlags, bool needsTimer) : CapcomPC98Player(playerPrio, playerFlag, reservedChanFlags), PC98AudioPluginDriver(), _cbProc(proc), _ac(nullptr), _chan(nullptr), _ready(false) {
+	_ac = new PC98AudioCore(mixer, needsTimer ? this : nullptr, kType26);
+	assert(_ac);
+	_chan = new CapcomPC98_FM_Channel*[3];
+	assert(_chan);
+	for (int i = 0; i < 3; ++i)
+		_chan[i] = new CapcomPC98_FM_Channel(i, _ac, _instruments);
+}
+
+CapcomPC98_FM::~CapcomPC98_FM() {
+	delete _ac;
+	if (_chan) {
+		for (int i = 0; i < 3; ++i)
+			delete _chan[i];
+		delete[] _chan;
+	}
+}
+
+bool CapcomPC98_FM::init() {
+	if (!(_chan && _ac && _ac->init()))
+		return false;
+
+	_ac->writeReg(0, 7, 0xBF);
+	for (int i = 0; i < 14; ++i) {
+		if (i != 7)
+			_ac->writeReg(0, i, 0);
+	}
+
+	static const uint8 iniData[] = { 0x00, 0x7F, 0x1F, 0x1F, 0x1F, 0xFF, 0x00 };
+	for (int i = 0; i < 7; ++i) {
+		for (int ii = 0; ii < 16; ++ii) {
+			if ((ii & 3) != 3)
+				_ac->writeReg(0, 0x30 + (i << 4) + ii, iniData[i]);
+		}
+	}
+
+	for (int i = 0; i < 3; ++i)
+		_ac->writeReg(0, 0xB0 + i, 0xC0);
+
+	_ac->writeReg(0, 0x26, 230);
+	_ac->writeReg(0, 0x27, 0x2a);
+
+	loadInstruments(_initData, 1);
+
+	reset();
+
+	_ready = true;
+
+	return true;
+}
+
+void CapcomPC98_FM::deinit() {
+	PC98AudioCore::MutexLock lock = _ac->stackLockMutex();
+	_ready = false;
+}
+
+void CapcomPC98_FM::reset() {
+	for (int i = 0; i < 3; ++i)
+		_chan[i]->reset();
+
+	for (int i = 0; i < 3; ++i)
+		_chan[i]->keyOff();
+
+	for (int i = 0; i < 3; ++i)
+		_chan[i]->programChange(0);
+}
+
+void CapcomPC98_FM::loadInstruments(const uint8 *data, uint16 number) {
+	_instruments.clear();
+	while (number--) {
+		_instruments.push_back(data);
+		data += 72;
+	}
+}
+
+void CapcomPC98_FM::setMasterVolume (int vol) {
+
+}
+
+PC98AudioCore::MutexLock CapcomPC98_FM::lockMutex() {
+	if (!_ready)
+		error("CapcomPC98_FM::lockMutex(): Invalid call");
+	return _ac->stackLockMutex();
+}
+
+void CapcomPC98_FM::send(uint32 evt) {
+	uint8 ch = evt & 0x0f;
+	uint8 p1 = (evt >> 8) & 0xff;
+	uint8 p2 = (evt >> 16) & 0xff;
+
+	if (ch > 2)
+		return;
+
+	switch (evt & 0xf0) {
+	case 0x80:
+		_chan[ch]->noteOff(p1);
+		break;
+	case 0x90:
+		if (p2)
+			_chan[ch]->noteOn(p1, p2);
+		else
+			_chan[ch]->noteOff(p1);
+		break;
+	case 0xb0:
+		controlChange(ch, p1, p2);
+		break;
+	case 0xc0:
+		_chan[ch]->programChange(p1);
+		break;
+	case 0xe0:
+		_chan[ch]->pitchBend(((p2 & 0x7f) << 7) | (p1 & 0x7f));
+		break;
+	default:
+		break;
+	}
+}
+
+
+void CapcomPC98_FM::timerCallbackB() {
+	if (_ready && _cbProc.isValid()) {
+		PC98AudioCore::MutexLock lock = _ac->stackLockMutex();
+		_cbProc();
+	}
+}
+
+void CapcomPC98_FM::processSounds() {
+	for (int i = 0; i < 3; ++i)
+		_chan[i]->processSounds();
+}
+
+void CapcomPC98_FM::controlChange(uint8 ch, uint8 control, uint8 val) {
+	if (ch > 2)
+		return;
+
+	switch (control) {
+	case 1:
+		_chan[ch]->modWheel(val);
+		break;
+	case 2:
+		_chan[ch]->breathControl(val);
+		break;
+	case 3:
+		_soundMarkers[ch] = val;
+		break;
+	case 4:
+		_chan[ch]->pitchBendSensitivity(val);
+		break;
+	case 5:
+		_chan[ch]->portamentoTime(val);
+		break;
+	case 7:
+		_chan[ch]->volume(val);
+		break;
+	case 65:
+		_chan[ch]->togglePortamento(val);
+		break;
+	case 123:
+		_chan[ch]->allNotesOff();
+		break;
+	default:
+		break;
+	}
+}
+
+void CapcomPC98_FM::restoreStateIntern() {
+	for (int i = 0; i < 3; ++i) {
+		if ((1 << i) & _reservedChanFlags)
+			continue;
+		_chan[i]->restore();
+	}
+}
+
+const uint8 CapcomPC98_FM::_initData[72] = {
+	0x49, 0x4e, 0x49, 0x54, 0x5f, 0x56, 0x4f, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	0x00, 0x00,
+	0x00, 0x00, 0x01, 0x7f, 0x1f, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x1f,
+	0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x1f, 0x00, 0x00, 0x0f, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1f, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+CapcomPC98AudioDriverInternal *CapcomPC98AudioDriverInternal::_refInstance = 0;
+int CapcomPC98AudioDriverInternal::_refCount = 0;
+
+CapcomPC98AudioDriverInternal::CapcomPC98AudioDriverInternal(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev) : _ready(false), _fmDevice(nullptr), _timerProc(nullptr), _mutexProc(nullptr), _marker(0), _musicVolume(0), _sfxVolume(0) {
+	MusicType type = MidiDriver::getMusicType(dev);
+	_timerProc = new CapcomPC98_FM::CBProc(this, &CapcomPC98AudioDriverInternal::timerCallback);
+	assert(_timerProc);
+	_mutexProc = new CapcomPC98_MIDI::MutexProc(this, &CapcomPC98AudioDriverInternal::lockMutex);
+	assert(_mutexProc);
+
+	if (type == MT_MT32 || type == MT_GM) {
+		_players[0] = new CapcomPC98_MIDI(dev, type == MT_MT32, *_mutexProc);
+		_players[1] = _fmDevice = new CapcomPC98_FM(mixer, *_timerProc, true, CapcomPC98Player::kPrioPlay, 7, true);
+		_marker = 1;
+	} else {
+		_players[0] = new CapcomPC98_FM(mixer, *_timerProc, false, CapcomPC98Player::kStdPlay, 3, false);
+		_players[1] = _fmDevice = new CapcomPC98_FM(mixer, *_timerProc, true, CapcomPC98Player::kPrioPlay | CapcomPC98Player::kPrioClaim, 4, true);
+	}
+
+	bool ready = true;
+	for (int i = 0; i < 2; ++i) {
+		if (!(_players[i] && _players[i]->init()))
+			ready = false;
+	}
+
+	_ready = ready;
 }
 
 CapcomPC98AudioDriverInternal::~CapcomPC98AudioDriverInternal() {
+	_ready = false;
+
+	for (int i = 0; i < 2; ++i)
+		_players[i]->deinit();
+
+	for (int i = 0; i < 2; ++i)
+		delete _players[i];
 
+	delete _timerProc;
+	delete _mutexProc;
 }
 
-CapcomPC98AudioDriver::CapcomPC98AudioDriver() {
+CapcomPC98AudioDriverInternal *CapcomPC98AudioDriverInternal::open(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev) {
+	_refCount++;
 
+	if (_refCount == 1 && _refInstance == 0)
+		_refInstance = new CapcomPC98AudioDriverInternal(mixer, dev);
+	else if (_refCount < 2 || _refInstance == 0)
+		error("CapcomPC98AudioDriverInternal::open(): Internal instance management failure");
+
+	return _refInstance;
+}
+
+void CapcomPC98AudioDriverInternal::close() {
+	if (!_refCount)
+		return;
+
+	_refCount--;
+
+	if (!_refCount) {
+		delete _refInstance;
+		_refInstance = 0;
+	}
+}
+
+void CapcomPC98AudioDriverInternal::reset() {
+	for (int i = 0; i < 2; ++i)
+		_players[i]->reset();
+}
+
+void CapcomPC98AudioDriverInternal::loadFMInstruments(const uint8 *data) {
+	for (int i = 0; i < 2; ++i)
+		_players[i]->loadInstruments(data + 2, READ_LE_UINT16(data));
+}
+
+void CapcomPC98AudioDriverInternal::startSong(const uint8 *data, uint8 volume, bool loop) {
+	stopSong();
+	if (_ready)
+		_players[0]->startSound(data, volume, loop);
+}
+
+void CapcomPC98AudioDriverInternal::stopSong() {
+	if (_ready)
+		_players[0]->stopSound();
+}
+
+void CapcomPC98AudioDriverInternal::startSoundEffect(const uint8 *data, uint8 volume) {
+	stopSoundEffect();
+	if (_ready)
+		_players[1]->startSound(data, volume, false);
+}
+
+void CapcomPC98AudioDriverInternal::stopSoundEffect() {
+	if (_ready)
+		_players[1]->stopSound();
+}
+
+int CapcomPC98AudioDriverInternal::checkSoundMarker() const {
+	return _players[0]->getMarker(_marker);
+}
+
+bool CapcomPC98AudioDriverInternal::songIsPlaying() const {
+	return CapcomPC98Player::getPlayerStatus() & CapcomPC98Player::kStdPlay;
+}
+
+bool CapcomPC98AudioDriverInternal::soundEffectIsPlaying() const {
+	return CapcomPC98Player::getPlayerStatus() & CapcomPC98Player::kPrioPlay;
+}
+
+void CapcomPC98AudioDriverInternal::setMusicVolume(int volume) {
+	_musicVolume = volume;
+	updateMasterVolume();
+}
+
+void CapcomPC98AudioDriverInternal::setSoundEffectVolume(int volume) {
+	_sfxVolume = volume;
+	updateMasterVolume();
+}
+
+void CapcomPC98AudioDriverInternal::timerCallback() {
+	for (int i = 0; i < 2; ++i)
+		_players[i]->nextTick();
+}
+
+PC98AudioCore::MutexLock CapcomPC98AudioDriverInternal::lockMutex() {
+	if (!_ready)
+		error("CapcomPC98AudioDriverInternal::lockMutex(): Invalid call");
+
+	return _fmDevice->lockMutex();
+}
+
+void CapcomPC98AudioDriverInternal::updateMasterVolume() {
+	if (!_ready)
+		return;
+	_players[0]->setMasterVolume(_musicVolume);
+	_players[1]->setMasterVolume(_sfxVolume);
+}
+
+CapcomPC98AudioDriver::CapcomPC98AudioDriver(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev) {
+	_drv = CapcomPC98AudioDriverInternal::open(mixer, dev);
 }
 
 CapcomPC98AudioDriver::~CapcomPC98AudioDriver() {
+	CapcomPC98AudioDriverInternal::close();
+	_drv = 0;
+}
+
+bool CapcomPC98AudioDriver::isUsable() const {
+	return (_drv && _drv->isUsable());
+}
+
+void CapcomPC98AudioDriver::reset() {
+	if (_drv)
+		_drv->reset();
+}
+
+void CapcomPC98AudioDriver::loadFMInstruments(const uint8 *data) {
+	if (_drv)
+		_drv->loadFMInstruments(data);
+}
+
+void CapcomPC98AudioDriver::startSong(const uint8 *data, uint8 volume, bool loop) {
+	if (_drv)
+		_drv->startSong(data, volume, loop);
+}
+
+void CapcomPC98AudioDriver::stopSong() {
+	if (_drv)
+		_drv->stopSong();
+}
+
+void CapcomPC98AudioDriver::startSoundEffect(const uint8 *data, uint8 volume) {
+	if (_drv)
+		_drv->startSoundEffect(data, volume);
+}
+
+void CapcomPC98AudioDriver::stopSoundEffect() {
+	if (_drv)
+		_drv->stopSoundEffect();
+}
+
+int CapcomPC98AudioDriver::checkSoundMarker() const {
+	return _drv ? _drv->checkSoundMarker() : 99;
+}
+
+bool CapcomPC98AudioDriver::songIsPlaying() const {
+	return _drv ? _drv->songIsPlaying() : false;
+}
+
+bool CapcomPC98AudioDriver::soundEffectIsPlaying() const {
+	return _drv ? _drv->soundEffectIsPlaying() : false;
+}
+
+void CapcomPC98AudioDriver::setMusicVolume(int volume) {
+	if (_drv)
+		_drv->setMusicVolume(volume);
+}
 
+void CapcomPC98AudioDriver::setSoundEffectVolume(int volume) {
+	if (_drv)
+		_drv->setSoundEffectVolume(volume);
 }
 
 } // End of namespace Kyra
diff --git a/engines/kyra/sound/drivers/capcom98.h b/engines/kyra/sound/drivers/capcom98.h
index 9da302435f7..869f1ac0da3 100644
--- a/engines/kyra/sound/drivers/capcom98.h
+++ b/engines/kyra/sound/drivers/capcom98.h
@@ -24,8 +24,8 @@
 #ifndef KYRA_SOUND_CAPCOM98_H
 #define KYRA_SOUND_CAPCOM98_H
 
+#include "audio/mididrv.h"
 #include "common/scummsys.h"
-//#include "common/array.h"
 
 namespace Audio {
 	class Mixer;
@@ -37,9 +37,25 @@ class CapcomPC98AudioDriverInternal;
 
 class CapcomPC98AudioDriver {
 public:
-	CapcomPC98AudioDriver();
+	CapcomPC98AudioDriver(Audio::Mixer *mixer, MidiDriver::DeviceHandle dev);
 	~CapcomPC98AudioDriver();
 
+	bool isUsable() const;
+
+	// All data passed to the following functions has to be maintained by the caller.
+	void reset();
+	void loadFMInstruments(const uint8 *data);
+	void startSong(const uint8 *data, uint8 volume, bool loop);
+	void stopSong();
+	void startSoundEffect(const uint8 *data, uint8 volume);
+	void stopSoundEffect();
+	int checkSoundMarker() const;
+	bool songIsPlaying() const;
+	bool soundEffectIsPlaying() const;
+
+	void setMusicVolume(int volume);
+	void setSoundEffectVolume(int volume);
+
 private:
 	CapcomPC98AudioDriverInternal *_drv;
 };
diff --git a/engines/kyra/sound/sound_intern.h b/engines/kyra/sound/sound_intern.h
index 6be2c35842d..aa0dc70c679 100644
--- a/engines/kyra/sound/sound_intern.h
+++ b/engines/kyra/sound/sound_intern.h
@@ -540,13 +540,14 @@ public:
 	~SoundPC98_Darkmoon() override;
 
 	kType getMusicType() const override;
+	kType getSfxType() const override;
 
 	bool init() override;
 
 	void initAudioResourceInfo(int set, void *info) override;
 	void selectAudioResourceSet(int set) override;
 	bool hasSoundFile(uint file) const override { return true; }
-	void loadSoundFile(uint file) override {}
+	void loadSoundFile(uint file) override;
 	void loadSoundFile(Common::String name) override;
 
 	void playTrack(uint8 track) override;
@@ -565,11 +566,22 @@ public:
 	void resetTrigger() override;
 
 private:
+	void restartBackgroundMusic();
+	const uint8 *getData(uint16 track) const;
+
 	KyraEngine_v1 *_vm;
 	CapcomPC98AudioDriver *_driver;
+	uint8 *_soundData, *_fileBuffer;
+
+	int _lastTrack;
 
+	const SoundResourceInfo_PC *res() const {return _resInfo[_currentResourceSet]; }
+	SoundResourceInfo_PC *_resInfo[3];
 	int _currentResourceSet;
 
+	Common::String _soundFileLoaded;
+
+	MidiDriver::DeviceHandle _dev;
 	kType _drvType;
 	bool _ready;
 };
diff --git a/engines/kyra/sound/sound_pc98_darkmoon.cpp b/engines/kyra/sound/sound_pc98_darkmoon.cpp
index b7b6380d17a..0b56e627d9f 100644
--- a/engines/kyra/sound/sound_pc98_darkmoon.cpp
+++ b/engines/kyra/sound/sound_pc98_darkmoon.cpp
@@ -30,105 +30,202 @@
 namespace Kyra {
 
 SoundPC98_Darkmoon::SoundPC98_Darkmoon(KyraEngine_v1 *vm, MidiDriver::DeviceHandle dev, Audio::Mixer *mixer) : Sound(vm, mixer),
-	_vm(vm), _driver(0), _currentResourceSet(-1), _ready(false), _drvType(kPC98) {
+	_vm(vm), _driver(nullptr), _soundData(nullptr), _currentResourceSet(-1), _ready(false), _dev(dev), _drvType(kPC98), _lastTrack(-1) {
+
+	memset(&_resInfo, 0, sizeof(_resInfo));
+	_soundData = new uint8[20600];
+	memset(_soundData, 0, 20600);
+	_fileBuffer = new uint8[10500];
+	memset(_fileBuffer, 0, 10500);
+
 	MusicType type = MidiDriver::getMusicType(dev);
-	if (type == MT_MT32) {
+	if (type == MT_MT32)
 		_drvType = kMidiMT32;
-	} else if (type == MT_GM) {
+	else if (type == MT_GM)
 		_drvType = kMidiGM;
-	} else {
-
-	}
 }
 
 SoundPC98_Darkmoon::~SoundPC98_Darkmoon() {
 	delete _driver;
+	delete[] _soundData;
+	delete[] _fileBuffer;
+	for (int i = 0; i < 3; i++)
+		initAudioResourceInfo(i, nullptr);
 }
 
 Sound::kType SoundPC98_Darkmoon::getMusicType() const {
 	return _drvType;
 }
 
+Sound::kType SoundPC98_Darkmoon::getSfxType() const {
+	return kPC98;
+}
+
 bool SoundPC98_Darkmoon::init() {
-	_driver = new CapcomPC98AudioDriver();
-	_ready = true;
-	return true;
+	_driver = new CapcomPC98AudioDriver(_mixer, _dev);
+	_ready = (_soundData && _driver && _driver->isUsable());
+	return _ready;
 }
 
 void SoundPC98_Darkmoon::initAudioResourceInfo(int set, void *info) {
-	//delete _resInfo[set];
-	//_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : 0;
+	if (set < kMusicIntro || set > kMusicFinale)
+		return;
+	delete _resInfo[set];
+	_resInfo[set] = info ? new SoundResourceInfo_PC(*(SoundResourceInfo_PC*)info) : nullptr;
 }
 
 void SoundPC98_Darkmoon::selectAudioResourceSet(int set) {
-	if (set == _currentResourceSet || !_ready)
+	if (set < kMusicIntro || set > kMusicFinale || set == _currentResourceSet || !_ready)
 		return;
+	if (_resInfo[set])
+		_currentResourceSet = set;
+}
 
-	//if (!_resInfo[set])
-	//	return;
+void SoundPC98_Darkmoon::loadSoundFile(uint file) {
+	if (!_ready)
+		return;
 
-	_currentResourceSet = set;
+	if (file < res()->fileListSize)
+		loadSoundFile(res()->fileList[file]);
 }
 
 void SoundPC98_Darkmoon::loadSoundFile(Common::String name) {
 	if (!_ready)
 		return;
 
-	//if (file >= _resInfo[_currentResourceSet]->fileListSize)
-	//	return;
+	haltTrack();
+	stopAllSoundEffects();
 
-	//Common::SeekableReadStream *s = _vm->resource()->createReadStream(_resInfo[_currentResourceSet]->fileList[file]);
-	//_driver->loadMusicData(s);
-	//delete s;
+	name += (_drvType == kPC98 ? ".SDO" : ".SDM");
+	if (!_ready || _soundFileLoaded == name)
+		return;
+
+	Common::SeekableReadStream *in = _vm->resource()->createReadStream(name.c_str());
+	if (!in)
+		error("SoundPC98_Darkmoon::loadSoundFile(): Failed to load sound file '%s'", name.c_str());
+
+	uint16 sz = in->readUint16LE();
+	uint8 cmp = in->readByte();
+	in->seek(1, SEEK_CUR);
+	uint32 outSize = in->readUint32LE();
+	if ((cmp == 0 && outSize > 10500) || (cmp != 0 && outSize > 20600))
+		error("SoundPC98_Darkmoon::loadSoundFile(): Failed to load sound file '%s'", name.c_str());
+	sz -= in->pos();
+	in->seek(2, SEEK_CUR);
+
+	memset(_fileBuffer, 0, 10500);
+	uint16 readSize = in->read(_fileBuffer, 10500);
+	assert(sz == readSize);
+	delete in;
+
+	memset(_soundData, 0, 20600);
+	if (cmp == 0) {
+		memcpy(_soundData, _fileBuffer, outSize);
+	} else if (cmp == 3) {
+		Screen::decodeFrame3(_fileBuffer, _soundData, outSize, true);
+	} else if (cmp == 4) {
+		Screen::decodeFrame4(_fileBuffer, _soundData, outSize);
+	} else {
+		error("SoundPC98_Darkmoon::loadSoundFile(): Failed to load sound file '%s'", name.c_str());
+	}
+
+	uint16 instrOffs = READ_LE_UINT16(_soundData);
+	if (instrOffs >= 20600)
+		error("SoundPC98_Darkmoon::loadSoundFile(): Failed to load sound file '%s'", name.c_str());
+	
+	_driver->loadFMInstruments(_soundData + instrOffs);
+	_driver->reset();
 }
 
 void SoundPC98_Darkmoon::playTrack(uint8 track) {
-	if (!_musicEnabled || !_ready)
-		return;
+	if (track == 0 || track == 2)
+		_lastTrack = track;
+	playSoundEffect(track, 127);
 }
 
 void SoundPC98_Darkmoon::haltTrack() {
-	if (!_musicEnabled || !_ready)
+	if (!_ready)
 		return;
-	//playTrack(0);
+	_driver->stopSong();
+	_lastTrack = -1;
 }
 
 bool SoundPC98_Darkmoon::isPlaying() const {
-	return false;
+	return _ready && _driver && _driver->songIsPlaying();
 }
 
-void SoundPC98_Darkmoon::playSoundEffect(uint16 track, uint8) {
-	if (!_sfxEnabled || !_ready || track >= 120)
+void SoundPC98_Darkmoon::playSoundEffect(uint16 track, uint8 vol) {
+	if (!_ready)
+		return;
+
+	if (track == 0 || track == 2) {
+		restartBackgroundMusic();
 		return;
-	//_driver->startSoundEffect(track);
+	}
+
+	const uint8 *data = getData(track);
+	if (!data)
+		return;
+
+	if (track < 52 || track > 67) {
+		if (_sfxEnabled)
+			_driver->startSoundEffect(data, vol);
+	} else if (_musicEnabled) {
+		_lastTrack = track;
+		_driver->startSong(data, vol, false);
+	}
 }
 
 void SoundPC98_Darkmoon::stopAllSoundEffects() {
-
+	if (_ready)
+		_driver->stopSoundEffect();
 }
 
 void SoundPC98_Darkmoon::beginFadeOut() {
-
+	haltTrack();
+	stopAllSoundEffects();
 }
 
 int SoundPC98_Darkmoon::checkTrigger() {
-	return 99;
+	return _driver ? _driver->checkSoundMarker() : 99;
 }
 
 void SoundPC98_Darkmoon::resetTrigger() {
 
 }
 
+void SoundPC98_Darkmoon::restartBackgroundMusic() {
+	if (_lastTrack == -1) {
+		haltTrack();
+		stopAllSoundEffects();
+	} else {
+		_lastTrack = -1;
+		const uint8 *data = getData(0);
+		if (!data)
+			return;
+		if (_musicEnabled)
+			_driver->startSong(data, 127, true);
+	}
+}
+
+const uint8 *SoundPC98_Darkmoon::getData(uint16 track) const {
+	if (!_ready || track >= 120)
+		return nullptr;
+
+	uint16 offset = READ_LE_UINT16(&_soundData[(track + 1) << 1]);
+	return (offset < 20600) ? &_soundData[offset] : nullptr;
+}
+
 void SoundPC98_Darkmoon::updateVolumeSettings() {
 	if (!_driver || !_ready)
 		return;
 
-	//bool mute = false;
-	//if (ConfMan.hasKey("mute"))
-	//	mute = ConfMan.getBool("mute");
+	bool mute = false;
+	if (ConfMan.hasKey("mute"))
+		mute = ConfMan.getBool("mute");
 
-	//_driver->setMusicVolume((mute ? 0 : ConfMan.getInt("music_volume")));
-	//_driver->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
+	_driver->setMusicVolume((mute ? 0 : ConfMan.getInt("music_volume")));
+	_driver->setSoundEffectVolume((mute ? 0 : ConfMan.getInt("sfx_volume")));
 }
 
 } // End of namespace Kyra
diff --git a/engines/kyra/sound/sound_pc98_eob.cpp b/engines/kyra/sound/sound_pc98_eob.cpp
index 16583569e5e..1bade38bd66 100644
--- a/engines/kyra/sound/sound_pc98_eob.cpp
+++ b/engines/kyra/sound/sound_pc98_eob.cpp
@@ -97,7 +97,7 @@ void SoundPC98_EoB::playTrack(uint8 track) {
 }
 
 void SoundPC98_EoB::haltTrack() {
-	if (!_musicEnabled || !_ready)
+	if (!_ready)
 		return;
 	playTrack(0);
 }




More information about the Scummvm-git-logs mailing list