From 5c3a07985882cc165210a0dc1842da5c92e408d9 Mon Sep 17 00:00:00 2001
From: David Gow <david@ingeniumdigital.com>
Date: Sun, 17 Oct 2021 14:18:32 +0800
Subject: [PATCH] Add support for real OPL2 hardware access via ALSA/liboplhw

This change adds a new ./configure option, --enable-oplhw, which
compiles in a new OPL "emulator", called "oplhw", which uses real OPL2
hardware on Linux, if a driver is available.

This requires the liboplhw library, available here:
https://davidgow.net/hacks/oplhw.html
https://github.com/sulix/liboplhw

To enable it, edit your dosbox.conf, and in the [sblaster] section, set:
oplmode=opl2
oplemu=oplhw
opldev=[device]

(Where [device] is the ALSA hwdep device for your soundcard, often
something like hw:0,0 -- check /proc/asound/hwdep to find the correct
numbers.)
---
 configure.ac              | 10 ++++++
 src/dosbox.cpp            |  5 ++-
 src/hardware/Makefile.am  |  2 +-
 src/hardware/adlib.cpp    |  9 +++++
 src/hardware/alsa_opl.cpp | 72 +++++++++++++++++++++++++++++++++++++++
 src/hardware/alsa_opl.h   | 45 ++++++++++++++++++++++++
 6 files changed, 141 insertions(+), 2 deletions(-)
 create mode 100644 src/hardware/alsa_opl.cpp
 create mode 100644 src/hardware/alsa_opl.h

diff --git a/configure.ac b/configure.ac
index 8932074..eab5c36 100644
--- a/configure.ac
+++ b/configure.ac
@@ -202,6 +202,16 @@ if test x$alsa_midi = xtrue ; then
   CXXFLAGS="$CXXFLAGS $ALSA_CFLAGS"
 fi
 
+dnl enable disable oplhw
+AH_TEMPLATE(C_ALSA_OPL,[Define to 1 to enable ALSA OPL2 support via liboplhw])
+AC_ARG_ENABLE([oplhw], AS_HELP_STRING([--enable-oplhw], [Support real OPL2 cards via ALSA with liboplhw]))
+if test x$enable_oplhw = xyes; then
+  PKG_CHECK_MODULES(OPLHW, [oplhw])
+  CXXFLAGS="$CXXFLAGS $OPLHW_CFLAGS"
+  LIBS="$LIBS $OPLHW_LIBS"
+  AC_DEFINE(C_ALSA_OPL,1)
+fi
+
 #Check for big endian machine, should #define WORDS_BIGENDIAN if so
 AC_C_BIGENDIAN
 
