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

View File

@ -32,7 +32,7 @@ static opl_driver_t *drivers[] =
static opl_driver_t *driver = NULL;
unsigned int opl_sample_rate = 22050;
unsigned int opl_sample_rate = OPL_SAMPLE_RATE;
//
// Init/shutdown code.
@ -42,11 +42,11 @@ unsigned int opl_sample_rate = 22050;
// true if an OPL is detected.
static opl_init_result_t InitDriver(opl_driver_t *_driver,
unsigned int port_base)
unsigned int port_base, int num_chips)
{
// Initialize the driver.
if (!_driver->init_func(port_base))
if (!_driver->init_func(port_base, num_chips))
{
return OPL_INIT_NONE;
}
@ -58,21 +58,21 @@ static opl_init_result_t InitDriver(opl_driver_t *_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;
}
// 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;
opl_init_result_t result;
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)
{
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
// 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;
int i;
@ -103,7 +103,7 @@ opl_init_result_t OPL_Init(unsigned int port_base)
{
if (!strcmp(driver_name, drivers[i]->name))
{
result = InitDriver(drivers[i], port_base);
result = InitDriver(drivers[i], port_base, num_chips);
if (result)
{
return result;
@ -123,7 +123,7 @@ opl_init_result_t OPL_Init(unsigned int port_base)
}
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;
}
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)
{
@ -153,11 +153,11 @@ void OPL_WritePort(opl_port_t port, unsigned int value)
printf("OPL_write: %i, %x\n", port, value);
fflush(stdout);
#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)
{
@ -168,7 +168,7 @@ unsigned int OPL_ReadPort(opl_port_t port)
fflush(stdout);
#endif
result = driver->read_port_func(port);
result = driver->read_port_func(chip, port);
#ifdef OPL_DEBUG_TRACE
printf("OPL_read: %i -> %x\n", port, result);
@ -188,24 +188,24 @@ unsigned int OPL_ReadPort(opl_port_t port)
// (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
void OPL_WriteRegister(int reg, int value)
void OPL_WriteRegister(int chip, int reg, int value)
{
int i;
if (reg & 0x100)
{
OPL_WritePort(OPL_REGISTER_PORT_OPL3, reg);
OPL_WritePort(chip, OPL_REGISTER_PORT_OPL3, reg);
}
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
@ -213,17 +213,17 @@ void OPL_WriteRegister(int reg, int value)
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
// cause the appropriate delay
for (i=0; i<24; ++i)
{
OPL_ReadStatus();
OPL_ReadStatus(chip);
}
}
@ -231,13 +231,15 @@ void OPL_WriteRegister(int reg, int value)
void OPL_InitRegisters(int opl3)
{
for (int c = 0; c < num_opl_chips; ++c)
{
int r;
// Initialize level registers
for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r, 0x3f);
OPL_WriteRegister(c, r, 0x3f);
}
// Initialize other registers
@ -247,34 +249,34 @@ void OPL_InitRegisters(int opl3)
for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r, 0x00);
OPL_WriteRegister(c, r, 0x00);
}
// More registers ...
for (r=1; r < OPL_REGS_LEVEL; ++r)
{
OPL_WriteRegister(r, 0x00);
OPL_WriteRegister(c, 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);
OPL_WriteRegister(c, OPL_REG_TIMER_CTRL, 0x60);
OPL_WriteRegister(c, OPL_REG_TIMER_CTRL, 0x80);
// "Allow FM chips to control the waveform of each operator":
OPL_WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20);
OPL_WriteRegister(c, OPL_REG_WAVEFORM_ENABLE, 0x20);
if (opl3)
{
OPL_WriteRegister(OPL_REG_NEW, 0x01);
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(r | 0x100, 0x3f);
OPL_WriteRegister(c, r | 0x100, 0x3f);
}
// Initialize other registers
@ -284,23 +286,24 @@ void OPL_InitRegisters(int opl3)
for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r | 0x100, 0x00);
OPL_WriteRegister(c, r | 0x100, 0x00);
}
// More registers ...
for (r=1; r < OPL_REGS_LEVEL; ++r)
{
OPL_WriteRegister(r | 0x100, 0x00);
OPL_WriteRegister(c, r | 0x100, 0x00);
}
}
// Keyboard split point on (?)
OPL_WriteRegister(OPL_REG_FM_MODE, 0x40);
OPL_WriteRegister(c, OPL_REG_FM_MODE, 0x40);
if (opl3)
{
OPL_WriteRegister(OPL_REG_NEW, 0x01);
OPL_WriteRegister(c, OPL_REG_NEW, 0x01);
}
}
}

View File

@ -38,6 +38,9 @@ typedef enum
OPL_REGISTER_PORT_OPL3 = 2
} opl_port_t;
#define OPL_SAMPLE_RATE 49716
#define OPL_MAX_CHIPS 6
#define OPL_NUM_OPERATORS 21
#define OPL_NUM_VOICES 9
@ -68,13 +71,15 @@ typedef enum
#define OPL_MS ((uint64_t) 1000)
#define OPL_US ((uint64_t) 1)
extern int num_opl_chips;
//
// Low-level functions.
//
// 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.
@ -86,11 +91,11 @@ void OPL_SetSampleRate(unsigned int rate);
// 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:
unsigned int OPL_ReadPort(opl_port_t port);
unsigned int OPL_ReadPort(int chip, opl_port_t port);
//
// Higher-level functions.
@ -98,11 +103,11 @@ unsigned int OPL_ReadPort(opl_port_t port);
// 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.
void OPL_WriteRegister(int reg, int value);
void OPL_WriteRegister(int chip, int reg, int value);
// Initialize all registers, performed on startup.

View File

