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

sev- noreply at scummvm.org
Sun Jun 21 13:20:04 UTC 2026


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

Summary:
62d8322944 AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)
45e363f353 AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)
fa13bd283b AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)
a0a300c1bc AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)
301678656f AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)
d541e51e28 AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)


Commit: 62d83229444c2e0f328b9e34320879a8c9befb54
    https://github.com/scummvm/scummvm/commit/62d83229444c2e0f328b9e34320879a8c9befb54
Author: Paweł Góralski (20125592+n0kturnal at users.noreply.github.com)
Date: 2026-06-21T15:19:56+02:00

Commit Message:
AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)

Added integration with nFM library (https://framagit.org/nokturnal/nfm), which provides driver layer for various OPL devices. Changelist provides Atari support only for now, it might be extended to other platforms as well with some additional work. It requires build system adjustments either by prebuilding library directly from repository or downloading prebuilt package to a place folder set by '--with-nfm-prefix' and with --enable-nfm switch enabling nFM support in build). Software driver integration wasn't tested, might be revisited after Atari audio backend updates. Disabled explicitly nFM on Firebee, nFM is not supported / tested on Firebee/Coldfire yet. Fixed broken auto nFM tests in configure script, set library detection to auto ('--enable-nfm' is not needed if path '--with-nfm-prefix' is valid and contains library). Use angled braces for library includes. Minor formatting in create_project.cpp as in suggested changes by SEV.

Changed paths:
  A audio/nfmopl.cpp
  A audio/nfmopl.h
    audio/adlib.cpp
    audio/fmopl.cpp
    audio/module.mk
    backends/platform/atari/build-firebee.sh
    base/version.cpp
    configure
    devtools/create_project/create_project.cpp


diff --git a/audio/adlib.cpp b/audio/adlib.cpp
index bb746af8fa7..f38c0ed15d7 100644
--- a/audio/adlib.cpp
+++ b/audio/adlib.cpp
@@ -37,7 +37,7 @@ static int g_tick;
 
 // Only include OPL3 when we actually have an AdLib emulator builtin, which
 // supports OPL3.
-#if !defined(DISABLE_DOSBOX_OPL) || !defined(DISABLE_NUKED_OPL)
+#if !defined(DISABLE_DOSBOX_OPL) || !defined(DISABLE_NUKED_OPL) || defined(USE_NFM)
 #define ENABLE_OPL3
 #endif
 
diff --git a/audio/fmopl.cpp b/audio/fmopl.cpp
index 8eba28c3cea..d56083fc409 100644
--- a/audio/fmopl.cpp
+++ b/audio/fmopl.cpp
@@ -22,8 +22,13 @@
 #include "audio/fmopl.h"
 
 #ifdef USE_RETROWAVE
-#include "audio/rwopl3.h"
+	#include "audio/rwopl3.h"
 #endif
+
+#ifdef USE_NFM
+	#include "audio/nfmopl.h"
+#endif
+
 #include "audio/softsynth/opl/dosbox.h"
 #include "audio/softsynth/opl/mame.h"
 #include "audio/softsynth/opl/nuked.h"
@@ -38,22 +43,34 @@ namespace OPL {
 
 #ifdef USE_ALSA
 namespace ALSA {
-	OPL *create(Config::OplType type);
+OPL *create(Config::OplType type);
 } // End of namespace ALSA
 #endif // USE_ALSA
 
 #ifdef ENABLE_OPL2LPT
 namespace OPL2LPT {
-	OPL *create(Config::OplType type);
+OPL *create(Config::OplType type);
 } // End of namespace OPL2LPT
 #endif // ENABLE_OPL2LPT
 
 #ifdef USE_RETROWAVE
 namespace RetroWaveOPL3 {
-	OPL *create(Config::OplType type);
+OPL *create(Config::OplType type);
 } // End of namespace RetroWaveOPL3
 #endif // ENABLE_RETROWAVE_OPL3
 
+#ifdef USE_NFM
+namespace NfmOPL {
+namespace RealChip {
+OPL *create(Config::OplType type, enum NfmOPL::OplDevice dt);
+} // End of namespace RealChip
+
+namespace EmulatedChip {
+OPL *create(Config::OplType type, enum NfmOPL::OplDevice dt);
+} // End of namespace EmulatedChip
+} // End of namespace NfmOPL
+#endif
+
 // Config implementation
 
 enum OplEmulator {
@@ -66,6 +83,17 @@ enum OplEmulator {
 	kOPL2LPT = 6,
 	kOPL3LPT = 7,
 	kRWOPL3 = 8
+#ifdef USE_NFM
+	,kNfmNokturnFM2 = 9,
+	kNfmNokturnFM3 = 10,
+	kNfmRWOpl3Express = 11,
+	kNfmOPL2LPT = 12,
+	kNfmOPL3LPT = 13,
+	kNfmCeOPL2AudioBoard = 14,
+	kNfmCeOPL3Duo = 15,
+	kNfmNatfeatsNull = 16,
+	kNfmNukedOpl3 = 17
+#endif
 };
 
 OPL::OPL() {
@@ -99,6 +127,17 @@ const Config::EmulatorDescription Config::_drivers[] = {
 #endif
 #ifdef USE_RETROWAVE
 	{"rwopl3", _s("RetroWave OPL3"), kRWOPL3, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
+#endif
+#ifdef USE_NFM
+	{"nfm_nokturnfm2", _s("[nFM] NokturnFM2 (OPL2)"), kNfmNokturnFM2, kFlagOpl2 },
+	{"nfm_nokturnfm3", _s("[nFM] NokturnFM3 (OPL3)"), kNfmNokturnFM3, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
+	{"nfm_rwopl3", _s("[nFM] RetroWave USB OPL3 Express (OPL3)"), kNfmRWOpl3Express, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
+	{"nfm_opl2lpt", _s("[nFM] Serdaco OPL2LPT (OPL2)"), kNfmOPL2LPT, kFlagOpl2 },
+	{"nfm_opl3lpt", _s("[nFM] Serdaco OPL3LPT (OPL3)"), kNfmOPL3LPT, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
+	{"nfm_ce_opl2ab", _s("[nFM] Cheerful Electronics OPL2 Audio Board (OPL2)"), kNfmCeOPL2AudioBoard, kFlagOpl2 },
+	{"nfm_ce_opl3duo", _s("[nFM] Cheerful Electronics OPL3 Duo! (2xOPL3)"), kNfmCeOPL3Duo, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
+	{"nfm_natfeats_null", _s("[nFM] NatFeats / NULL"), kNfmNatfeatsNull, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
+	{"nfm_nuked_opl3", _s("[nFM] Nuked-OPL3 softsynth (OPL3)"), kNfmNukedOpl3, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
 #endif
 	{ nullptr, nullptr, 0, 0 }
 };
@@ -234,7 +273,7 @@ OPL *Config::create(DriverId driver, OplType type) {
 		}
 
 		warning("OPL2LPT only supprts OPL2");
-		return 0;
+		return nullptr;
 	case kOPL3LPT:
 		return OPL2LPT::create(type);
 #endif
@@ -244,6 +283,49 @@ OPL *Config::create(DriverId driver, OplType type) {
 		return RetroWaveOPL3::create(type);
 #endif
 
+#ifdef USE_NFM
+	case kNfmNokturnFM2: {
+		if (type == kOpl2) {
+			return NfmOPL::RealChip::create(type, NfmOPL::dtNokturnFM2);
+		}
+		warning("NokturnFM2 supports only OPL2");
+		return nullptr;
+	};
+
+	case kNfmNokturnFM3:
+		return NfmOPL::RealChip::create(type, NfmOPL::dtNokturnFM3);
+
+	case kNfmRWOpl3Express:
+		return NfmOPL::RealChip::create(type, NfmOPL::dtRWOpl3Express);
+
+	case kNfmOPL2LPT: {
+		if (type == kOpl2) {
+			return NfmOPL::RealChip::create(type, NfmOPL::dtOPL2LPT);
+		}
+		warning("OPL2LPT supports only OPL2");
+		return nullptr;
+	};
+
+	case kNfmOPL3LPT:
+		return NfmOPL::RealChip::create(type, NfmOPL::dtOPL3LPT);
+
+	case kNfmCeOPL2AudioBoard: {
+		if (type == kOpl2) {
+			return NfmOPL::RealChip::create(type, NfmOPL::dtOPL2AudioBoard);
+		}
+
+		warning("OPL2 Audio Board supports only OPL2");
+		return nullptr;
+	};
+
+	case kNfmCeOPL3Duo:
+		return NfmOPL::RealChip::create(type, NfmOPL::dtOPL3Duo);
+	case kNfmNatfeatsNull:
+		return NfmOPL::RealChip::create(type, NfmOPL::dtNatfeatsOpl);
+	case kNfmNukedOpl3:
+		return NfmOPL::EmulatedChip::create(type, NfmOPL::dtNukedOpl3);
+#endif
+
 	case kNull:
 		return new NullOPL();
 
@@ -275,9 +357,9 @@ bool OPL::emulateDualOpl2OnOpl3(int r, int v, Config::OplType oplType) {
 	// Prevent writes to the following registers of the second set:
 	// - 01 - Test register. Setting any bit here will disable output.
 	// - 04 - Connection select. This is used to enable 4 operator instruments,
-	//		  which are not used for dual OPL2.
+	//        which are not used for dual OPL2.
 	// - 05 - New. Only allow writes which set bit 0 to 1, which enables OPL3
-	//		  features.
+	//        features.
 	if (r == 0x101 || r == 0x104 || (r == 0x105 && ((v & 1) == 0)))
 		return false;
 
diff --git a/audio/module.mk b/audio/module.mk
index 9841aa55a69..54633bd7bfa 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -133,6 +133,11 @@ MODULE_OBJS += \
 	rwopl3.o
 endif
 
+ifdef USE_NFM
+MODULE_OBJS += \
+	nfmopl.o
+endif
+	
 ifdef USE_VGMTRANS_AUDIO
 MODULE_OBJS += \
 	soundfont/rawfile.o \
diff --git a/audio/nfmopl.cpp b/audio/nfmopl.cpp
new file mode 100644
index 00000000000..8af695f3bee
--- /dev/null
+++ b/audio/nfmopl.cpp
@@ -0,0 +1,694 @@
+/* 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/>.
+ *
+ */
+
+/*
+    OPL interface using nFM library(https://framagit.org/nokturnal/nfm)
+    for NokturnFM2 / 3, OPL-L carts, CE OPL2 Audio board, CE OPL3 Duo!, Serdaco OPL2LPT / OPL3LPT, RetroWave OPL3 Express and more ...
+    (c) 2023-26 Paweł Góralski
+ */
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+#include "common/scummsys.h"
+#include "audio/mixer.h"
+#include "audio/fmopl.h"
+#include "audio/nfmopl.h"
+
+#include "backends/platform/atari/atari-debug.h"
+
+extern "C"
+{
+#include <hwinfo.h>
+#include <sc_gmemalloc.h>
+#include <nfmoplshadowregs.h>
+}
+
+#ifndef RELEASE_BUILD
+	#define NFM_ENABLE_LOGS 1
+#endif
+
+#define NFM_ENABLE_BUFFERED_OUTPUT false
+#define NFM_ENABLE_CUSTOM_ALLOC 1
+
+// TODO set for Linux / Win
+//static const char gsOpl3ExpressPortName[] = "";
+
+#if NFM_ENABLE_CUSTOM_ALLOC
+	// custom allocators
+	#include "backends/platform/atari/dlmalloc.h"
+	#define NFM_MSPACE_SIZE 1*1024
+#endif
+
+namespace OPL {
+namespace NfmOPL {
+
+#if NFM_ENABLE_CUSTOM_ALLOC
+
+extern "C"
+{
+	static mspace s_mNfmSpace = nullptr;
+	static sMemoryCallbacks s_MemCallbacks;
+	static void *s_nfmMemoryBase = nullptr;
+
+	static void *nfmAlloc(size_t amount, const eMemoryFlag flag, void* userData, const char* functionName, char* fileName, uint32_t lineNo) {
+#if NFM_ENABLE_LOGS
+		atari_debug("nfmAlloc()");
+#endif
+		return mspace_malloc(s_mNfmSpace, amount);
+	}
+
+	static void *nfmAlignedAlloc(size_t alignment, size_t amount, const eMemoryFlag flag, void* userData, const int8_t* functionName, int8_t* fileName, uint32_t lineNo) {
+#if NFM_ENABLE_LOGS
+		atari_debug("nfmAlignedAlloc()");
+#endif
+		return mspace_memalign(s_mNfmSpace, alignment, amount);
+	}
+
+	static void *nfmRealloc(void* pOriginal, size_t size, void* userData) {
+#if NFM_ENABLE_LOGS
+		atari_debug("nfmRealloc()");
+#endif
+		return mspace_realloc(s_mNfmSpace, pOriginal, size);
+	}
+
+	static void nfmFree(void* ptr, void* userData) {
+#if NFM_ENABLE_LOGS
+		atari_debug("nfmFree()");
+#endif
+		mspace_free(s_mNfmSpace, ptr);
+	}
+
+	static void nfmOutOfMemoryCb(void* userData) {
+#if NFM_ENABLE_LOGS
+		atari_debug("nfmOutOfMemoryCb() out of memory!");
+#endif
+	}
+
+}
+#endif
+
+#if NFM_ENABLE_LOGS
+static const char *s_DebugConfigMsgStrs[NfmOPL::dtNumDevices] = {
+	"Configuring NokturnFM2 cartridge",
+	"Configuring NokturnFM3 cartridge",
+	"Configuring RetroWave OPL3 Express",
+	"Configuring Serdaco OPL2LPT",
+	"Configuring Serdaco OPL3LPT",
+	"Configuring CE OPL2 Audio Board",
+	"Configuring CE OPL3 Duo!",
+	"Configuring NatFeats / NULL",
+	"Configuring Nuked-OPL3",
+};
+
+static const char *s_DebugOplWriteStrs[NfmOPL::dtNumDevices] = {
+	"NfmOPL NokturnFM2 writeReg",
+	"NfmOPL NokturnFM3 writeReg",
+	"NfmOPL OPL3 Express writeReg",
+	"NfmOPL OPL2LPT writeReg",
+	"NfmOPL OPL3LPT writeReg",
+	"NfmOPL OPL2AudioBoard writeReg",
+	"NfmOPL OPL3Duo writeReg",
+	"NfmOPL NatFeats / NULL writeReg",
+	"NfmOPL Nuked-OPL3 writeReg",
+};
+#endif
+namespace RealChip {
+// hardware opl
+OPL::OPL(Config::OplType type, NfmOPL::OplDevice deviceType) : _type(type), _deviceType(deviceType), _activeReg(0), _initialized(false), _useBuffer(NFM_ENABLE_BUFFERED_OUTPUT), _incapableDevice(false) {
+#if NFM_ENABLE_LOGS
+	atari_debug("NfmOPL::RealChip create");
+	atari_debug("Requesting hardware info");
+#endif
+	Supexec(updateHardwareInfo);
+
+	// defaults
+	_ifaceCfg.deviceType = eFmDriverType::FMD_UNDEFINED;
+	_ifaceCfg.soundchip = CM_UNDEFINED;
+	_ifaceCfg.operationMode = CO_UNDEFINED;
+	_ifaceCfg.setup = CC_UNDEFINED;
+	_ifaceCfg.dualChipEmulationEnabled = false;
+	
+	_oplWrite = nullptr;
+	_oplEnqueWrite = nullptr;
+	_oplFlush = nullptr;
+	_oplReset = nullptr;
+
+	if (_type == Config::kOpl2) {
+		_ifaceCfg.operationMode = CO_OPL2;
+	}
+
+	if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
+		_ifaceCfg.operationMode = CO_OPL3;
+	}
+
+	_ifaceCfg.setup = CC_SINGLE;
+
+#if NFM_ENABLE_LOGS
+	if (deviceType < dtNumDevices) {
+		atari_debug(s_DebugConfigMsgStrs[deviceType]);
+	}
+#endif
+
+	switch (deviceType) {
+	case dtNokturnFM2: {
+		_params.uParam.outputPort = OPT_ST_CART;
+		_ifaceCfg.deviceType = eFmDriverType::FMD_OPLCART;
+		_ifaceCfg.soundchip = CM_OPL2;
+		_ifaceCfg.setup = CC_SINGLE;
+
+		if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
+			_incapableDevice = true;
+		}
+	}
+	break;
+	case dtNokturnFM3: {
+		_params.uParam.outputPort = OPT_ST_CART;
+		_ifaceCfg.deviceType = eFmDriverType::FMD_OPLCART;
+		_ifaceCfg.soundchip = CM_OPL3;
+		_ifaceCfg.setup = CC_SINGLE;
+	}
+	break;
+	case dtRWOpl3Express: {
+		_useBuffer = true;                                          // use buffered output, sending data has significant overhead
+		_params.uOpl3ExpressSettings.outputPort = OPT_USB;
+		_ifaceCfg.deviceType = eFmDriverType::FMD_OPL3EXPRESS;
+		_ifaceCfg.soundchip = CM_OPL3;
+		_ifaceCfg.setup = CC_SINGLE;
+		// TODO set serial port on Win / Linux requested by user
+	}
+	break;
+	case dtOPL2LPT: {
+		_params.uParam.outputPort = OPT_LPT;
+
+		_ifaceCfg.deviceType = eFmDriverType::FMD_OPL2LPT;
+		_ifaceCfg.soundchip = CM_OPL2;
+		_ifaceCfg.setup = CC_SINGLE;
+
+		if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
+			_incapableDevice = true;
+		}
+	}
+	break;
+	case dtOPL3LPT: {
+		// OPL2 mode is forced internally on anything below TT due to lack of signals
+		_params.uParam.outputPort = OPT_LPT;
+
+		_ifaceCfg.deviceType = eFmDriverType::FMD_OPL3LPT;
+		_ifaceCfg.soundchip = CM_OPL3;
+		_ifaceCfg.setup = CC_SINGLE;
+	}
+	break;
+	case dtOPL2AudioBoard: {
+		_params.uParam.outputPort = OPT_LPT_SPI;
+		_params.uCeAudioBoardSettings.isOpl2AudioBoard = true;
+		_ifaceCfg.deviceType = eFmDriverType::FMD_CE_OPL2AUDIO_LPT_SPI;
+		_ifaceCfg.soundchip = CM_OPL2;
+		_ifaceCfg.setup = CC_SINGLE;
+
+		if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
+			_incapableDevice = true;
+		}
+	}
+	break;
+	case dtOPL3Duo: {
+		_params.uParam.outputPort = OPT_LPT_SPI;
+		_params.uCeAudioBoardSettings.isOpl2AudioBoard = false;
+		_ifaceCfg.deviceType = eFmDriverType::FMD_CE_OPL3DUO_LPT_SPI;
+		_ifaceCfg.soundchip = CM_OPL3;
+		_ifaceCfg.setup = CC_SINGLE;
+	}
+	break;
+	case dtNatfeatsOpl: {
+		_params.uParam.outputPort = OPT_INTERNAL;
+		_ifaceCfg.deviceType = eFmDriverType::FMD_NULL;
+		_ifaceCfg.soundchip = CM_OPL3;
+		_ifaceCfg.setup = CC_SINGLE;
+	}
+	break;
+	default: {
+		warning("NfmOPL::RealChip Unrecognized device type or software synthesizer was requested.");
+	}
+	break;
+	};
+}
+
+OPL::~OPL() {
+	if (_initialized == true) {
+#if NFM_ENABLE_LOGS
+		atari_debug("NfmOPL destroy");
+#endif
+		if (_useBuffer) {
+			// flush
+#if NFM_ENABLE_LOGS
+			atari_debug("OPL flush");
+#endif
+			_oplFlush();
+		}
+
+		_oplWrite = nullptr;
+		_oplEnqueWrite = nullptr;
+		_oplFlush = nullptr;
+		_oplReset = nullptr;
+
+		(void)nfDeinit(&_iface);
+		(void)nfDestroyInterface(&_iface);
+
+		_incapableDevice = false;
+		_useBuffer = false;
+		_initialized = false;
+#if NFM_ENABLE_CUSTOM_ALLOC
+		if (s_mNfmSpace) {
+			destroy_mspace(s_mNfmSpace);
+			Mfree(s_nfmMemoryBase);
+			s_nfmMemoryBase = nullptr;
+		}
+#endif
+	}
+}
+
+bool OPL::init() {
+#if NFM_ENABLE_LOGS
+	atari_debug("NfmOPL::RealChip init");
+#endif
+	if (_incapableDevice) {
+#if NFM_ENABLE_LOGS
+		atari_debug("NfmOPL::RealChip OPL2 device cannot emulate requested dual OPL2 / OPL3!");
+#endif
+		return false;
+	}
+
+#if NFM_ENABLE_CUSTOM_ALLOC
+	// set default callbacks
+	setDefaultUserMemoryCallbacks();
+
+	s_nfmMemoryBase = (void *)Mxalloc((int32_t)NFM_MSPACE_SIZE + 256, (int16_t)3);
+
+	if (s_nfmMemoryBase) {
+		s_mNfmSpace = create_mspace_with_base(s_nfmMemoryBase, NFM_MSPACE_SIZE, 0);
+
+		if (s_mNfmSpace == 0) {
+#if NFM_ENABLE_LOGS
+			atari_debug("NfmOPL::RealChip create_mspace failed!");
+#endif
+			return false;
+		}
+
+		// install custom memory allocator callbacks
+		s_MemCallbacks.alloc = nfmAlloc;
+		s_MemCallbacks.alignedAlloc = nfmAlignedAlloc;
+		s_MemCallbacks.release = nfmFree;
+		s_MemCallbacks.realloc = nfmRealloc;
+		s_MemCallbacks.outOfMemory = nfmOutOfMemoryCb;
+
+		setUserMemoryCallbacks(&s_MemCallbacks);
+	} else {
+#if NFM_ENABLE_LOGS
+		atari_debug("NfmOPL::RealChip Out of system memory!");
+#endif
+		return false;
+	}
+#else
+	setDefaultUserMemoryCallbacks();
+#endif
+	_iface = nfCreateInterface(_ifaceCfg);
+
+	if (_iface.setup != CC_UNDEFINED) {
+		const int32_t retval = nfInit(&_iface, &_params);
+
+		if (retval >= 0) {
+			_oplWrite = _iface.write;
+			_oplEnqueWrite = _iface.enqueWrite;
+			_oplFlush = _iface.flush;
+			_oplReset = _iface.reset;
+
+			initDualOpl2OnOpl3(_type);
+			_initialized = true;
+
+#if NFM_ENABLE_LOGS
+			atari_debug("NfmOPL::RealChip init OK");
+#endif
+			return true;
+		}
+	}
+
+#if NFM_ENABLE_LOGS
+	atari_debug("NfmOPL::RealChip init failed!");
+#endif
+	return false;
+}
+
+void OPL::reset() {
+#if NFM_ENABLE_LOGS
+	atari_debug("NfmOPL::RealChip reset");
+#endif
+	for (int16_t i = 0; i < 256; i ++) {
+		writeReg((int)i, 0);
+	}
+
+	if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
+		for (int16_t i = 0; i < 256; i++) {
+			writeReg((int)i + 256, 0);
+		}
+	}
+
+	_activeReg = 0;
+
+	initDualOpl2OnOpl3(_type);
+}
+
+void OPL::write(int portAddress, int value) {
+	if (portAddress & 1) {
+		writeReg(_activeReg, value);
+		return;
+	} else {
+		if (_type == Config::kOpl2) {
+			_activeReg = value & 0xff;
+			return;
+		} else {
+			// opl3 / dual opl2
+			_activeReg = (value & 0xff) | ((portAddress << 7) & 0x100);
+			return;
+		}
+
+		warning("NfmOPL::RealChip: unsupported OPL mode %d", _type);
+	}
+}
+
+void OPL::writeReg(int reg, int value) {
+#if NFM_ENABLE_LOGS
+	atari_debug(s_DebugOplWriteStrs[_deviceType]);
+#endif
+
+	if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
+		reg &= 0x1ff;
+	} else {
+		reg &= 0xff;
+	}
+
+	value &= 0xff;
+
+	if (emulateDualOpl2OnOpl3(reg, value, _type)) {
+		sOplRegisterWrite regWrite;
+
+		if (reg < 0x100) {
+			regWrite = {0, (uint8_t)reg, (uint8_t)value};
+		} else {
+			regWrite = {1, (uint8_t)(reg - 0x100), (uint8_t)value};
+		}
+
+		if (_useBuffer) {
+			_oplEnqueWrite(&regWrite);
+		} else {
+			_oplWrite(&regWrite);
+		}
+	}
+}
+
+void OPL::onTimer() {
+	if (_useBuffer) {
+		if (_initialized) {
+#if NFM_ENABLE_LOGS
+			// flush
+			atari_debug("NfmOPL::RealChip flush");
+#endif
+			_oplFlush();
+		}
+	}
+
+	Audio::RealChip::onTimer();
+}
+
+OPL *create(Config::OplType type, OplDevice device) {
+	return new OPL(type, device);
+}
+} // End of namespace RealChip
+
+namespace EmulatedChip {
+OPL::OPL(Config::OplType type, enum NfmOPL::OplDevice deviceType): _type(type), _rate(0), _deviceType(deviceType), _activeReg(0), _initialized(false), _useBuffer(NFM_ENABLE_BUFFERED_OUTPUT), _incapableDevice(false) {
+#if NFM_ENABLE_LOGS
+	atari_debug("NfmOPL::EmulatedChip create");
+	atari_debug("Requesting hardware info");
+#endif
+	Supexec(updateHardwareInfo);
+
+	// defaults
+	_ifaceCfg.deviceType = eFmDriverType::FMD_UNDEFINED;
+	_ifaceCfg.soundchip = CM_UNDEFINED;
+	_ifaceCfg.operationMode = CO_UNDEFINED;
+	_ifaceCfg.setup = CC_UNDEFINED;
+	_ifaceCfg.dualChipEmulationEnabled = false;
+
+	_oplWrite = nullptr;
+	_oplEnqueWrite = nullptr;
+	_oplFlush = nullptr;
+	_oplReset = nullptr;
+	_generateAudioStream = nullptr;
+
+	if (_type == Config::kOpl2) {
+		_ifaceCfg.operationMode = CO_OPL2;
+	}
+
+	if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
+		_ifaceCfg.operationMode = CO_OPL3;
+	}
+
+	_ifaceCfg.setup = CC_SINGLE;
+
+#if NFM_ENABLE_LOGS
+	if (deviceType < dtNumDevices) {
+		atari_debug(s_DebugConfigMsgStrs[deviceType]);
+	}
+#endif
+	switch (deviceType) {
+	case dtNukedOpl3: {
+		_rate = g_system->getMixer()->getOutputRate();
+		_params.uSoftSynthSettings.outputPort = OPT_INTERNAL;
+		_params.uSoftSynthSettings.sampleRate = _rate;
+		_params.uSoftSynthSettings.enableDualChipEmulation = _ifaceCfg.dualChipEmulationEnabled;
+
+		_ifaceCfg.deviceType = FMD_NUKEDOPL3;
+		_ifaceCfg.soundchip = CM_OPL3;
+		_ifaceCfg.operationMode = CO_OPL3;
+		_ifaceCfg.setup = CC_SINGLE;
+	}
+	break;
+
+	default: {
+		warning("NfmOPL::EmulatedChip Unrecognized device type or not software synthesizer!");
+		_incapableDevice = true;
+	}
+	break;
+	};
+}
+
+OPL::~OPL() {
+
+	if (_initialized == true) {
+#if NFM_ENABLE_LOGS
+		atari_debug("NfmOPL::EmulatedChip destroy");
+#endif
+		stop();
+
+		if (_useBuffer) {
+#if NFM_ENABLE_LOGS
+			// flush
+			atari_debug("NfmOPL::EmulatedChip OPL flush");
+#endif
+			_oplFlush();
+		}
+
+		_oplWrite = nullptr;
+		_oplEnqueWrite = nullptr;
+		_oplFlush = nullptr;
+		_oplReset = nullptr;
+		_generateAudioStream = nullptr;
+
+		(void)nfDeinit(&_iface);
+		(void)nfDestroyInterface(&_iface);
+
+		_incapableDevice = false;
+		_useBuffer = false;
+		_initialized = false;
+
+#if NFM_ENABLE_CUSTOM_ALLOC
+		if (s_mNfmSpace) {
+			destroy_mspace(s_mNfmSpace);
+			Mfree(s_nfmMemoryBase);
+			s_nfmMemoryBase = nullptr;
+		}
+#endif
+	}
+}
+
+bool OPL::init() {
+#if NFM_ENABLE_LOGS
+	atari_debug("NfmOPL::EmulatedChip init");
+#endif
+	if (_incapableDevice) {
+#if NFM_ENABLE_LOGS
+		atari_debug("NfmOPL::EmulatedChip device isn't soft synth type!");
+#endif
+		return false;
+	}
+
+#if NFM_ENABLE_CUSTOM_ALLOC
+	// set default callbacks
+	setDefaultUserMemoryCallbacks();
+
+	s_nfmMemoryBase = (void *)Mxalloc((int32_t)NFM_MSPACE_SIZE + 256, (int16_t)3);
+
+	if (s_nfmMemoryBase) {
+		s_mNfmSpace = create_mspace_with_base(s_nfmMemoryBase, NFM_MSPACE_SIZE, 0);
+
+		if (s_mNfmSpace == 0) {
+#if NFM_ENABLE_LOGS
+			atari_debug("NfmOPL::EmulatedChip create_mspace failed!");
+#endif
+			return false;
+		}
+
+		// install custom memory allocator callbacks
+		s_MemCallbacks.alloc = nfmAlloc;
+		s_MemCallbacks.alignedAlloc = nfmAlignedAlloc;
+		s_MemCallbacks.release = nfmFree;
+		s_MemCallbacks.realloc = nfmRealloc;
+		s_MemCallbacks.outOfMemory = nfmOutOfMemoryCb;
+
+		setUserMemoryCallbacks(&s_MemCallbacks);
+	} else {
+#if NFM_ENABLE_LOGS
+		atari_debug("NfmOPL::EmulatedChip Out of system memory!");
+#endif
+		return false;
+	}
+#else
+	setDefaultUserMemoryCallbacks();
+#endif
+	_iface = nfCreateInterface(_ifaceCfg);
+
+	if (_iface.setup != CC_UNDEFINED) {
+		const int32_t retval = nfInit(&_iface, &_params);
+
+		if (retval >= 0) {
+			_oplWrite = _iface.write;
+			_oplEnqueWrite = _iface.enqueWrite;
+			_oplFlush = _iface.flush;
+			_oplReset = _iface.reset;
+			_generateAudioStream = _iface.generateAudioStream;
+
+			_activeReg = 0;
+
+			initDualOpl2OnOpl3(_type);
+
+			_initialized = true;
+
+#if NFM_ENABLE_LOGS
+			atari_debug("NfmOPL::RealChip init OK");
+#endif
+			return true;
+		}
+	}
+
+#if NFM_ENABLE_LOGS
+	atari_debug("NfmOPL::RealChip init failed!");
+#endif
+	return false;
+}
+
+void OPL::reset() {
+
+	_oplReset();
+
+	for (int16_t i = 0; i < 256; i ++) {
+		writeReg((int)i, 0);
+	}
+
+	if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
+		for (int16_t i = 0; i < 256; i++) {
+			writeReg((int)i + 256, 0);
+		}
+	}
+
+	_activeReg = 0;
+
+	initDualOpl2OnOpl3(_type);
+}
+
+void OPL::write(int portAddress, int value) {
+	if (portAddress & 1) {
+		writeReg(_activeReg, value);
+		return;
+	} else {
+		if (_type == Config::kOpl2) {
+			_activeReg = value & 0xff;
+			return;
+		} else {
+			// opl3 / dual opl2
+			_activeReg = (value & 0xff) | ((portAddress << 7) & 0x100);
+			return;
+		}
+
+		warning("NfmOPL::EmulatedChip: unsupported OPL mode %d", _type);
+	}
+}
+
+void OPL::writeReg(int reg, int value) {
+#if NFM_ENABLE_LOGS
+	atari_debug(s_DebugOplWriteStrs[_deviceType]);
+#endif
+
+	if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
+		reg &= 0x1ff;
+	} else {
+		reg &= 0xff;
+	}
+
+	value &= 0xff;
+
+	if (emulateDualOpl2OnOpl3(reg, value, _type)) {
+		sOplRegisterWrite regWrite;
+
+		if (reg < 0x100) {
+			regWrite = {0, (uint8_t)reg, (uint8_t)value};
+		} else {
+			regWrite = {1, (uint8_t)(reg - 0x100), (uint8_t)value};
+		}
+
+		if (_useBuffer) {
+			_oplEnqueWrite(&regWrite);
+		} else {
+			_oplWrite(&regWrite);
+		}
+	}
+}
+
+void OPL::generateSamples(int16 *buffer, int length) {
+	assert(buffer != nullptr);
+	assert(length!=0);
+	_generateAudioStream(buffer, 0, (uint16_t)length / 2);
+}
+
+OPL *create(Config::OplType type, OplDevice device) {
+	return new OPL(type, device);
+}
+} // End of namespace EmulatedChip
+
+} // End of namespace NfmOPL
+} // End of namespace OPL
diff --git a/audio/nfmopl.h b/audio/nfmopl.h
new file mode 100644
index 00000000000..43f2cbd299e
--- /dev/null
+++ b/audio/nfmopl.h
@@ -0,0 +1,133 @@
+/* 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/>.
+ *
+ */
+
+/*
+    OPL interface using nFM library(https://framagit.org/nokturnal/nfm)
+    for NokturnFM2 / 3, OPL-L carts, CE OPL2 Audio board, CE OPL3 Duo!, Serdaco OPL2LPT / OPL3LPT, RetroWave OPL3 Express and more ...
+    (c) 2023-26 Paweł Góralski
+ */
+
+#ifndef AUDIO_SYNTH_NFM_OPL_H
+#define AUDIO_SYNTH_NFM_OPL_H
+
+#include "audio/fmopl.h"
+
+#ifndef restrict
+	#define restrict __restrict__
+#endif
+
+extern "C"
+{
+#include <nfmcore.h>
+#include <nfmutil.h>
+}
+
+namespace OPL {
+namespace NfmOPL {
+enum OplDevice : int16_t {
+	dtNokturnFM2 = 0,
+	dtNokturnFM3,
+	dtRWOpl3Express,
+	dtOPL2LPT,
+	dtOPL3LPT,
+	dtOPL2AudioBoard,
+	dtOPL3Duo,
+	dtNatfeatsOpl,
+	dtNukedOpl3,
+	dtNumDevices
+};
+
+namespace RealChip {
+class OPL : public ::OPL::OPL, public Audio::RealChip {
+private:
+	Config::OplType _type;
+	OplDevice _deviceType;
+	sOplInterface _iface;
+	funcPtrOplWrite _oplWrite;
+	funcPtrOplWrite _oplEnqueWrite;
+	funcPtrOplFlush _oplFlush;
+	funcPtrOplReset _oplReset;
+
+	sInterfaceInitData _params;
+	sOplInterfaceConfiguration _ifaceCfg;
+
+	int _activeReg;
+	bool _initialized;
+	bool _useBuffer;
+	bool _incapableDevice;
+public:
+	explicit OPL(Config::OplType type, enum NfmOPL::OplDevice deviceType);
+	~OPL();
+
+	bool init() override final;
+	void reset() override final;
+
+	void write(int portAddress, int value) override final;
+	void writeReg(int reg, int value) override final;
+
+protected:
+
+	virtual void onTimer() override final;
+};
+};  // End of namespace RealChip
+
+namespace EmulatedChip {
+class OPL : public ::OPL::OPL, public Audio::EmulatedChip {
+private:
+	Config::OplType _type;
+	OplDevice _deviceType;
+	sOplInterface _iface;
+	funcPtrOplWrite _oplWrite;
+	funcPtrOplWrite _oplEnqueWrite;
+	funcPtrOplFlush _oplFlush;
+	funcPtrOplReset _oplReset;
+	funcGenerateAudioStream _generateAudioStream;
+	sInterfaceInitData _params;
+	sOplInterfaceConfiguration _ifaceCfg;
+
+	int _activeReg;
+	uint _rate;
+	bool _initialized;
+	bool _useBuffer;
+	bool _incapableDevice;
+public:
+	explicit OPL(Config::OplType type, enum NfmOPL::OplDevice deviceType);
+	~OPL();
+
+	bool isStereo() const override {
+		return true;
+	}
+
+	bool init() override final;
+	void reset() override final;
+
+	void write(int portAddress, int value) override final;
+	void writeReg(int reg, int value) override final;
+
+protected:
+	void generateSamples(int16 *buffer, int numSamples) override final;
+};
+}; // End of namespace EmulatedChip
+
+} // End of namespace NfmOPL
+} // End of namespace OPL
+
+#endif
diff --git a/backends/platform/atari/build-firebee.sh b/backends/platform/atari/build-firebee.sh
index e175f5476a5..48aee04c5ac 100755
--- a/backends/platform/atari/build-firebee.sh
+++ b/backends/platform/atari/build-firebee.sh
@@ -40,6 +40,7 @@ then
 	--with-freetype2-prefix="$(${PLATFORM}-gcc -print-sysroot)/usr/bin/${CPU_DIR}" \
 	--with-mikmod-prefix="$(${PLATFORM}-gcc -print-sysroot)/usr/bin/${CPU_DIR}" \
 	--enable-release \
+	--disable-nfm \
 	--enable-verbose-build \
 	${PLUGINS_FLAGS}
 fi
diff --git a/base/version.cpp b/base/version.cpp
index e0cf813636a..1719489d9aa 100644
--- a/base/version.cpp
+++ b/base/version.cpp
@@ -253,4 +253,9 @@ const char gScummVMFeatures[] = ""
 #ifdef USE_RETROWAVE
 	"RetroWave "
 #endif
+
+#ifdef USE_NFM
+	"Nokturnal nFM "
+#endif
+
 	;
diff --git a/configure b/configure
index cdbe5b166dd..91512ba356a 100755
--- a/configure
+++ b/configure
@@ -160,6 +160,7 @@ _flac=auto
 _mad=auto
 _opl2lpt=no
 _retrowave=auto
+_nfm=auto
 _alsa=auto
 _seq_midi=auto
 _sndio=auto
@@ -1172,6 +1173,9 @@ Optional Libraries:
   --with-retrowave-prefix=DIR prefix where libretrowave is installed (optional)
   --enable-retrowave          enable RetroWave OPL3 support
 
+  --with-nfm-prefix=DIR  prefix where nFM is installed (optional)
+  --enable-nfm           enable OPL2/3 support via nFM
+
   --with-sparkle-prefix=DIR prefix where sparkle is installed
                             (macOS/Windows only - optional)
   --disable-sparkle         disable sparkle automatic update support
@@ -1310,6 +1314,8 @@ for ac_option in $@; do
 	--disable-opl2lpt)            _opl2lpt=no            ;;
 	--enable-retrowave)           _retrowave=yes         ;;
 	--disable-retrowave)          _retrowave=no          ;;
+	--enable-nfm)                 _nfm=yes               ;;
+	--disable-nfm)                _nfm=no                ;;
 	--enable-flac)                _flac=yes              ;;
 	--disable-flac)               _flac=no               ;;
 	--enable-mad)                 _mad=yes               ;;
