summaryrefslogtreecommitdiff
path: root/opl/opl.c
diff options
context:
space:
mode:
Diffstat (limited to 'opl/opl.c')
-rw-r--r--opl/opl.c466
1 files changed, 466 insertions, 0 deletions
diff --git a/opl/opl.c b/opl/opl.c
new file mode 100644
index 00000000..6d0e16db
--- /dev/null
+++ b/opl/opl.c
@@ -0,0 +1,466 @@
+// Emacs style mode select -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2009 Simon Howard
+//
+// 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.
+//
+// DESCRIPTION:
+// OPL interface.
+//
+//-----------------------------------------------------------------------------
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef _WIN32_WCE
+#include "libc_wince.h"
+#endif
+
+#include "SDL.h"
+
+#include "opl.h"
+#include "opl_internal.h"
+
+//#define OPL_DEBUG_TRACE
+
+#ifdef HAVE_IOPERM
+extern opl_driver_t opl_linux_driver;
+#endif
+#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
+extern opl_driver_t opl_openbsd_driver;
+#endif
+#ifdef _WIN32
+extern opl_driver_t opl_win32_driver;
+#endif
+extern opl_driver_t opl_sdl_driver;
+
+static opl_driver_t *drivers[] =
+{
+#ifdef HAVE_IOPERM
+ &opl_linux_driver,
+#endif
+#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
+ &opl_openbsd_driver,
+#endif
+#ifdef _WIN32
+ &opl_win32_driver,
+#endif
+ &opl_sdl_driver,
+ NULL
+};
+
+static opl_driver_t *driver = NULL;
+static int init_stage_reg_writes = 1;
+
+unsigned int opl_sample_rate = 22050;
+
+//
+// Init/shutdown code.
+//
+
+// Initialize the specified driver and detect an OPL chip. Returns
+// true if an OPL is detected.
+
+static int InitDriver(opl_driver_t *_driver, unsigned int port_base)
+{
+ // Initialize the driver.
+
+ if (!_driver->init_func(port_base))
+ {
+ return 0;
+ }
+
+ // The driver was initialized okay, so we now have somewhere
+ // to write to. It doesn't mean there's an OPL chip there,
+ // though. Perform the detection sequence to make sure.
+ // (it's done twice, like how Doom does it).
+
+ driver = _driver;
+ init_stage_reg_writes = 1;
+
+ if (!OPL_Detect() || !OPL_Detect())
+ {
+ printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name);
+ _driver->shutdown_func();
+ driver = NULL;
+ return 0;
+ }
+
+ // Initialize all registers.
+
+ OPL_InitRegisters();
+
+ init_stage_reg_writes = 0;
+
+ printf("OPL_Init: Using driver '%s'.\n", driver->name);
+
+ return 1;
+}
+
+// Find a driver automatically by trying each in the list.
+
+static int AutoSelectDriver(unsigned int port_base)
+{
+ int i;
+
+ for (i=0; drivers[i] != NULL; ++i)
+ {
+ if (InitDriver(drivers[i], port_base))
+ {
+ return 1;
+ }
+ }
+
+ printf("OPL_Init: Failed to find a working driver.\n");
+
+ return 0;
+}
+
+// Initialize the OPL library. Returns true if initialized
+// successfully.
+
+int OPL_Init(unsigned int port_base)
+{
+ char *driver_name;
+ int i;
+
+ driver_name = getenv("OPL_DRIVER");
+
+ if (driver_name != NULL)
+ {
+ // Search the list until we find the driver with this name.
+
+ for (i=0; drivers[i] != NULL; ++i)
+ {
+ if (!strcmp(driver_name, drivers[i]->name))
+ {
+ if (InitDriver(drivers[i], port_base))
+ {
+ return 1;
+ }
+ else
+ {
+ printf("OPL_Init: Failed to initialize "
+ "driver: '%s'.\n", driver_name);
+ return 0;
+ }
+ }
+ }
+
+ printf("OPL_Init: unknown driver: '%s'.\n", driver_name);
+
+ return 0;
+ }
+ else
+ {
+ return AutoSelectDriver(port_base);
+ }
+}
+
+// Shut down the OPL library.
+
+void OPL_Shutdown(void)
+{
+ if (driver != NULL)
+ {
+ driver->shutdown_func();
+ driver = NULL;
+ }
+}
+
+// Set the sample rate used for software OPL emulation.
+
+void OPL_SetSampleRate(unsigned int rate)
+{
+ opl_sample_rate = rate;
+}
+
+void OPL_WritePort(opl_port_t port, unsigned int value)
+{
+ if (driver != NULL)
+ {
+#ifdef OPL_DEBUG_TRACE
+ printf("OPL_write: %i, %x\n", port, value);
+ fflush(stdout);
+#endif
+ driver->write_port_func(port, value);
+ }
+}
+
+unsigned int OPL_ReadPort(opl_port_t port)
+{
+ if (driver != NULL)
+ {
+ unsigned int result;
+
+#ifdef OPL_DEBUG_TRACE
+ printf("OPL_read: %i...\n", port);
+ fflush(stdout);
+#endif
+
+ result = driver->read_port_func(port);
+
+#ifdef OPL_DEBUG_TRACE
+ printf("OPL_read: %i -> %x\n", port, result);
+ fflush(stdout);
+#endif
+
+ return result;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+//
+// Higher-level functions, based on the lower-level functions above
+// (register write, etc).
+//
+
+unsigned int OPL_ReadStatus(void)
+{
+ return OPL_ReadPort(OPL_REGISTER_PORT);
+}
+
+// Write an OPL register value
+
+void OPL_WriteRegister(int reg, int value)
+{
+ int i;
+
+ OPL_WritePort(OPL_REGISTER_PORT, reg);
+
+ // For timing, read the register port six times after writing the
+ // register number to cause the appropriate delay
+
+ for (i=0; i<6; ++i)
+ {
+ // An oddity of the Doom OPL code: at startup initialization,
+ // the spacing here is performed by reading from the register
+ // port; after initialization, the data port is read, instead.
+
+ if (init_stage_reg_writes)
+ {
+ OPL_ReadPort(OPL_REGISTER_PORT);
+ }
+ else
+ {
+ OPL_ReadPort(OPL_DATA_PORT);
+ }
+ }
+
+ OPL_WritePort(OPL_DATA_PORT, value);
+
+ // Read the register port 24 times after writing the value to
+ // cause the appropriate delay
+
+ for (i=0; i<24; ++i)
+ {
+ OPL_ReadStatus();
+ }
+}
+
+// Detect the presence of an OPL chip
+
+int OPL_Detect(void)
+{
+ int result1, result2;
+ int i;
+
+ // Reset both timers:
+ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
+
+ // Enable interrupts:
+ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
+
+ // Read status
+ result1 = OPL_ReadStatus();
+
+ // Set timer:
+ OPL_WriteRegister(OPL_REG_TIMER1, 0xff);
+
+ // Start timer 1:
+ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x21);
+
+ // Wait for 80 microseconds
+ // This is how Doom does it:
+
+ for (i=0; i<200; ++i)
+ {
+ OPL_ReadStatus();
+ }
+
+ OPL_Delay(1);
+
+ // Read status
+ result2 = OPL_ReadStatus();
+
+ // Reset both timers:
+ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
+
+ // Enable interrupts:
+ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
+
+ return (result1 & 0xe0) == 0x00
+ && (result2 & 0xe0) == 0xc0;
+}
+
+// Initialize registers on startup
+
+void OPL_InitRegisters(void)
+{
+ int r;
+
+ // Initialize level registers
+
+ for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
+ {
+ OPL_WriteRegister(r, 0x3f);
+ }
+
+ // Initialize other registers
+ // These two loops write to registers that actually don't exist,
+ // but this is what Doom does ...
+ // Similarly, the <= is also intenational.
+
+ for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
+ {
+ OPL_WriteRegister(r, 0x00);
+ }
+
+ // More registers ...
+
+ for (r=1; r < OPL_REGS_LEVEL; ++r)
+ {
+ OPL_WriteRegister(r, 0x00);
+ }
+
+ // Re-initialize the low registers:
+
+ // Reset both timers and enable interrupts:
+ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
+ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
+
+ // "Allow FM chips to control the waveform of each operator":
+ OPL_WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20);
+
+ // Keyboard split point on (?)
+ OPL_WriteRegister(OPL_REG_FM_MODE, 0x40);
+}
+
+//
+// Timer functions.
+//
+
+void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data)
+{
+ if (driver != NULL)
+ {
+ driver->set_callback_func(ms, callback, data);
+ }
+}
+
+void OPL_ClearCallbacks(void)
+{
+ if (driver != NULL)
+ {
+ driver->clear_callbacks_func();
+ }
+}
+
+void OPL_Lock(void)
+{
+ if (driver != NULL)
+ {
+ driver->lock_func();
+ }
+}
+
+void OPL_Unlock(void)
+{
+ if (driver != NULL)
+ {
+ driver->unlock_func();
+ }
+}
+
+typedef struct
+{
+ int finished;
+
+ SDL_mutex *mutex;
+ SDL_cond *cond;
+} delay_data_t;
+
+static void DelayCallback(void *_delay_data)
+{
+ delay_data_t *delay_data = _delay_data;
+
+ SDL_LockMutex(delay_data->mutex);
+ delay_data->finished = 1;
+
+ SDL_CondSignal(delay_data->cond);
+
+ SDL_UnlockMutex(delay_data->mutex);
+}
+
+void OPL_Delay(unsigned int ms)
+{
+ delay_data_t delay_data;
+
+ if (driver == NULL)
+ {
+ return;
+ }
+
+ // Create a callback that will signal this thread after the
+ // specified time.
+
+ delay_data.finished = 0;
+ delay_data.mutex = SDL_CreateMutex();
+ delay_data.cond = SDL_CreateCond();
+
+ OPL_SetCallback(ms, DelayCallback, &delay_data);
+
+ // Wait until the callback is invoked.
+
+ SDL_LockMutex(delay_data.mutex);
+
+ while (!delay_data.finished)
+ {
+ SDL_CondWait(delay_data.cond, delay_data.mutex);
+ }
+
+ SDL_UnlockMutex(delay_data.mutex);
+
+ // Clean up.
+
+ SDL_DestroyMutex(delay_data.mutex);
+ SDL_DestroyCond(delay_data.cond);
+}
+
+void OPL_SetPaused(int paused)
+{
+ if (driver != NULL)
+ {
+ driver->set_paused_func(paused);
+ }
+}
+