@ -21,10 +21,10 @@
#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 unsigned int (*opl_read_port_func)(opl_port_t port);
typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value);
typedef unsigned int (*opl_read_port_func)(int chip, opl_port_t port);
typedef void (*opl_write_port_func)(int chip, opl_port_t port, unsigned int value);
typedef void (*opl_set_callback_func)(uint64_t us,
opl_callback_t callback,
void *data);

View File

@ -50,7 +50,7 @@ static uint64_t pause_offset;
// OPL software emulator structure.
static opl3_chip opl_chip;
static opl3_chip opl_chips[OPL_MAX_CHIPS];
static int opl_opl3mode;
// Register number that was written.
@ -137,7 +137,23 @@ int OPL_FillBuffer(byte *buffer, int buffer_samples)
}
// 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;
// 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;
pause_offset = 0;
@ -179,13 +195,14 @@ static int OPL_SDL_Init(unsigned int port_base)
// 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;
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;
@ -194,6 +211,9 @@ static unsigned int OPL_SDL_PortRead(opl_port_t port)
return 0xff;
}
if (chip > 0)
return result;
if (timer1.enabled && current_time > timer1.expire_time)
{
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)
{
case OPL_REG_TIMER1:
// Only allow timers on the first chip
if (chip == 0)
{
timer1.value = value;
OPLTimer_CalculateEndTime(&timer1);
}
break;
case OPL_REG_TIMER2:
// Only allow timers on the first chip
if (chip == 0)
{
timer2.value = value;
OPLTimer_CalculateEndTime(&timer2);
}
break;
case OPL_REG_TIMER_CTRL:
@ -258,19 +286,25 @@ static void WriteRegister(unsigned int reg_num, unsigned int value)
OPLTimer_CalculateEndTime(&timer2);
}
}
break;
case OPL_REG_NEW:
// 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:
OPL3_WriteRegBuffered(&opl_chip, reg_num, value);
OPL3_WriteRegBuffered(&opl_chips[chip], reg_num, value);
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)
{
@ -282,7 +316,7 @@ static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value)
}
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_sound.h"
#include "m_array.h"
#include "m_config.h"
#include "m_io.h"
#include "m_swap.h"
#include "memio.h"
@ -327,9 +328,9 @@ static char (*percussion_names)[32];
// Voices:
static opl_voice_t voices[OPL_NUM_VOICES * 2];
static opl_voice_t *voice_free_list[OPL_NUM_VOICES * 2];
static opl_voice_t *voice_alloced_list[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 * OPL_MAX_CHIPS];
static opl_voice_t *voice_alloced_list[OPL_NUM_VOICES * 2 * OPL_MAX_CHIPS];
static int voice_free_num;
static int voice_alloced_num;
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 int opl_io_port = 0x388;
int num_opl_chips = 1;
// If true, OPL sound channels are reversed to their correct arrangement
// (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
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)
{
int level;
@ -489,11 +491,11 @@ static void LoadOperatorData(int operator, genmidi_op_t * data,
*volume = level;
OPL_WriteRegister(OPL_REGS_LEVEL + operator, level);
OPL_WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo);
OPL_WriteRegister(OPL_REGS_ATTACK + operator, data->attack);
OPL_WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain);
OPL_WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform);
OPL_WriteRegister(chip, OPL_REGS_LEVEL + operator, level);
OPL_WriteRegister(chip, OPL_REGS_TREMOLO + operator, data->tremolo);
OPL_WriteRegister(chip, OPL_REGS_ATTACK + operator, data->attack);
OPL_WriteRegister(chip, OPL_REGS_SUSTAIN + operator, data->sustain);
OPL_WriteRegister(chip, OPL_REGS_WAVEFORM + operator, data->waveform);
}
// 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
// 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);
LoadOperatorData(voice->op1 | voice->array, &data->modulator, !modulating,
LoadOperatorData(voice->array >> 9, voice->op1 | (voice->array & 0x100), &data->modulator, !modulating,
&voice->mod_volume);
// Set feedback register that control the connection between the
// two operators. Turn on bits in the upper nybble; I think this
// 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);
// 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);
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);
// 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)
{
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));
}
}
@ -605,7 +607,7 @@ static void SetVoicePan(opl_voice_t *voice, unsigned int pan)
voice->reg_pan = pan;
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);
}
@ -627,7 +629,7 @@ static void InitVoices(void)
voices[i].index = 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].array = (i / OPL_NUM_VOICES) << 8;
voices[i].array = (i / OPL_NUM_VOICES) << (opl_opl3mode ? 8 : 9);
voices[i].current_instr = NULL;
// Add this voice to the freelist.
@ -666,7 +668,7 @@ static void I_OPL_SetMusicVolume(int volume)
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);
}
@ -883,9 +885,9 @@ static void UpdateVoiceFrequency(opl_voice_t *voice)
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);
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);
voice->freq = freq;
@ -1196,9 +1198,9 @@ static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event)
{
opl_channel_data_t *channel;
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;
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;
// 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;
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)
{
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.
// As an extension, we also allow it to be set from the config file.
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)
{
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)
{
opl_opl3mode = 1;
num_opl_voices = OPL_NUM_VOICES * 2;
num_opl_voices = OPL_NUM_VOICES * 2 * num_opl_chips;
}
else
{
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.
OPL_InitRegisters(opl_opl3mode);
@ -1550,7 +1554,7 @@ static boolean I_OPL_OpenStream(void *data, ALsizei size, ALenum *format,
}
*format = AL_FORMAT_STEREO16;
*freq = SND_SAMPLERATE;
*freq = OPL_SAMPLE_RATE;
*frame_size = 2 * sizeof(short);
return true;
@ -1670,7 +1674,10 @@ static const char **I_OPL_DeviceList(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 =