@@ -1496,6 +1502,11 @@ for ac_option in $@; do
 		RETROWAVE_CFLAGS="-I$arg/include"
 		RETROWAVE_LIBS="-L$arg/lib"
 		;;
+	--with-nfm-prefix=*)
+		arg=`echo $ac_option | cut -d '=' -f 2`
+		NFM_CFLAGS="-I$arg/include -I$arg/include/platform/atari"
+		NFM_LIBS="-L$arg/lib/m68020-60"
+		;;
 	--with-flac-prefix=*)
 		arg=`echo $ac_option | cut -d '=' -f 2`
 		FLAC_CFLAGS="-I$arg/include"
@@ -3883,6 +3894,7 @@ if test -n "$_host"; then
 			#	append_var LDFLAGS "-s"
 			#fi
 			append_var DEFINES "-DSCUMM_OPTIMISED_CODE"
+			append_var DEFINES "-DDISABLE_DOSBOX_OPL"
 
 			# auto -> no
 			if test "$_optimizations" = "yes"; then
@@ -5593,6 +5605,58 @@ fi
 define_in_config_if_yes "$_retrowave" 'USE_RETROWAVE'
 echo "$_retrowave"
 
+#
+# Check for nFM OPL2/3 library
+#
+echocheck "nFM OPL2 / OPL3"
+if test "$_nfm" = auto ; then
+	_nfm=no
+	cat > $TMPC << EOF
+extern "C"
+{
+#include <nfmcore.h>
+#include <nfmutil.h>
+}
+
+sOplInterface gOplIf;
+
+int main(void)
+{ 
+	sOplInterfaceConfiguration ifCfg;
+	ifCfg.deviceType = FMD_NULL;
+    ifCfg.soundchip = CM_OPL3;
+    ifCfg.operationMode = CO_OPL3;
+    ifCfg.setup = CC_SINGLE;
+    ifCfg.dualChipEmulationEnabled = false;
+
+    sInterfaceInitData params;
+	params.uParam.outputPort = OPT_INTERNAL;
+    params.uParam.param = 0;
+
+    gOplIf = nfCreateInterface(ifCfg); 
+
+    const int32_t retval = nfInit(&gOplIf, &params);
+
+    if (retval >= 0)
+    {
+	 (void)nfDeinit(&gOplIf);
+     return 0;
+    }	
+
+  return -1;
+}
+EOF
+	cc_check $NFM_CFLAGS -DSC_PLATFORM_ATARI=1 -DSC_ENABLE_MALLOC=0 -DNFM_OPL_SHADOW_REG_WRITE_ENABLE=1 $NFM_LIBS -lnfmcore -lnfmutil -ldrvcommon -ldrvnokturnfm -ldrvopl3Duo -ldrvopl3express -ldrvoplxlpt -ldrvoplnull -lsyscore -lcontainers -lallocators &&\
+	_nfm=yes
+fi
+if test "$_nfm" = yes; then
+	append_var DEFINES "-DSC_PLATFORM_ATARI=1 -DSC_ENABLE_MALLOC=0 -DNFM_OPL_SHADOW_REG_WRITE_ENABLE=1"
+	append_var LIBS "$NFM_LIBS -lnfmcore -lnfmutil -ldrvcommon -ldrvnokturnfm -ldrvopl3Duo -ldrvopl3express -ldrvoplxlpt -ldrvoplnull -lsyscore -lcontainers -lallocators"
+	append_var INCLUDES "$NFM_CFLAGS"
+fi
+define_in_config_if_yes "$_nfm" 'USE_NFM'
+echo "$_nfm"
+
 #
 # Check for FLAC
 #
diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp
index 3e6e98768da..af4d776dca9 100644
--- a/devtools/create_project/create_project.cpp
+++ b/devtools/create_project/create_project.cpp
@@ -1203,6 +1203,7 @@ const Feature s_features[] = {
 	{    "sdlnet",     "USE_SDL_NET", true, true,  "SDL_net support" },
 	{   "discord",     "USE_DISCORD", true, false, "Discord support" },
 	{ "retrowave",   "USE_RETROWAVE", true, false, "RetroWave OPL3 support" },
+	{     "nFM",           "USE_NFM", true, false, "Nokturnal nFM OPL2/OPL3 support" },
 	{       "a52",         "USE_A52", true, false, "ATSC A/52 support" },
 	{       "mpc",      "USE_MPCDEC", true, false, "Musepack support" },
 


Commit: 45e363f353bc9e601344333cb575f444fef7550d
    https://github.com/scummvm/scummvm/commit/45e363f353bc9e601344333cb575f444fef7550d
Author: Paweł Góralski (20125592+n0kturnal at users.noreply.github.com)
Date: 2026-06-21T15:19:56+02:00

Commit Message:
AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)

Fixed formatting.

Changed paths:
    devtools/create_project/create_project.cpp


diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp
index af4d776dca9..f2c09800889 100644
--- a/devtools/create_project/create_project.cpp
+++ b/devtools/create_project/create_project.cpp
@@ -1203,7 +1203,7 @@ const Feature s_features[] = {
 	{    "sdlnet",     "USE_SDL_NET", true, true,  "SDL_net support" },
 	{   "discord",     "USE_DISCORD", true, false, "Discord support" },
 	{ "retrowave",   "USE_RETROWAVE", true, false, "RetroWave OPL3 support" },
-	{     "nFM",           "USE_NFM", true, false, "Nokturnal nFM OPL2/OPL3 support" },
+	{       "nFM",         "USE_NFM", true, false, "Nokturnal nFM OPL2/OPL3 support" },
 	{       "a52",         "USE_A52", true, false, "ATSC A/52 support" },
 	{       "mpc",      "USE_MPCDEC", true, false, "Musepack support" },
 


Commit: fa13bd283be5e56f303daa5d87516496bd8ed5eb
    https://github.com/scummvm/scummvm/commit/fa13bd283be5e56f303daa5d87516496bd8ed5eb
Author: Paweł Góralski (20125592+n0kturnal at users.noreply.github.com)
Date: 2026-06-21T15:19:56+02:00

Commit Message:
AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)

