This repository has been archived on 2023-07-11. You can view files and clone it, but cannot push or open issues or pull requests.
chocolate-doom-wii/opl/opl.c
2016-10-30 18:40:00 -04:00

524 lines
11 KiB
C

//
// Copyright(C) 2005-2014 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.
//
// DESCRIPTION:
// OPL interface.
//
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#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 opl_init_result_t InitDriver(opl_driver_t *_driver,
unsigned int port_base)
{
opl_init_result_t result1, result2;
// Initialize the driver.
if (!_driver->init_func(port_base))
{
return OPL_INIT_NONE;
}
// 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;
result1 = OPL_Detect();
result2 = OPL_Detect();
if (result1 == OPL_INIT_NONE || result2 == OPL_INIT_NONE)
{
printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name);
_driver->shutdown_func();
driver = NULL;
return OPL_INIT_NONE;
}
init_stage_reg_writes = 0;
printf("OPL_Init: Using driver '%s'.\n", driver->name);
return result2;
}
// Find a driver automatically by trying each in the list.
static opl_init_result_t AutoSelectDriver(unsigned int port_base)
{
int i;
opl_init_result_t result;
for (i=0; drivers[i] != NULL; ++i)
{
result = InitDriver(drivers[i], port_base);
if (result != OPL_INIT_NONE)
{
return result;
}
}
printf("OPL_Init: Failed to find a working driver.\n");
return OPL_INIT_NONE;
}
// Initialize the OPL library. Return value indicates type of OPL chip
// detected, if any.
opl_init_result_t OPL_Init(unsigned int port_base)
{
char *driver_name;
int i;
int result;
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))
{
result = InitDriver(drivers[i], port_base);
if (result)
{
return result;
}
else
{
printf("OPL_Init: Failed to initialize "
"driver: '%s'.\n", driver_name);
return OPL_INIT_NONE;
}
}
}
printf("OPL_Init: unknown driver: '%s'.\n", driver_name);
return OPL_INIT_NONE;
}
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;
if (reg & 0x100)
{
OPL_WritePort(OPL_REGISTER_PORT_OPL3, reg);
}
else
{
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
opl_init_result_t 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 * OPL_MS);
// Read status
result2 = OPL_ReadStatus();
// Reset both timers:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
// Enable interrupts:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
if ((result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0)
{
result1 = OPL_ReadPort(OPL_REGISTER_PORT);
result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3);
if (result1 == 0x00)
{
return OPL_INIT_OPL3;
}
else
{
return OPL_INIT_OPL2;
}
}
else
{
return OPL_INIT_NONE;
}
}
// Initialize registers on startup
void OPL_InitRegisters(int opl3)
{
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);
if (opl3)
{
OPL_WriteRegister(OPL_REG_NEW, 0x01);
// Initialize level registers
for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r | 0x100, 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 | 0x100, 0x00);
}
// More registers ...
for (r=1; r < OPL_REGS_LEVEL; ++r)
{
OPL_WriteRegister(r | 0x100, 0x00);
}
}
// Keyboard split point on (?)
OPL_WriteRegister(OPL_REG_FM_MODE, 0x40);
if (opl3)
{
OPL_WriteRegister(OPL_REG_NEW, 0x01);
}
}
//
// Timer functions.
//
void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data)
{
if (driver != NULL)
{
driver->set_callback_func(us, 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(uint64_t us)
{
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(us, 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);
}
}
void OPL_AdjustCallbacks(float value)
{
if (driver != NULL)
{
driver->adjust_callbacks_func(value);
}
}