shithub: choc

Download patch

ref: 0e90c19ca717298f4f3b83dfd075cdf0c458de32
parent: 5082f14944442344030d66f6fbdf86a75a1c1c70
parent: 1e516e34911d3a95479c303fe26b59f20e618252
author: Simon Howard <fraggle+github@gmail.com>
date: Sat May 30 09:04:56 EDT 2015

Merge pull request #545 from khokh2001/opl3_mode

opl: Add OPL3 mode.

The DMX library had limited support for the features of the OPL3 chip,
enabled by setting the DMXOPTIONS variable. This reproduces the OPL3 
support in Chocolate Doom's OPL playback and emulation layer.

Huge thanks to Alexey Khokholov for researching and developing this. 
This fixes #470.

--- a/opl/opl.c
+++ b/opl/opl.c
@@ -67,6 +67,8 @@
 
 static int InitDriver(opl_driver_t *_driver, unsigned int port_base)
 {
+    int result1, result2;
+
     // Initialize the driver.
 
     if (!_driver->init_func(port_base))
@@ -82,7 +84,9 @@
     driver = _driver;
     init_stage_reg_writes = 1;
 
-    if (!OPL_Detect() || !OPL_Detect())
+    result1 = OPL_Detect();
+    result2 = OPL_Detect();
+    if (!result1 || !result2)
     {
         printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name);
         _driver->shutdown_func();
@@ -90,15 +94,11 @@
         return 0;
     }
 
-    // Initialize all registers.
-
-    OPL_InitRegisters();
-
     init_stage_reg_writes = 0;
 
     printf("OPL_Init: Using driver '%s'.\n", driver->name);
 
-    return 1;
+    return result2;
 }
 
 // Find a driver automatically by trying each in the list.