Explicit setting of SC_PLATFORM_ATARI define was removed, macro is set by compiler preprocessor now in nFM library core headers.

Changed paths:
    configure


diff --git a/configure b/configure
index 91512ba356a..3a98319b250 100755
--- a/configure
+++ b/configure
@@ -5646,11 +5646,11 @@ int main(void)
   return -1;
 }
 EOF
-	cc_check $NFM_CFLAGS -DSC_PLATFORM_ATARI=1 -DSC_ENABLE_MALLOC=0 -DNFM_OPL_SHADOW_REG_WRITE_ENABLE=1 $NFM_LIBS -lnfmcore -lnfmutil -ldrvcommon -ldrvnokturnfm -ldrvopl3Duo -ldrvopl3express -ldrvoplxlpt -ldrvoplnull -lsyscore -lcontainers -lallocators &&\
+	cc_check $NFM_CFLAGS -DSC_ENABLE_MALLOC=0 -DNFM_OPL_SHADOW_REG_WRITE_ENABLE=1 $NFM_LIBS -lnfmcore -lnfmutil -ldrvcommon -ldrvnokturnfm -ldrvopl3Duo -ldrvopl3express -ldrvoplxlpt -ldrvoplnull -lsyscore -lcontainers -lallocators &&\
 	_nfm=yes
 fi
 if test "$_nfm" = yes; then
