woof/opl/opl.c
2024-05-13 16:43:07 +07:00

335 lines
7.0 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 <stdio.h>
#include <string.h>
#include "m_io.h"
#include "opl.h"
#include "opl_internal.h"
//#define OPL_DEBUG_TRACE
static opl_driver_t *drivers[] =
{
&opl_sdl_driver,
NULL
};
static opl_driver_t *driver = NULL;
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)
{
// 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;
printf("OPL_Init: Using driver '%s'.\n", driver->name);
return OPL_INIT_OPL3;
}
// 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 = M_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)
{
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();
}
}
// 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_AdjustCallbacks(float value)
{
if (driver != NULL)
{
driver->adjust_callbacks_func(value);
}
}