diff --git a/src/dosbox.cpp b/src/dosbox.cpp
index df040f0..ffa8a16 100644
--- a/src/dosbox.cpp
+++ b/src/dosbox.cpp
@@ -602,11 +602,14 @@ void DOSBOX_Init(void) {
 	Pstring->Set_values(oplmodes);
 	Pstring->Set_help("Type of OPL emulation. On 'auto' the mode is determined by sblaster type. All OPL modes are Adlib-compatible, except for 'cms'.");
 
-	const char* oplemus[]={ "default", "compat", "fast", "mame", 0};
+	const char* oplemus[]={ "default", "compat", "fast", "mame", "oplhw", 0};
 	Pstring = secprop->Add_string("oplemu",Property::Changeable::WhenIdle,"default");
 	Pstring->Set_values(oplemus);
 	Pstring->Set_help("Provider for the OPL emulation. compat might provide better quality (see oplrate as well).");
 
+	Pstring = secprop->Add_string("opldevice",Property::Changeable::WhenIdle,"");
+	Pstring->Set_help("ALSA device to use for real OPL2 passthrough (e.g. hw:0,0).");
+
 	Pint = secprop->Add_int("oplrate",Property::Changeable::WhenIdle,44100);
 	Pint->Set_values(oplrates);
 	Pint->Set_help("Sample rate of OPL music emulation. Use 49716 for highest quality (set the mixer rate accordingly).");
diff --git a/src/hardware/Makefile.am b/src/hardware/Makefile.am
index 2d73948..0999d4b 100644
--- a/src/hardware/Makefile.am
+++ b/src/hardware/Makefile.am
@@ -10,6 +10,6 @@ libhardware_a_SOURCES = adlib.cpp dma.cpp gameblaster.cpp hardware.cpp iohandler
                         memory.cpp mixer.cpp pcspeaker.cpp pci_bus.cpp pic.cpp sblaster.cpp tandy_sound.cpp timer.cpp \
 			vga.cpp vga_attr.cpp vga_crtc.cpp vga_dac.cpp vga_draw.cpp vga_gfx.cpp vga_other.cpp \
 			vga_memory.cpp vga_misc.cpp vga_seq.cpp vga_xga.cpp vga_s3.cpp vga_tseng.cpp vga_paradise.cpp \
-			cmos.cpp disney.cpp gus.cpp mpu401.cpp ipx.cpp ipxserver.cpp dbopl.cpp
+			cmos.cpp disney.cpp gus.cpp mpu401.cpp ipx.cpp ipxserver.cpp dbopl.cpp alsa_opl.cpp
 
 
diff --git a/src/hardware/adlib.cpp b/src/hardware/adlib.cpp
index de56dcc..1442909 100644
--- a/src/hardware/adlib.cpp
+++ b/src/hardware/adlib.cpp
@@ -32,6 +32,8 @@
 #include "mame/fmopl.h"
 #include "mame/ymf262.h"
 
+#include "alsa_opl.h"
+
 #define OPL2_INTERNAL_FREQ    3600000   // The OPL2 operates at 3.6MHz
 #define OPL3_INTERNAL_FREQ    14400000  // The OPL3 operates at 14.4MHz
 
@@ -806,6 +808,13 @@ Module::Module( Section* configuration ) : Module_base(configuration) {
 		else {
 			handler = new MAMEOPL3::Handler();
 		}
+#ifdef C_ALSA_OPL
+	} else if (oplemu == "oplhw") {
+		std::string device(section->Get_string("opldevice"));
+		if (oplmode != OPL_opl2)
+			LOG_MSG("WARNING: Using hardware OPL2, but oplmode is not opl2\n");
+		handler = new ALSA_OPL::Handler(device);
+#endif
 	} else {
 		handler = new DBOPL::Handler();
 	}
diff --git a/src/hardware/alsa_opl.cpp b/src/hardware/alsa_opl.cpp
new file mode 100644
index 0000000..3e4db71
--- /dev/null
+++ b/src/hardware/alsa_opl.cpp
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (C) 2002-2017  The DOSBox Team
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <math.h>
+#include <unistd.h>
+
+#include "adlib.h"
+#include "dosbox.h"
+#include "cpu.h"
+#include "alsa_opl.h"
+
+#if C_ALSA_OPL
+#include "oplhw.h"
+
+namespace ALSA_OPL {
+
+	void Handler::WriteReg(Bit32u reg, Bit8u val) {
+#if C_DEBUG
+		LOG_MSG("OPL2LPT: cycles %" PRId32 ", on reg 0x%" PRIx32 ", write 0x%" PRIx8,
+			CPU_Cycles, reg, val);
+#endif
+		if (!dev)
+			return;
+		oplhw_Write(dev, next_port & 0xff, val);
+	}
+
+	Bit32u Handler::WriteAddr(Bit32u port, Bit8u val) {
+#if C_DEBUG
+		LOG_MSG("ALSA_OPL: cycles %" PRId32 ", on port 0x%" PRIx32 ", write 0x%" PRIx8,
+			CPU_Cycles, port, val);
+#endif
+		next_port = val;
+		return val;
+	}
+
+	void Handler::Generate(MixerChannel* chan, Bitu samples) {
+		/* No need to generate sound */
+		chan->enabled = false;
+		return;
+	}
+
+	void Handler::Init(Bitu rate) {
+	}
+
+	Handler::Handler(std::string name) {
+		LOG_MSG("ALSA_OPL: opening device \"%s\"\n", name.c_str());
+		dev = oplhw_OpenDevice(name.c_str());
+	}
+
+	Handler::~Handler() {
+		if (dev)
+			oplhw_CloseDevice(dev);
+	}
+
+};
+
+#endif
diff --git a/src/hardware/alsa_opl.h b/src/hardware/alsa_opl.h
new file mode 100644
index 0000000..4b6b06a
--- /dev/null
+++ b/src/hardware/alsa_opl.h
@@ -0,0 +1,45 @@
+/* -*- c++ -*- */
+/*
+ *  Copyright (C) 2002-2017  The DOSBox Team
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#if C_ALSA_OPL
+
+#include "adlib.h"
+#include "dosbox.h"
+#include <oplhw.h>
+
+namespace ALSA_OPL {
+
+	struct Handler : public Adlib::Handler {
+	private:
+		oplhw_device *dev;
+		Bit8u next_port;
+
+	public:
+		virtual Bit32u WriteAddr( Bit32u port, Bit8u val );
+		virtual void WriteReg( Bit32u addr, Bit8u val );
+		virtual void Generate( MixerChannel* chan, Bitu samples );
+		virtual void Init( Bitu rate );
+		int WriteThread();
+		explicit Handler(std::string name);
+		~Handler();
+	};
+
+};		//Namespace
+
+#endif
-- 
2.32.0