-	append_var DEFINES "-DSC_PLATFORM_ATARI=1 -DSC_ENABLE_MALLOC=0 -DNFM_OPL_SHADOW_REG_WRITE_ENABLE=1"
+	append_var DEFINES "-DSC_ENABLE_MALLOC=0 -DNFM_OPL_SHADOW_REG_WRITE_ENABLE=1"
 	append_var LIBS "$NFM_LIBS -lnfmcore -lnfmutil -ldrvcommon -ldrvnokturnfm -ldrvopl3Duo -ldrvopl3express -ldrvoplxlpt -ldrvoplnull -lsyscore -lcontainers -lallocators"
 	append_var INCLUDES "$NFM_CFLAGS"
 fi


Commit: a0a300c1bc48247dee298c86e8b7aaa63b7d8716
    https://github.com/scummvm/scummvm/commit/a0a300c1bc48247dee298c86e8b7aaa63b7d8716
Author: Paweł Góralski (20125592+n0kturnal at users.noreply.github.com)
Date: 2026-06-21T15:19:56+02:00

Commit Message:
AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)

Helper script for downloading prebuild nFM library to Atari backend folder

Changed paths:
  A backends/platform/atari/get-nfm.sh


diff --git a/backends/platform/atari/get-nfm.sh b/backends/platform/atari/get-nfm.sh
new file mode 100644
index 00000000000..831a759d8d8
--- /dev/null
+++ b/backends/platform/atari/get-nfm.sh
@@ -0,0 +1,14 @@
+#!/bin/bash -eux
+# -e: Exit immediately if a command exits with a non-zero status.
+# -u: Treat unset variables as an error when substituting.
+# -x: Display expanded script commands
+
+# downloads and unpacks in ./deps folder precompiled nFM library release for m68k Atari TOS / mintelf gcc stdlib
+NFM_VERSION=0.2.1
+
+curl -o ./backends/platform/atari/nfm.zip https://nokturnal.pl/downloads/atari/nokturnfm/nfmplay/builds/releases/0.2.1/ataritos-mintelf-gcc-stdlib-m68k-0.2.1-5c688e1a.zip
+rm -rf ./backends/platform/atari/deps/nfm
+
+unzip ./backends/platform/atari/nfm.zip "nFM_${NFM_VERSION}/*" -d ./backends/platform/atari/deps/
+mv ./backends/platform/atari/deps/nFM_${NFM_VERSION} ./backends/platform/atari/deps/nfm
+rm ./backends/platform/atari/nfm.zip
\ No newline at end of file


