OPL3 Improvements (#1907)

* Use correct OPL3 channel polarity by default

* Support multiple OPL3 chips

* Use native OPL sample rate and allow OpenAL to resample it

* Bind DMX OPL stereo correction to a config variable; default to vanilla

* Consolidate num_opl_chips variables; remove menu item
Use BETWEEN macro for mix clamping

* Disable upper register banks when dmx is in OPL2 mode
This commit is contained in:
jpernst 2024-09-14 22:18:56 -07:00 committed by GitHub
parent a4eb56e4ce
commit 3bee945f2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 170 additions and 121 deletions

137
opl/opl.c
View File

@ -32,7 +32,7 @@ static opl_driver_t *drivers[] =
static opl_driver_t *driver = NULL; static opl_driver_t *driver = NULL;
unsigned int opl_sample_rate = 22050; unsigned int opl_sample_rate = OPL_SAMPLE_RATE;
// //
// Init/shutdown code. // Init/shutdown code.
@ -42,11 +42,11 @@ unsigned int opl_sample_rate = 22050;
// true if an OPL is detected. // true if an OPL is detected.
static opl_init_result_t InitDriver(opl_driver_t *_driver, static opl_init_result_t InitDriver(opl_driver_t *_driver,
unsigned int port_base) unsigned int port_base, int num_chips)
{ {
// Initialize the driver. // Initialize the driver.
if (!_driver->init_func(port_base)) if (!_driver->init_func(port_base, num_chips))
{ {
return OPL_INIT_NONE; return OPL_INIT_NONE;
} }
@ -58,21 +58,21 @@ static opl_init_result_t InitDriver(opl_driver_t *_driver,
driver = _driver; driver = _driver;
printf("OPL_Init: Using driver '%s'.\n", driver->name); printf("OPL_Init: Using driver '%s' with %d chip(s).\n", driver->name, num_opl_chips);
return OPL_INIT_OPL3; return OPL_INIT_OPL3;
} }
// Find a driver automatically by trying each in the list. // Find a driver automatically by trying each in the list.
static opl_init_result_t AutoSelectDriver(unsigned int port_base) static opl_init_result_t AutoSelectDriver(unsigned int port_base, int num_chips)
{ {
int i; int i;
opl_init_result_t result; opl_init_result_t result;
for (i=0; drivers[i] != NULL; ++i) for (i=0; drivers[i] != NULL; ++i)
{ {
result = InitDriver(drivers[i], port_base); result = InitDriver(drivers[i], port_base, num_chips);
if (result != OPL_INIT_NONE) if (result != OPL_INIT_NONE)
{ {
return result; return result;
@ -87,7 +87,7 @@ static opl_init_result_t AutoSelectDriver(unsigned int port_base)
// Initialize the OPL library. Return value indicates type of OPL chip // Initialize the OPL library. Return value indicates type of OPL chip
// detected, if any. // detected, if any.
opl_init_result_t OPL_Init(unsigned int port_base) opl_init_result_t OPL_Init(unsigned int port_base, int num_chips)
{ {
char *driver_name; char *driver_name;
int i; int i;
@ -103,7 +103,7 @@ opl_init_result_t OPL_Init(unsigned int port_base)
{ {
if (!strcmp(driver_name, drivers[i]->name)) if (!strcmp(driver_name, drivers[i]->name))
{ {
result = InitDriver(drivers[i], port_base); result = InitDriver(drivers[i], port_base, num_chips);
if (result) if (result)
{ {
return result; return result;
@ -123,7 +123,7 @@ opl_init_result_t OPL_Init(unsigned int port_base)
} }
else else
{ {
return AutoSelectDriver(port_base); return AutoSelectDriver(port_base, num_chips);
} }
} }
@ -145,7 +145,7 @@ void OPL_SetSampleRate(unsigned int rate)
opl_sample_rate = rate; opl_sample_rate = rate;
} }
void OPL_WritePort(opl_port_t port, unsigned int value) void OPL_WritePort(int chip, opl_port_t port, unsigned int value)
{ {
if (driver != NULL) if (driver != NULL)
{ {
@ -153,11 +153,11 @@ void OPL_WritePort(opl_port_t port, unsigned int value)
printf("OPL_write: %i, %x\n", port, value); printf("OPL_write: %i, %x\n", port, value);
fflush(stdout); fflush(stdout);
#endif #endif
driver->write_port_func(port, value); driver->write_port_func(chip, port, value);
} }
} }
unsigned int OPL_ReadPort(opl_port_t port) unsigned int OPL_ReadPort(int chip, opl_port_t port)
{ {
if (driver != NULL) if (driver != NULL)
{ {
@ -168,7 +168,7 @@ unsigned int OPL_ReadPort(opl_port_t port)
fflush(stdout); fflush(stdout);
#endif #endif
result = driver->read_port_func(port); result = driver->read_port_func(chip, port);
#ifdef OPL_DEBUG_TRACE #ifdef OPL_DEBUG_TRACE
printf("OPL_read: %i -> %x\n", port, result); printf("OPL_read: %i -> %x\n", port, result);
@ -188,24 +188,24 @@ unsigned int OPL_ReadPort(opl_port_t port)
// (register write, etc). // (register write, etc).
// //
unsigned int OPL_ReadStatus(void) unsigned int OPL_ReadStatus(int chip)
{ {
return OPL_ReadPort(OPL_REGISTER_PORT); return OPL_ReadPort(chip, OPL_REGISTER_PORT);
} }
// Write an OPL register value // Write an OPL register value
void OPL_WriteRegister(int reg, int value) void OPL_WriteRegister(int chip, int reg, int value)
{ {
int i; int i;
if (reg & 0x100) if (reg & 0x100)
{ {
OPL_WritePort(OPL_REGISTER_PORT_OPL3, reg); OPL_WritePort(chip, OPL_REGISTER_PORT_OPL3, reg);
} }
else else
{ {
OPL_WritePort(OPL_REGISTER_PORT, reg); OPL_WritePort(chip, OPL_REGISTER_PORT, reg);
} }
// For timing, read the register port six times after writing the // For timing, read the register port six times after writing the
@ -213,17 +213,17 @@ void OPL_WriteRegister(int reg, int value)
for (i=0; i<6; ++i) for (i=0; i<6; ++i)
{ {
OPL_ReadPort(OPL_DATA_PORT); OPL_ReadPort(chip, OPL_DATA_PORT);
} }
OPL_WritePort(OPL_DATA_PORT, value); OPL_WritePort(chip, OPL_DATA_PORT, value);
// Read the register port 24 times after writing the value to // Read the register port 24 times after writing the value to
// cause the appropriate delay // cause the appropriate delay
for (i=0; i<24; ++i) for (i=0; i<24; ++i)
{ {
OPL_ReadStatus(); OPL_ReadStatus(chip);
} }
} }
@ -231,50 +231,15 @@ void OPL_WriteRegister(int reg, int value)
void OPL_InitRegisters(int opl3) void OPL_InitRegisters(int opl3)
{ {
int r; for (int c = 0; c < num_opl_chips; ++c)
// Initialize level registers
for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
{ {
OPL_WriteRegister(r, 0x3f); int r;
}
// 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 // Initialize level registers
for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
{ {
OPL_WriteRegister(r | 0x100, 0x3f); OPL_WriteRegister(c, r, 0x3f);
} }
// Initialize other registers // Initialize other registers
@ -284,23 +249,61 @@ void OPL_InitRegisters(int opl3)
for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
{ {
OPL_WriteRegister(r | 0x100, 0x00); OPL_WriteRegister(c, r, 0x00);
} }
// More registers ... // More registers ...
for (r=1; r < OPL_REGS_LEVEL; ++r) for (r=1; r < OPL_REGS_LEVEL; ++r)
{ {
OPL_WriteRegister(r | 0x100, 0x00); OPL_WriteRegister(c, r, 0x00);
} }
}
// Keyboard split point on (?) // Re-initialize the low registers:
OPL_WriteRegister(OPL_REG_FM_MODE, 0x40);
if (opl3) // Reset both timers and enable interrupts:
{ OPL_WriteRegister(c, OPL_REG_TIMER_CTRL, 0x60);
OPL_WriteRegister(OPL_REG_NEW, 0x01); OPL_WriteRegister(c, OPL_REG_TIMER_CTRL, 0x80);
// "Allow FM chips to control the waveform of each operator":
OPL_WriteRegister(c, OPL_REG_WAVEFORM_ENABLE, 0x20);
if (opl3)
{
OPL_WriteRegister(c, OPL_REG_NEW, 0x01);
// Initialize level registers
for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(c, 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(c, r | 0x100, 0x00);
}
// More registers ...
for (r=1; r < OPL_REGS_LEVEL; ++r)
{
OPL_WriteRegister(c, r | 0x100, 0x00);
}
}
// Keyboard split point on (?)
OPL_WriteRegister(c, OPL_REG_FM_MODE, 0x40);
if (opl3)
{
OPL_WriteRegister(c, OPL_REG_NEW, 0x01);
}
} }
} }

View File

@ -38,6 +38,9 @@ typedef enum
OPL_REGISTER_PORT_OPL3 = 2 OPL_REGISTER_PORT_OPL3 = 2
} opl_port_t; } opl_port_t;
#define OPL_SAMPLE_RATE 49716
#define OPL_MAX_CHIPS 6
#define OPL_NUM_OPERATORS 21 #define OPL_NUM_OPERATORS 21
#define OPL_NUM_VOICES 9 #define OPL_NUM_VOICES 9
@ -68,13 +71,15 @@ typedef enum
#define OPL_MS ((uint64_t) 1000) #define OPL_MS ((uint64_t) 1000)
#define OPL_US ((uint64_t) 1) #define OPL_US ((uint64_t) 1)
extern int num_opl_chips;
// //
// Low-level functions. // Low-level functions.
// //
// Initialize the OPL subsystem. // Initialize the OPL subsystem.
opl_init_result_t OPL_Init(unsigned int port_base); opl_init_result_t OPL_Init(unsigned int port_base, int num_chips);
// Shut down the OPL subsystem. // Shut down the OPL subsystem.
@ -86,11 +91,11 @@ void OPL_SetSampleRate(unsigned int rate);
// Write to one of the OPL I/O ports: // Write to one of the OPL I/O ports:
void OPL_WritePort(opl_port_t port, unsigned int value); void OPL_WritePort(int chip, opl_port_t port, unsigned int value);
// Read from one of the OPL I/O ports: // Read from one of the OPL I/O ports:
unsigned int OPL_ReadPort(opl_port_t port); unsigned int OPL_ReadPort(int chip, opl_port_t port);
// //
// Higher-level functions. // Higher-level functions.
@ -98,11 +103,11 @@ unsigned int OPL_ReadPort(opl_port_t port);
// Read the cuurrent status byte of the OPL chip. // Read the cuurrent status byte of the OPL chip.
unsigned int OPL_ReadStatus(void); unsigned int OPL_ReadStatus(int chip);
// Write to an OPL register. // Write to an OPL register.
void OPL_WriteRegister(int reg, int value); void OPL_WriteRegister(int chip, int reg, int value);
// Initialize all registers, performed on startup. // Initialize all registers, performed on startup.

View File

@ -21,10 +21,10 @@
#include "opl.h" #include "opl.h"
typedef int (*opl_init_func)(unsigned int port_base); typedef int (*opl_init_func)(unsigned int port_base, int num_chips);
typedef void (*opl_shutdown_func)(void); typedef void (*opl_shutdown_func)(void);
typedef unsigned int (*opl_read_port_func)(opl_port_t port); typedef unsigned int (*opl_read_port_func)(int chip, opl_port_t port);
typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value); typedef void (*opl_write_port_func)(int chip, opl_port_t port, unsigned int value);
typedef void (*opl_set_callback_func)(uint64_t us, typedef void (*opl_set_callback_func)(uint64_t us,
opl_callback_t callback, opl_callback_t callback,
void *data); void *data);

View File

@ -50,7 +50,7 @@ static uint64_t pause_offset;
// OPL software emulator structure. // OPL software emulator structure.
static opl3_chip opl_chip; static opl3_chip opl_chips[OPL_MAX_CHIPS];
static int opl_opl3mode; static int opl_opl3mode;
// Register number that was written. // Register number that was written.
@ -137,7 +137,23 @@ int OPL_FillBuffer(byte *buffer, int buffer_samples)
} }
// Add emulator output to buffer. // Add emulator output to buffer.
OPL3_GenerateStream(&opl_chip, (Bit16s *)(buffer + filled * 4), nsamples); Bit16s *cursor = (Bit16s *)(buffer + filled * 4);
for (int s = 0; s < nsamples; ++s)
{
Bit32s mix[2] = {0, 0};
for (int c = 0; c < num_opl_chips; ++c)
{
Bit16s sample[2];
OPL3_GenerateResampled(&opl_chips[c], sample);
mix[0] += sample[0];
mix[1] += sample[1];
}
cursor[0] = BETWEEN(-32768, 32767, mix[0]);
cursor[1] = BETWEEN(-32768, 32767, mix[1]);
cursor += 2;
}
//OPL3_GenerateStream(&opl_chip, (Bit16s *)(buffer + filled * 4), nsamples);
filled += nsamples; filled += nsamples;
// Invoke callbacks for this point in time. // Invoke callbacks for this point in time.
@ -161,7 +177,7 @@ static void OPL_SDL_Shutdown(void)
*/ */
} }
static int OPL_SDL_Init(unsigned int port_base) static int OPL_SDL_Init(unsigned int port_base, int num_chips)
{ {
opl_sdl_paused = 0; opl_sdl_paused = 0;
pause_offset = 0; pause_offset = 0;
@ -179,13 +195,14 @@ static int OPL_SDL_Init(unsigned int port_base)
// Create the emulator structure: // Create the emulator structure:
OPL3_Reset(&opl_chip, mixing_freq); for (int c = 0; c < num_opl_chips; ++c)
OPL3_Reset(&opl_chips[c], mixing_freq);
opl_opl3mode = 0; opl_opl3mode = 0;
return 1; return 1;
} }
static unsigned int OPL_SDL_PortRead(opl_port_t port) static unsigned int OPL_SDL_PortRead(int chip, opl_port_t port)
{ {
unsigned int result = 0; unsigned int result = 0;
@ -194,6 +211,9 @@ static unsigned int OPL_SDL_PortRead(opl_port_t port)
return 0xff; return 0xff;
} }
if (chip > 0)
return result;
if (timer1.enabled && current_time > timer1.expire_time) if (timer1.enabled && current_time > timer1.expire_time)
{ {
result |= 0x80; // Either have expired result |= 0x80; // Either have expired
@ -224,18 +244,26 @@ static void OPLTimer_CalculateEndTime(opl_timer_t *timer)
} }
} }
static void WriteRegister(unsigned int reg_num, unsigned int value) static void WriteRegister(int chip, unsigned int reg_num, unsigned int value)
{ {
switch (reg_num) switch (reg_num)
{ {
case OPL_REG_TIMER1: case OPL_REG_TIMER1:
timer1.value = value; // Only allow timers on the first chip
OPLTimer_CalculateEndTime(&timer1); if (chip == 0)
{
timer1.value = value;
OPLTimer_CalculateEndTime(&timer1);
}
break; break;
case OPL_REG_TIMER2: case OPL_REG_TIMER2:
timer2.value = value; // Only allow timers on the first chip
OPLTimer_CalculateEndTime(&timer2); if (chip == 0)
{
timer2.value = value;
OPLTimer_CalculateEndTime(&timer2);
}
break; break;
case OPL_REG_TIMER_CTRL: case OPL_REG_TIMER_CTRL:
@ -258,19 +286,25 @@ static void WriteRegister(unsigned int reg_num, unsigned int value)
OPLTimer_CalculateEndTime(&timer2); OPLTimer_CalculateEndTime(&timer2);
} }
} }
break; break;
case OPL_REG_NEW: case OPL_REG_NEW:
opl_opl3mode = value & 0x01; // Keep all chips synchronized with the first chip's opl3 mode
if (chip == 0)
{
for (int c = 0; c < num_opl_chips; ++c)
OPL3_WriteRegBuffered(&opl_chips[c], reg_num, value);
opl_opl3mode = value & 0x01;
}
break;
default: default:
OPL3_WriteRegBuffered(&opl_chip, reg_num, value); OPL3_WriteRegBuffered(&opl_chips[chip], reg_num, value);
break; break;
} }
} }
static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value) static void OPL_SDL_PortWrite(int chip, opl_port_t port, unsigned int value)
{ {
if (port == OPL_REGISTER_PORT) if (port == OPL_REGISTER_PORT)
{ {
@ -282,7 +316,7 @@ static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value)
} }
else if (port == OPL_DATA_PORT) else if (port == OPL_DATA_PORT)
{ {
WriteRegister(register_num, value); WriteRegister(chip, register_num, value);
} }
} }