@@ -106,12 +106,14 @@
 static int AutoSelectDriver(unsigned int port_base)
 {
     int i;
+    int result;
 
     for (i=0; drivers[i] != NULL; ++i)
     {
-        if (InitDriver(drivers[i], port_base))
+        result = InitDriver(drivers[i], port_base);
+        if (result)
         {
-            return 1;
+            return result;
         }
     }
 
@@ -127,6 +129,7 @@
 {
     char *driver_name;
     int i;
+    int result;
 
     driver_name = getenv("OPL_DRIVER");
 
@@ -138,9 +141,10 @@
         {
             if (!strcmp(driver_name, drivers[i]->name))
             {
-                if (InitDriver(drivers[i], port_base))
+                result = InitDriver(drivers[i], port_base);
+                if (result)
                 {
-                    return 1;
+                    return result;
                 }
                 else
                 {
@@ -233,7 +237,14 @@
 {
     int i;
 
-    OPL_WritePort(OPL_REGISTER_PORT, reg);
+    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
@@ -306,13 +317,22 @@
     // Enable interrupts:
     OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
 
-    return (result1 & 0xe0) == 0x00
-        && (result2 & 0xe0) == 0xc0;
+    if ((result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0)
+    {
+        result1 = OPL_ReadPort(OPL_REGISTER_PORT);
+        result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3);
+        if (result1 == 0x00)
+        {
+            return 2;
+        }
+        return 1;
+    }
+    return 0;
 }
 
 // Initialize registers on startup
 
-void OPL_InitRegisters(void)
+void OPL_InitRegisters(int opl3)
 {
     int r;
 
@@ -349,8 +369,42 @@
     // "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);
+    }
 }
 
 //
--- a/opl/opl.h
+++ b/opl/opl.h
@@ -26,7 +26,8 @@
 typedef enum
 {
     OPL_REGISTER_PORT = 0,
-    OPL_DATA_PORT = 1
+    OPL_DATA_PORT = 1,
+    OPL_REGISTER_PORT_OPL3 = 2
 } opl_port_t;
 
 #define OPL_NUM_OPERATORS   21
@@ -37,6 +38,7 @@
 #define OPL_REG_TIMER2            0x03
 #define OPL_REG_TIMER_CTRL        0x04
 #define OPL_REG_FM_MODE           0x08
+#define OPL_REG_NEW               0x105
 
 // Operator registers (21 of each):
 
@@ -101,7 +103,7 @@
 
 // Initialize all registers, performed on startup.
 
-void OPL_InitRegisters(void);
+void OPL_InitRegisters(int opl3);
 
 //
 // Timer callback functions.
--- a/opl/opl_sdl.c
+++ b/opl/opl_sdl.c
@@ -71,6 +71,7 @@
 // OPL software emulator structure.
 
 static Chip opl_chip;
+static int opl_opl3mode;
 
 // Temporary mixing buffer used by the mixing callback.
 
@@ -164,15 +165,30 @@
 
     assert(nsamples < mixing_freq);
 
-    Chip__GenerateBlock2(&opl_chip, nsamples, mix_buffer);
+    if (opl_opl3mode)
+    {
+        Chip__GenerateBlock3(&opl_chip, nsamples, mix_buffer);
 
-    // Mix into the destination buffer, doubling up into stereo.
+        // Mix into the destination buffer, doubling up into stereo.
 
-    for (i=0; i<nsamples; ++i)
-    {
-        buffer[i * 2] = (int16_t) mix_buffer[i];
-        buffer[i * 2 + 1] = (int16_t) mix_buffer[i];
+        for (i=0; i<nsamples; ++i)
+        {
+            buffer[i * 2] = (int16_t) mix_buffer[i * 2];
+            buffer[i * 2 + 1] = (int16_t) mix_buffer[i * 2 + 1];
+        }
     }
+    else
+    {
+        Chip__GenerateBlock2(&opl_chip, nsamples, mix_buffer);
+
+        // Mix into the destination buffer, doubling up into stereo.
+
+        for (i=0; i<nsamples; ++i)
+        {
+            buffer[i * 2] = (int16_t) mix_buffer[i];
+            buffer[i * 2 + 1] = (int16_t) mix_buffer[i];
+        }
+	}
 }
 
 // Callback function to fill a new sound buffer:
@@ -351,7 +367,7 @@
 
     // Mix buffer:
 
-    mix_buffer = malloc(mixing_freq * sizeof(uint32_t));
+    mix_buffer = malloc(mixing_freq * sizeof(uint32_t) * 2);
 
     // Create the emulator structure:
 
@@ -358,6 +374,7 @@
     DBOPL_InitTables();
     Chip__Chip(&opl_chip);
     Chip__Setup(&opl_chip, mixing_freq);
+    opl_opl3mode = 0;
 
     callback_mutex = SDL_CreateMutex();
     callback_queue_mutex = SDL_CreateMutex();
@@ -372,6 +389,11 @@
 {
     unsigned int result = 0;
 
+    if (port == OPL_REGISTER_PORT_OPL3)
+    {
+        return 0xff;
+    }
+
     if (timer1.enabled && current_time > timer1.expire_time)
     {
         result |= 0x80;   // Either have expired
@@ -439,6 +461,9 @@
 
             break;
 
+        case OPL_REG_NEW:
+            opl_opl3mode = value & 0x01;
+
         default:
             Chip__WriteReg(&opl_chip, reg_num, value);
             break;
@@ -450,6 +475,10 @@
     if (port == OPL_REGISTER_PORT)
     {
         register_num = value;
+    }
+    else if (port == OPL_REGISTER_PORT_OPL3)
+    {
+        register_num = value | 0x100;
     }
     else if (port == OPL_DATA_PORT)
     {
--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@ -86,6 +86,10 @@
 
     int volume;
 
+    // Pan
+
+    int pan;
+
     // Pitch bend value:
 
     int bend;
@@ -115,6 +119,9 @@
     // The operators used by this voice:
     int op1, op2;
 
+    // Array used by voice:
+    int array;
+
     // Currently-loaded instrument data
     genmidi_instr_t *current_instr;
 
@@ -143,6 +150,9 @@
     // The current volume (register value) that has been set for this channel.
     unsigned int reg_volume;
 
+    // Pan.
+    unsigned int reg_pan;
+
     // Priority.
     unsigned int priority;
 
@@ -312,10 +322,12 @@
 
 // Voices:
 
-static opl_voice_t voices[OPL_NUM_VOICES];
+static opl_voice_t voices[OPL_NUM_VOICES * 2];
 static opl_voice_t *voice_free_list;
 static opl_voice_t *voice_alloced_list;
 static int voice_alloced_num;
+static int opl_opl3mode;
+static int num_opl_voices;
 
 // Track data for playing tracks:
 
@@ -338,6 +350,7 @@
 // adlib chip.
 
 int opl_io_port = 0x388;
+int opl_type = 0;
 
 // Load instrument table from GENMIDI lump:
 
@@ -519,15 +532,15 @@
     // is set in SetVoiceVolume (below).  If we are not using
     // modulating mode, we must set both to minimum volume.
 
-    LoadOperatorData(voice->op2, &data->carrier, true);
-    LoadOperatorData(voice->op1, &data->modulator, !modulating);
+    LoadOperatorData(voice->op2 | voice->array, &data->carrier, true);
+    LoadOperatorData(voice->op1 | voice->array, &data->modulator, !modulating);
 
     // 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,
-                      data->feedback | 0x30);
+    OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array,
+                      data->feedback | voice->reg_pan);
 
     // Hack to force a volume update.
 
@@ -568,7 +581,8 @@
     {
         voice->reg_volume = car_volume | (opl_voice->carrier.scale & 0xc0);
 
-        OPL_WriteRegister(OPL_REGS_LEVEL + voice->op2, voice->reg_volume);
+        OPL_WriteRegister((OPL_REGS_LEVEL + voice->op2) | voice->array,
+                          voice->reg_volume);
 
         // If we are using non-modulated feedback mode, we must set the
         // volume for both voices.
@@ -581,7 +595,7 @@
             {
                 mod_volume = car_volume;
             }
-            OPL_WriteRegister(OPL_REGS_LEVEL + voice->op1,
+            OPL_WriteRegister((OPL_REGS_LEVEL + voice->op1) | voice->array,
                               mod_volume |
                               (opl_voice->modulator.scale & 0xc0));
         }
@@ -588,6 +602,17 @@
     }
 }
 
+static void SetVoicePan(opl_voice_t *voice, unsigned int pan)
+{
+    genmidi_voice_t *opl_voice;
+
+    voice->reg_pan = pan;
+    opl_voice = &voice->current_instr->voices[voice->current_instr_voice];;
+
+    OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array,
+                      opl_voice->feedback | pan);
+}
+
 // Initialize the voice table and freelist
 
 static void InitVoices(void)
@@ -600,11 +625,12 @@
 
     // Initialize each voice.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < num_opl_voices; ++i)
     {
-        voices[i].index = i;
-        voices[i].op1 = voice_operators[0][i];
-        voices[i].op2 = voice_operators[1][i];
+        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].current_instr = NULL;
 
         // Add this voice to the freelist.
@@ -625,7 +651,7 @@
 
     // Update the volume of all voices.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < num_opl_voices; ++i)
     {
         if (voices[i].channel != NULL)
         {
@@ -636,7 +662,8 @@
 
 static void VoiceKeyOff(opl_voice_t *voice)
 {
-    OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, voice->freq >> 8);
+    OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array,
+                      voice->freq >> 8);
 }
 
 static opl_channel_data_t *TrackChannelForEvent(opl_track_data_t *track,
@@ -869,8 +896,10 @@
 
     if (voice->freq != freq)
     {
-        OPL_WriteRegister(OPL_REGS_FREQ_1 + voice->index, freq & 0xff);
-        OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, (freq >> 8) | 0x20);
+        OPL_WriteRegister((OPL_REGS_FREQ_1 + voice->index) | voice->array,
+                          freq & 0xff);
+        OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array,
+                          (freq >> 8) | 0x20);
 
         voice->freq = freq;
     }
@@ -913,6 +942,8 @@
         voice->note = note;
     }
 
+    voice->reg_pan = channel->pan;
+
     // Program the voice with the instrument data:
 
     SetVoiceInstrument(voice, instrument, instrument_voice);
@@ -980,11 +1011,11 @@
 
     if (opl_drv_ver == opl_v_old)
     {
-        if (voice_alloced_num == OPL_NUM_VOICES)
+        if (voice_alloced_num == num_opl_voices)
         {
             ReplaceExistingVoiceOld(channel);
         }
-        if (voice_alloced_num == OPL_NUM_VOICES - 1 && double_voice)
+        if (voice_alloced_num == num_opl_voices - 1 && double_voice)
         {
             ReplaceExistingVoiceOld(channel);
         }
@@ -1041,7 +1072,7 @@
 
     // Update all voices that this channel is using.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < num_opl_voices; ++i)
     {
         if (voices[i].channel == channel)
         {
@@ -1050,6 +1081,39 @@
     }
 }
 
+static void SetChannelPan(opl_channel_data_t *channel, unsigned int pan)
+{
+    unsigned int reg_pan;
+    unsigned int i;
+
+    if (opl_opl3mode)
+    {
+        if (pan >= 96)
+        {
+            reg_pan = 0x10;
+        }
+        else if (pan <= 48)
+        {
+            reg_pan = 0x20;
+        }
+        else
+        {
+            reg_pan = 0x30;
+        }
+        if (channel->pan != reg_pan)
+        {
+            channel->pan = reg_pan;
+            for (i = 0; i < num_opl_voices; i++)
+            {
+                if (voices[i].channel == channel)
+                {
+                    SetVoicePan(&voices[i], reg_pan);
+                }
+            }
+        }
+    }
+}
+
 // Handler for the MIDI_CONTROLLER_ALL_NOTES_OFF channel event.
 static void AllNotesOff(opl_channel_data_t *channel, unsigned int param)
 {
@@ -1108,6 +1172,10 @@
             SetChannelVolume(channel, param);
             break;
 
+        case MIDI_CONTROLLER_PAN:
+            SetChannelPan(channel, param);
+            break;
+
         case MIDI_CONTROLLER_ALL_NOTES_OFF:
             AllNotesOff(channel, param);
             break;
@@ -1135,7 +1203,7 @@
 
     // Update all voices for this channel.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < num_opl_voices; ++i)
     {
         if (voices[i].channel == channel)
         {
@@ -1323,6 +1391,7 @@
 
     channel->instrument = &main_instrs[0];
     channel->volume = 127;
+    channel->pan = 0x30;
     channel->bend = 0;
 }
 
@@ -1397,7 +1466,7 @@
     // Turn off all main instrument voices (not percussion).
     // This is what Vanilla does.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < num_opl_voices; ++i)
     {
         if (voices[i].channel != NULL
          && voices[i].current_instr < percussion_instrs)
@@ -1434,7 +1503,7 @@
 
     // Free all voices.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < num_opl_voices; ++i)
     {
         if (voices[i].channel != NULL)
         {
@@ -1581,13 +1650,31 @@
 
 static boolean I_OPL_InitMusic(void)
 {
+    int opl_chip_type;
+
     OPL_SetSampleRate(snd_samplerate);
 
-    if (!OPL_Init(opl_io_port))
+    opl_chip_type = OPL_Init(opl_io_port);
+    if (!opl_chip_type)
     {
         printf("Dude.  The Adlib isn't responding.\n");
         return false;
     }
+
+    if (opl_chip_type == 2 && opl_type)
+    {
+        opl_opl3mode = 1;
+        num_opl_voices = OPL_NUM_VOICES * 2;
+    }
+    else
+    {
+        opl_opl3mode = 0;
+        num_opl_voices = OPL_NUM_VOICES;
+    }
+
+    // Initialize all registers.
+
+    OPL_InitRegisters(opl_opl3mode);
 
     // Load instruments from GENMIDI lump:
 
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@ -68,6 +68,7 @@
 
 extern opl_driver_ver_t opl_drv_ver;
 extern int opl_io_port;
+extern int opl_type;
 
 // For native music module:
 
@@ -446,6 +447,7 @@
     M_BindIntVariable("snd_samplerate",          &snd_samplerate);
     M_BindIntVariable("snd_cachesize",           &snd_cachesize);
     M_BindIntVariable("opl_io_port",             &opl_io_port);
+    M_BindIntVariable("opl_type",                &opl_type);
 
     M_BindStringVariable("timidity_cfg_path",    &timidity_cfg_path);
     M_BindStringVariable("gus_patch_path",       &gus_patch_path);
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -824,6 +824,11 @@
     CONFIG_VARIABLE_INT_HEX(opl_io_port),
 
     //!
+    // OPL chip type.
+    //
+    CONFIG_VARIABLE_INT(opl_type),
+
+    //!
     // @game doom heretic strife
     //
     // If non-zero, the ENDOOM text screen is displayed when exiting the
--- a/src/setup/sound.c
+++ b/src/setup/sound.c
@@ -61,6 +61,12 @@
     "CD audio"
 };
 
+static char *opltype_strings[] =
+{
+    "OPL2",
+    "OPL3"
+};
+
 static char *cfg_extension[] = { "cfg", NULL };
 
 // Config file variables:
@@ -84,6 +90,7 @@
 static char *timidity_cfg_path = NULL;
 static char *gus_patch_path = NULL;
 static int gus_ram_kb = 1024;
+static int opl_type = 0;
 
 // DOS specific variables: these are unused but should be maintained
 // so that the config file can be shared between chocolate
@@ -139,29 +146,36 @@
 {
     TXT_CAST_ARG(txt_table_t, extra_table);
 
-    // Rebuild the GUS table. Start by emptying it, then only add the
-    // GUS control widget if we are in GUS music mode.
-
-    TXT_ClearTable(extra_table);
-
-    if (snd_musicmode == MUSICMODE_GUS)
+    switch (snd_musicmode)
     {
+    case MUSICMODE_OPL:
+        TXT_InitTable(extra_table, 2);
+        TXT_SetColumnWidths(extra_table, 19, 4);
         TXT_AddWidgets(extra_table,
-                       TXT_NewLabel("GUS patch path:"),
-                       TXT_NewFileSelector(&gus_patch_path, 30,
-                                           "Select path to GUS patches",
-                                           TXT_DIRECTORY),
-                       NULL);
-    }
+                        TXT_NewLabel("OPL type"),
+                        TXT_NewDropdownList(&opl_type, opltype_strings, 2),
+                        NULL);
+        break;
 
-    if (snd_musicmode == MUSICMODE_NATIVE)
-    {
+    case MUSICMODE_GUS:
+        TXT_InitTable(extra_table, 1);
         TXT_AddWidgets(extra_table,
-                       TXT_NewLabel("Timidity configuration file:"),
-                       TXT_NewFileSelector(&timidity_cfg_path, 30,
-                                           "Select Timidity config file",
-                                           cfg_extension),
-                       NULL);
+                        TXT_NewLabel("GUS patch path:"),
+                        TXT_NewFileSelector(&gus_patch_path, 30,
+                                            "Select path to GUS patches",
+                                            TXT_DIRECTORY),
+                        NULL);
+        break;
+
+    case MUSICMODE_NATIVE:
+        TXT_InitTable(extra_table, 1);
+        TXT_AddWidgets(extra_table,
+                        TXT_NewLabel("Timidity configuration file:"),
+                        TXT_NewFileSelector(&timidity_cfg_path, 30,
+                                            "Select Timidity config file",
+                                            cfg_extension),
+                        NULL);
+        break;
     }
 }
 
@@ -324,6 +338,7 @@
 
     M_BindIntVariable("snd_cachesize",            &snd_cachesize);
     M_BindIntVariable("opl_io_port",              &opl_io_port);
+    M_BindIntVariable("opl_type",                 &opl_type);
 
     if (gamemission == strife)
     {