Commit: 301678656ff4b6cd66e95811ebef4a834e1198fb
    https://github.com/scummvm/scummvm/commit/301678656ff4b6cd66e95811ebef4a834e1198fb
Author: Paweł Góralski (20125592+n0kturnal at users.noreply.github.com)
Date: 2026-06-21T15:19:56+02:00

Commit Message:
AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)

Updated helper script for downloading prebuild nFM library to Atari dependencies folder in backend, added ST Bus ISA / VME Sound Blaster to drop down list. Fixed linking issues related to newly added drivers. Fixed debug output - atari_debug() macro was removed.

Changed paths:
    audio/fmopl.cpp
    audio/nfmopl.cpp
    audio/nfmopl.h
    backends/platform/atari/get-nfm.sh
    configure


diff --git a/audio/fmopl.cpp b/audio/fmopl.cpp
index d56083fc409..c760dbc6265 100644
--- a/audio/fmopl.cpp
+++ b/audio/fmopl.cpp
@@ -91,8 +91,9 @@ enum OplEmulator {
 	kNfmOPL3LPT = 13,
 	kNfmCeOPL2AudioBoard = 14,
 	kNfmCeOPL3Duo = 15,
-	kNfmNatfeatsNull = 16,
-	kNfmNukedOpl3 = 17
+	kNfmStBusIsaVmeSb = 16,
+	kNfmNatfeatsNull = 17,
+	kNfmNukedOpl3 = 18
 #endif
 };
 