View File

@ -26,6 +26,7 @@
#include "i_printf.h" #include "i_printf.h"
#include "i_sound.h" #include "i_sound.h"
#include "m_array.h" #include "m_array.h"
#include "m_config.h"
#include "m_io.h" #include "m_io.h"
#include "m_swap.h" #include "m_swap.h"
#include "memio.h" #include "memio.h"
@ -327,9 +328,9 @@ static char (*percussion_names)[32];
// Voices: // Voices:
static opl_voice_t voices[OPL_NUM_VOICES * 2]; static opl_voice_t voices[OPL_NUM_VOICES * 2 * OPL_MAX_CHIPS];
static opl_voice_t *voice_free_list[OPL_NUM_VOICES * 2]; static opl_voice_t *voice_free_list[OPL_NUM_VOICES * 2 * OPL_MAX_CHIPS];
static opl_voice_t *voice_alloced_list[OPL_NUM_VOICES * 2]; static opl_voice_t *voice_alloced_list[OPL_NUM_VOICES * 2 * OPL_MAX_CHIPS];
static int voice_free_num; static int voice_free_num;
static int voice_alloced_num; static int voice_alloced_num;
static int opl_opl3mode; static int opl_opl3mode;
@ -361,6 +362,7 @@ static unsigned int last_perc_count;
static char *snd_dmxoption = "-opl3"; // [crispy] default to OPL3 emulation static char *snd_dmxoption = "-opl3"; // [crispy] default to OPL3 emulation
static int opl_io_port = 0x388; static int opl_io_port = 0x388;
int num_opl_chips = 1;
// If true, OPL sound channels are reversed to their correct arrangement // If true, OPL sound channels are reversed to their correct arrangement
// (as intended by the MIDI standard) rather than the backwards one // (as intended by the MIDI standard) rather than the backwards one
@ -468,7 +470,7 @@ static void ReleaseVoice(int index)
// Load data to the specified operator // Load data to the specified operator
static void LoadOperatorData(int operator, genmidi_op_t * data, static void LoadOperatorData(int chip, int operator, genmidi_op_t * data,
boolean max_level, unsigned int *volume) boolean max_level, unsigned int *volume)
{ {
int level; int level;
@ -489,11 +491,11 @@ static void LoadOperatorData(int operator, genmidi_op_t * data,
*volume = level; *volume = level;
OPL_WriteRegister(OPL_REGS_LEVEL + operator, level); OPL_WriteRegister(chip, OPL_REGS_LEVEL + operator, level);
OPL_WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo); OPL_WriteRegister(chip, OPL_REGS_TREMOLO + operator, data->tremolo);
OPL_WriteRegister(OPL_REGS_ATTACK + operator, data->attack); OPL_WriteRegister(chip, OPL_REGS_ATTACK + operator, data->attack);
OPL_WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain); OPL_WriteRegister(chip, OPL_REGS_SUSTAIN + operator, data->sustain);
OPL_WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform); OPL_WriteRegister(chip, OPL_REGS_WAVEFORM + operator, data->waveform);
} }
// Set the instrument for a particular voice. // Set the instrument for a particular voice.
@ -526,16 +528,16 @@ static void SetVoiceInstrument(opl_voice_t *voice, genmidi_instr_t *instr,
// is set in SetVoiceVolume (below). If we are not using // is set in SetVoiceVolume (below). If we are not using
// modulating mode, we must set both to minimum volume. // modulating mode, we must set both to minimum volume.
LoadOperatorData(voice->op2 | voice->array, &data->carrier, true, LoadOperatorData(voice->array >> 9, voice->op2 | (voice->array & 0x100), &data->carrier, true,
&voice->car_volume); &voice->car_volume);
LoadOperatorData(voice->op1 | voice->array, &data->modulator, !modulating, LoadOperatorData(voice->array >> 9, voice->op1 | (voice->array & 0x100), &data->modulator, !modulating,
&voice->mod_volume); &voice->mod_volume);
// Set feedback register that control the connection between the // Set feedback register that control the connection between the
// two operators. Turn on bits in the upper nybble; I think this // two operators. Turn on bits in the upper nybble; I think this
// is for OPL3, where it turns on channel A/B. // is for OPL3, where it turns on channel A/B.
OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array, OPL_WriteRegister(voice->array >> 9, (OPL_REGS_FEEDBACK + voice->index) | (voice->array & 0x100),
data->feedback | voice->reg_pan); data->feedback | voice->reg_pan);
// Calculate voice priority. // Calculate voice priority.
@ -571,7 +573,7 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume)
{ {
voice->car_volume = car_volume | (voice->car_volume & 0xc0); voice->car_volume = car_volume | (voice->car_volume & 0xc0);
OPL_WriteRegister((OPL_REGS_LEVEL + voice->op2) | voice->array, OPL_WriteRegister(voice->array >> 9, (OPL_REGS_LEVEL + voice->op2) | (voice->array & 0x100),
voice->car_volume); voice->car_volume);
// If we are using non-modulated feedback mode, we must set the // If we are using non-modulated feedback mode, we must set the
@ -591,7 +593,7 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume)
if (mod_volume != voice->mod_volume) if (mod_volume != voice->mod_volume)
{ {
voice->mod_volume = mod_volume; voice->mod_volume = mod_volume;
OPL_WriteRegister((OPL_REGS_LEVEL + voice->op1) | voice->array, OPL_WriteRegister(voice->array >> 9, (OPL_REGS_LEVEL + voice->op1) | (voice->array & 0x100),
mod_volume | (opl_voice->modulator.scale & 0xc0)); mod_volume | (opl_voice->modulator.scale & 0xc0));
} }
} }
@ -605,7 +607,7 @@ static void SetVoicePan(opl_voice_t *voice, unsigned int pan)
voice->reg_pan = pan; voice->reg_pan = pan;
opl_voice = &voice->current_instr->voices[voice->current_instr_voice]; opl_voice = &voice->current_instr->voices[voice->current_instr_voice];
OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array, OPL_WriteRegister(voice->array >> 9, (OPL_REGS_FEEDBACK + voice->index) | (voice->array & 0x100),
opl_voice->feedback | pan); opl_voice->feedback | pan);
} }
@ -627,7 +629,7 @@ static void InitVoices(void)
voices[i].index = i % OPL_NUM_VOICES; voices[i].index = i % OPL_NUM_VOICES;
voices[i].op1 = voice_operators[0][i % OPL_NUM_VOICES]; voices[i].op1 = voice_operators[0][i % OPL_NUM_VOICES];
voices[i].op2 = voice_operators[1][i % OPL_NUM_VOICES]; voices[i].op2 = voice_operators[1][i % OPL_NUM_VOICES];
voices[i].array = (i / OPL_NUM_VOICES) << 8; voices[i].array = (i / OPL_NUM_VOICES) << (opl_opl3mode ? 8 : 9);
voices[i].current_instr = NULL; voices[i].current_instr = NULL;
// Add this voice to the freelist. // Add this voice to the freelist.
@ -666,7 +668,7 @@ static void I_OPL_SetMusicVolume(int volume)
static void VoiceKeyOff(opl_voice_t *voice) static void VoiceKeyOff(opl_voice_t *voice)
{ {
OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array, OPL_WriteRegister(voice->array >> 9, (OPL_REGS_FREQ_2 + voice->index) | (voice->array & 0x100),
voice->freq >> 8); voice->freq >> 8);
} }
@ -883,9 +885,9 @@ static void UpdateVoiceFrequency(opl_voice_t *voice)
if (voice->freq != freq) if (voice->freq != freq)
{ {
OPL_WriteRegister((OPL_REGS_FREQ_1 + voice->index) | voice->array, OPL_WriteRegister(voice->array >> 9, (OPL_REGS_FREQ_1 + voice->index) | (voice->array & 0x100),
freq & 0xff); freq & 0xff);
OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array, OPL_WriteRegister(voice->array >> 9, (OPL_REGS_FREQ_2 + voice->index) | (voice->array & 0x100),
(freq >> 8) | 0x20); (freq >> 8) | 0x20);
voice->freq = freq; voice->freq = freq;
@ -1196,9 +1198,9 @@ static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event)
{ {
opl_channel_data_t *channel; opl_channel_data_t *channel;
int i; int i;
opl_voice_t *voice_updated_list[OPL_NUM_VOICES * 2] = {0}; opl_voice_t *voice_updated_list[OPL_NUM_VOICES * 2 * OPL_MAX_CHIPS] = {0};
unsigned int voice_updated_num = 0; unsigned int voice_updated_num = 0;
opl_voice_t *voice_not_updated_list[OPL_NUM_VOICES * 2] = {0}; opl_voice_t *voice_not_updated_list[OPL_NUM_VOICES * 2 * OPL_MAX_CHIPS] = {0};
unsigned int voice_not_updated_num = 0; unsigned int voice_not_updated_num = 0;
// Update the channel bend value. Only the MSB of the pitch bend // Update the channel bend value. Only the MSB of the pitch bend
@ -1445,9 +1447,9 @@ static boolean I_OPL_InitStream(int device)
char *dmxoption; char *dmxoption;
opl_init_result_t chip_type; opl_init_result_t chip_type;
OPL_SetSampleRate(SND_SAMPLERATE); OPL_SetSampleRate(OPL_SAMPLE_RATE);
chip_type = OPL_Init(opl_io_port); chip_type = OPL_Init(opl_io_port, num_opl_chips);
if (chip_type == OPL_INIT_NONE) if (chip_type == OPL_INIT_NONE)
{ {
I_Printf(VB_ERROR, "Dude. The Adlib isn't responding."); I_Printf(VB_ERROR, "Dude. The Adlib isn't responding.");
@ -1457,6 +1459,12 @@ static boolean I_OPL_InitStream(int device)
// The DMXOPTION variable must be set to enable OPL3 support. // The DMXOPTION variable must be set to enable OPL3 support.
// As an extension, we also allow it to be set from the config file. // As an extension, we also allow it to be set from the config file.
dmxoption = M_getenv("DMXOPTION"); dmxoption = M_getenv("DMXOPTION");
// Secret, undocumented DMXOPTION that reverses the stereo channels
// into their correct orientation.
if (dmxoption != NULL && strstr(dmxoption, "-reverse") != NULL)
opl_stereo_correct = true;
if (dmxoption == NULL) if (dmxoption == NULL)
{ {
dmxoption = snd_dmxoption != NULL ? snd_dmxoption : ""; dmxoption = snd_dmxoption != NULL ? snd_dmxoption : "";
@ -1465,18 +1473,14 @@ static boolean I_OPL_InitStream(int device)
if (chip_type == OPL_INIT_OPL3 && strstr(dmxoption, "-opl3") != NULL) if (chip_type == OPL_INIT_OPL3 && strstr(dmxoption, "-opl3") != NULL)
{ {
opl_opl3mode = 1; opl_opl3mode = 1;
num_opl_voices = OPL_NUM_VOICES * 2; num_opl_voices = OPL_NUM_VOICES * 2 * num_opl_chips;
} }
else else
{ {
opl_opl3mode = 0; opl_opl3mode = 0;
num_opl_voices = OPL_NUM_VOICES; num_opl_voices = OPL_NUM_VOICES * num_opl_chips;
} }
// Secret, undocumented DMXOPTION that reverses the stereo channels
// into their correct orientation.
opl_stereo_correct = strstr(dmxoption, "-reverse") != NULL;
// Initialize all registers. // Initialize all registers.
OPL_InitRegisters(opl_opl3mode); OPL_InitRegisters(opl_opl3mode);
@ -1550,7 +1554,7 @@ static boolean I_OPL_OpenStream(void *data, ALsizei size, ALenum *format,
} }
*format = AL_FORMAT_STEREO16; *format = AL_FORMAT_STEREO16;
*freq = SND_SAMPLERATE; *freq = OPL_SAMPLE_RATE;
*frame_size = 2 * sizeof(short); *frame_size = 2 * sizeof(short);
return true; return true;
@ -1670,7 +1674,10 @@ static const char **I_OPL_DeviceList(void)
static void I_OPL_BindVariables(void) static void I_OPL_BindVariables(void)
{ {
; BIND_NUM(num_opl_chips, 1, 1, OPL_MAX_CHIPS,
"[OPL3 Emulation] Number of chips to emulate (1-6)");
BIND_BOOL(opl_stereo_correct, false,
"[OPL3 Emulation] Use MIDI-correct stereo channel polarity");
} }
stream_module_t stream_opl_module = stream_module_t stream_opl_module =