@@ -136,6 +137,7 @@ const Config::EmulatorDescription Config::_drivers[] = {
 	{"nfm_opl3lpt", _s("[nFM] Serdaco OPL3LPT (OPL3)"), kNfmOPL3LPT, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
 	{"nfm_ce_opl2ab", _s("[nFM] Cheerful Electronics OPL2 Audio Board (OPL2)"), kNfmCeOPL2AudioBoard, kFlagOpl2 },
 	{"nfm_ce_opl3duo", _s("[nFM] Cheerful Electronics OPL3 Duo! (2xOPL3)"), kNfmCeOPL3Duo, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
+	{"nfm_ce_stbus_isa_vme", _s("[nFM] ST Bus ISA / VME SoundBlaster"), kNfmStBusIsaVmeSb, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
 	{"nfm_natfeats_null", _s("[nFM] NatFeats / NULL"), kNfmNatfeatsNull, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
 	{"nfm_nuked_opl3", _s("[nFM] Nuked-OPL3 softsynth (OPL3)"), kNfmNukedOpl3, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3},
 #endif
@@ -320,6 +322,8 @@ OPL *Config::create(DriverId driver, OplType type) {
 
 	case kNfmCeOPL3Duo:
 		return NfmOPL::RealChip::create(type, NfmOPL::dtOPL3Duo);
+	case kNfmStBusIsaVmeSb:
+		return NfmOPL::RealChip::create(type, NfmOPL::dtStBusIsaVmeSb);
 	case kNfmNatfeatsNull:
 		return NfmOPL::RealChip::create(type, NfmOPL::dtNatfeatsOpl);
 	case kNfmNukedOpl3:
diff --git a/audio/nfmopl.cpp b/audio/nfmopl.cpp
index 8af695f3bee..fb1eec66d7a 100644
--- a/audio/nfmopl.cpp
+++ b/audio/nfmopl.cpp
@@ -27,12 +27,12 @@
 
 #define FORBIDDEN_SYMBOL_ALLOW_ALL
 #include "common/scummsys.h"
+#include "common/debug.h"
+
 #include "audio/mixer.h"
 #include "audio/fmopl.h"
 #include "audio/nfmopl.h"
 
-#include "backends/platform/atari/atari-debug.h"
-
 extern "C"
 {
 #include <hwinfo.h>
@@ -69,35 +69,35 @@ extern "C"
 
 	static void *nfmAlloc(size_t amount, const eMemoryFlag flag, void* userData, const char* functionName, char* fileName, uint32_t lineNo) {
 #if NFM_ENABLE_LOGS
-		atari_debug("nfmAlloc()");
+		debug("nfmAlloc()");
 #endif
 		return mspace_malloc(s_mNfmSpace, amount);
 	}
 
 	static void *nfmAlignedAlloc(size_t alignment, size_t amount, const eMemoryFlag flag, void* userData, const int8_t* functionName, int8_t* fileName, uint32_t lineNo) {
 #if NFM_ENABLE_LOGS
-		atari_debug("nfmAlignedAlloc()");
+		debug("nfmAlignedAlloc()");
 #endif
 		return mspace_memalign(s_mNfmSpace, alignment, amount);
 	}
 
 	static void *nfmRealloc(void* pOriginal, size_t size, void* userData) {
 #if NFM_ENABLE_LOGS
-		atari_debug("nfmRealloc()");
+		debug("nfmRealloc()");
 #endif
 		return mspace_realloc(s_mNfmSpace, pOriginal, size);
 	}
 
 	static void nfmFree(void* ptr, void* userData) {
 #if NFM_ENABLE_LOGS
-		atari_debug("nfmFree()");
+		debug("nfmFree()");
 #endif
 		mspace_free(s_mNfmSpace, ptr);
 	}
 
 	static void nfmOutOfMemoryCb(void* userData) {
 #if NFM_ENABLE_LOGS
-		atari_debug("nfmOutOfMemoryCb() out of memory!");
+		debug("nfmOutOfMemoryCb() out of memory!");
 #endif
 	}
 
@@ -113,6 +113,7 @@ static const char *s_DebugConfigMsgStrs[NfmOPL::dtNumDevices] = {
 	"Configuring Serdaco OPL3LPT",
 	"Configuring CE OPL2 Audio Board",
 	"Configuring CE OPL3 Duo!",
+	"Configuring ST Bus ISA / VME SoundBlaster",
 	"Configuring NatFeats / NULL",
 	"Configuring Nuked-OPL3",
 };
@@ -125,6 +126,7 @@ static const char *s_DebugOplWriteStrs[NfmOPL::dtNumDevices] = {
 	"NfmOPL OPL3LPT writeReg",
 	"NfmOPL OPL2AudioBoard writeReg",
 	"NfmOPL OPL3Duo writeReg",
+	"NfmOPL ST Bus ISA / VME SoundBlaster writeReg",
 	"NfmOPL NatFeats / NULL writeReg",
 	"NfmOPL Nuked-OPL3 writeReg",
 };
@@ -133,8 +135,8 @@ namespace RealChip {
 // hardware opl
 OPL::OPL(Config::OplType type, NfmOPL::OplDevice deviceType) : _type(type), _deviceType(deviceType), _activeReg(0), _initialized(false), _useBuffer(NFM_ENABLE_BUFFERED_OUTPUT), _incapableDevice(false) {
 #if NFM_ENABLE_LOGS
-	atari_debug("NfmOPL::RealChip create");
-	atari_debug("Requesting hardware info");
+	debug("NfmOPL::RealChip create");
+	debug("Requesting hardware info");
 #endif
 	Supexec(updateHardwareInfo);
 
@@ -162,7 +164,7 @@ OPL::OPL(Config::OplType type, NfmOPL::OplDevice deviceType) : _type(type), _dev
 
 #if NFM_ENABLE_LOGS
 	if (deviceType < dtNumDevices) {
-		atari_debug(s_DebugConfigMsgStrs[deviceType]);
+		debug(s_DebugConfigMsgStrs[deviceType]);
 	}
 #endif
 
@@ -235,6 +237,17 @@ OPL::OPL(Config::OplType type, NfmOPL::OplDevice deviceType) : _type(type), _dev
 		_ifaceCfg.setup = CC_SINGLE;
 	}
 	break;
+	case dtStBusIsaVmeSb: {
+		// TODO: handle additional VME / ISA parameters if needed
+		_params.uParam.outputPort = OPT_ISA;
+		_params.uParam.param = 0;
+
+		_ifaceCfg.deviceType = eFmDriverType::FMD_ISA_SB;
+		_ifaceCfg.soundchip = CM_OPL3;
+		_ifaceCfg.operationMode = CO_OPL2,
+		_ifaceCfg.setup = CC_SINGLE;
+	}
+	break;
 	case dtNatfeatsOpl: {
 		_params.uParam.outputPort = OPT_INTERNAL;
 		_ifaceCfg.deviceType = eFmDriverType::FMD_NULL;
@@ -252,12 +265,12 @@ OPL::OPL(Config::OplType type, NfmOPL::OplDevice deviceType) : _type(type), _dev
 OPL::~OPL() {
 	if (_initialized == true) {
 #if NFM_ENABLE_LOGS
-		atari_debug("NfmOPL destroy");
+		debug("NfmOPL destroy");
 #endif
 		if (_useBuffer) {
 			// flush
 #if NFM_ENABLE_LOGS
-			atari_debug("OPL flush");
+			debug("OPL flush");
 #endif
 			_oplFlush();
 		}
@@ -285,11 +298,11 @@ OPL::~OPL() {
 
 bool OPL::init() {
 #if NFM_ENABLE_LOGS
-	atari_debug("NfmOPL::RealChip init");
+	debug("NfmOPL::RealChip init");
 #endif
 	if (_incapableDevice) {
 #if NFM_ENABLE_LOGS
-		atari_debug("NfmOPL::RealChip OPL2 device cannot emulate requested dual OPL2 / OPL3!");
+	debug("NfmOPL::RealChip OPL2 device cannot emulate requested dual OPL2 / OPL3!");
 #endif
 		return false;
 	}
@@ -305,7 +318,7 @@ bool OPL::init() {
 
 		if (s_mNfmSpace == 0) {
 #if NFM_ENABLE_LOGS
-			atari_debug("NfmOPL::RealChip create_mspace failed!");
+			debug("NfmOPL::RealChip create_mspace failed!");
 #endif
 			return false;
 		}
@@ -320,7 +333,7 @@ bool OPL::init() {
 		setUserMemoryCallbacks(&s_MemCallbacks);
 	} else {
 #if NFM_ENABLE_LOGS
-		atari_debug("NfmOPL::RealChip Out of system memory!");
+		debug("NfmOPL::RealChip Out of system memory!");
 #endif
 		return false;
 	}
@@ -342,21 +355,21 @@ bool OPL::init() {
 			_initialized = true;
 
 #if NFM_ENABLE_LOGS
-			atari_debug("NfmOPL::RealChip init OK");
+			debug("NfmOPL::RealChip init OK");
 #endif
 			return true;
 		}
 	}
 
 #if NFM_ENABLE_LOGS
-	atari_debug("NfmOPL::RealChip init failed!");
+	debug("NfmOPL::RealChip init failed!");
 #endif
 	return false;
 }
 
 void OPL::reset() {
 #if NFM_ENABLE_LOGS
-	atari_debug("NfmOPL::RealChip reset");
+	debug("NfmOPL::RealChip reset");
 #endif
 	for (int16_t i = 0; i < 256; i ++) {
 		writeReg((int)i, 0);
@@ -393,7 +406,7 @@ void OPL::write(int portAddress, int value) {
 
 void OPL::writeReg(int reg, int value) {
 #if NFM_ENABLE_LOGS
-	atari_debug(s_DebugOplWriteStrs[_deviceType]);
+	debug(s_DebugOplWriteStrs[_deviceType]);
 #endif
 
 	if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
@@ -426,7 +439,7 @@ void OPL::onTimer() {
 		if (_initialized) {
 #if NFM_ENABLE_LOGS
 			// flush
-			atari_debug("NfmOPL::RealChip flush");
+			debug("NfmOPL::RealChip flush");
 #endif
 			_oplFlush();
 		}
@@ -443,8 +456,8 @@ OPL *create(Config::OplType type, OplDevice device) {
 namespace EmulatedChip {
 OPL::OPL(Config::OplType type, enum NfmOPL::OplDevice deviceType): _type(type), _rate(0), _deviceType(deviceType), _activeReg(0), _initialized(false), _useBuffer(NFM_ENABLE_BUFFERED_OUTPUT), _incapableDevice(false) {
 #if NFM_ENABLE_LOGS
-	atari_debug("NfmOPL::EmulatedChip create");
-	atari_debug("Requesting hardware info");
+	debug("NfmOPL::EmulatedChip create");
+	debug("Requesting hardware info");
 #endif
 	Supexec(updateHardwareInfo);
 
@@ -473,7 +486,7 @@ OPL::OPL(Config::OplType type, enum NfmOPL::OplDevice deviceType): _type(type),
 
 #if NFM_ENABLE_LOGS
 	if (deviceType < dtNumDevices) {
-		atari_debug(s_DebugConfigMsgStrs[deviceType]);
+		debug(s_DebugConfigMsgStrs[deviceType]);
 	}
 #endif
 	switch (deviceType) {
@@ -502,14 +515,14 @@ OPL::~OPL() {
 
 	if (_initialized == true) {
 #if NFM_ENABLE_LOGS
-		atari_debug("NfmOPL::EmulatedChip destroy");
+		debug("NfmOPL::EmulatedChip destroy");
 #endif
 		stop();
 
 		if (_useBuffer) {
 #if NFM_ENABLE_LOGS
 			// flush
-			atari_debug("NfmOPL::EmulatedChip OPL flush");
+			debug("NfmOPL::EmulatedChip OPL flush");
 #endif
 			_oplFlush();
 		}
@@ -539,11 +552,11 @@ OPL::~OPL() {
 
 bool OPL::init() {
 #if NFM_ENABLE_LOGS
-	atari_debug("NfmOPL::EmulatedChip init");
+	debug("NfmOPL::EmulatedChip init");
 #endif
 	if (_incapableDevice) {
 #if NFM_ENABLE_LOGS
-		atari_debug("NfmOPL::EmulatedChip device isn't soft synth type!");
+		debug("NfmOPL::EmulatedChip device isn't soft synth type!");
 #endif
 		return false;
 	}
@@ -559,7 +572,7 @@ bool OPL::init() {
 
 		if (s_mNfmSpace == 0) {
 #if NFM_ENABLE_LOGS
-			atari_debug("NfmOPL::EmulatedChip create_mspace failed!");
+			debug("NfmOPL::EmulatedChip create_mspace failed!");
 #endif
 			return false;
 		}
@@ -574,7 +587,7 @@ bool OPL::init() {
 		setUserMemoryCallbacks(&s_MemCallbacks);
 	} else {
 #if NFM_ENABLE_LOGS
-		atari_debug("NfmOPL::EmulatedChip Out of system memory!");
+		debug("NfmOPL::EmulatedChip Out of system memory!");
 #endif
 		return false;
 	}
@@ -600,14 +613,14 @@ bool OPL::init() {
 			_initialized = true;
 
 #if NFM_ENABLE_LOGS
-			atari_debug("NfmOPL::RealChip init OK");
+			debug("NfmOPL::RealChip init OK");
 #endif
 			return true;
 		}
 	}
 
 #if NFM_ENABLE_LOGS
-	atari_debug("NfmOPL::RealChip init failed!");
+	debug("NfmOPL::RealChip init failed!");
 #endif
 	return false;
 }
@@ -651,7 +664,7 @@ void OPL::write(int portAddress, int value) {
 
 void OPL::writeReg(int reg, int value) {
 #if NFM_ENABLE_LOGS
-	atari_debug(s_DebugOplWriteStrs[_deviceType]);
+	debug(s_DebugOplWriteStrs[_deviceType]);
 #endif
 
 	if (_type == Config::kOpl3 || _type == Config::kDualOpl2) {
diff --git a/audio/nfmopl.h b/audio/nfmopl.h
index 43f2cbd299e..73c383e3493 100644
--- a/audio/nfmopl.h
+++ b/audio/nfmopl.h
@@ -50,6 +50,7 @@ enum OplDevice : int16_t {
 	dtOPL3LPT,
 	dtOPL2AudioBoard,
 	dtOPL3Duo,
+	dtStBusIsaVmeSb,
 	dtNatfeatsOpl,
 	dtNukedOpl3,
 	dtNumDevices
diff --git a/backends/platform/atari/get-nfm.sh b/backends/platform/atari/get-nfm.sh
index 831a759d8d8..c4880a9bf3f 100644
--- a/backends/platform/atari/get-nfm.sh
+++ b/backends/platform/atari/get-nfm.sh
@@ -4,9 +4,10 @@
 # -x: Display expanded script commands
 
 # downloads and unpacks in ./deps folder precompiled nFM library release for m68k Atari TOS / mintelf gcc stdlib
-NFM_VERSION=0.2.1
+NFM_VERSION=0.2.2
+COMMIT_SHORT_SHA=4d077f4b
 
-curl -o ./backends/platform/atari/nfm.zip https://nokturnal.pl/downloads/atari/nokturnfm/nfmplay/builds/releases/0.2.1/ataritos-mintelf-gcc-stdlib-m68k-0.2.1-5c688e1a.zip
+curl -o ./backends/platform/atari/nfm.zip https://nokturnal.pl/downloads/atari/nokturnfm/nfmplay/builds/releases/${NFM_VERSION}/ataritos-mintelf-gcc-stdlib-m68k-${NFM_VERSION}-${COMMIT_SHORT_SHA}.zip
 rm -rf ./backends/platform/atari/deps/nfm
 
 unzip ./backends/platform/atari/nfm.zip "nFM_${NFM_VERSION}/*" -d ./backends/platform/atari/deps/
diff --git a/configure b/configure
index 3a98319b250..13090903c20 100755
--- a/configure
+++ b/configure
@@ -5646,12 +5646,12 @@ int main(void)
   return -1;
 }
 EOF
-	cc_check $NFM_CFLAGS -DSC_ENABLE_MALLOC=0 -DNFM_OPL_SHADOW_REG_WRITE_ENABLE=1 $NFM_LIBS -lnfmcore -lnfmutil -ldrvcommon -ldrvnokturnfm -ldrvopl3Duo -ldrvopl3express -ldrvoplxlpt -ldrvoplnull -lsyscore -lcontainers -lallocators &&\
+	cc_check $NFM_CFLAGS -DSC_ENABLE_MALLOC=0 -DNFM_OPL_SHADOW_REG_WRITE_ENABLE=1 $NFM_LIBS -lnfmcore -lnfmutil -ldrvcommon -ldrvnokturnfm -ldrvopl3Duo -ldrvopl3express -ldrvoplxlpt -ldrvopllcart -ldrvisasb -ldrvoplnull -lsyscore -lcontainers -lallocators &&\
 	_nfm=yes
 fi
 if test "$_nfm" = yes; then
 	append_var DEFINES "-DSC_ENABLE_MALLOC=0 -DNFM_OPL_SHADOW_REG_WRITE_ENABLE=1"
-	append_var LIBS "$NFM_LIBS -lnfmcore -lnfmutil -ldrvcommon -ldrvnokturnfm -ldrvopl3Duo -ldrvopl3express -ldrvoplxlpt -ldrvoplnull -lsyscore -lcontainers -lallocators"
+	append_var LIBS "$NFM_LIBS -lnfmcore -lnfmutil -ldrvcommon -ldrvnokturnfm -ldrvopl3Duo -ldrvopl3express -ldrvoplxlpt -ldrvopllcart -ldrvisasb -ldrvoplnull -lsyscore -lcontainers -lallocators"
 	append_var INCLUDES "$NFM_CFLAGS"
 fi
 define_in_config_if_yes "$_nfm" 'USE_NFM'


Commit: d541e51e28d01533e6be30f486449836612eae8b
    https://github.com/scummvm/scummvm/commit/d541e51e28d01533e6be30f486449836612eae8b
Author: Paweł Góralski (20125592+n0kturnal at users.noreply.github.com)
Date: 2026-06-21T15:19:56+02:00

Commit Message:
AUDIO : Atari TOS hardware / software OPL2 / 3 (YM262 / YM3812)

Include debug logging header only when needed

Changed paths:
    audio/nfmopl.cpp


diff --git a/audio/nfmopl.cpp b/audio/nfmopl.cpp
index fb1eec66d7a..018c778dafa 100644
--- a/audio/nfmopl.cpp
+++ b/audio/nfmopl.cpp
@@ -27,7 +27,6 @@
 
 #define FORBIDDEN_SYMBOL_ALLOW_ALL
 #include "common/scummsys.h"
-#include "common/debug.h"
 
 #include "audio/mixer.h"
 #include "audio/fmopl.h"
@@ -41,6 +40,7 @@ extern "C"
 }
 
 #ifndef RELEASE_BUILD
+	#include "common/debug.h"
 	#define NFM_ENABLE_LOGS 1
 #endif
 




More information about the Scummvm-git-logs mailing list