shithub: choc

Download patch

ref: 7c377fc88a3fbb7f69b91a15dc414c8965624a89
parent: 5082f14944442344030d66f6fbdf86a75a1c1c70
author: Nuke.YKT <alexeytf2@gmail.com>
date: Thu May 28 22:00:45 EDT 2015

Added OPL3 mode support.

--- 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 && result2 == 0xff)
+        {
+            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_new;
 
 // 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_new)
+    {
+        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_new = 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_new = 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_new;
+static int opl_voice_num;
 
 // 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,7 @@
     {
         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 +594,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 +601,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 +624,12 @@
 
     // Initialize each voice.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < opl_voice_num; ++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 +650,7 @@
 
     // Update the volume of all voices.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < opl_voice_num; ++i)
     {
         if (voices[i].channel != NULL)
         {
@@ -636,7 +661,7 @@
 
 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 +894,8 @@
 
     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 +938,8 @@
         voice->note = note;
     }
 
+    voice->reg_pan = channel->pan;
+
     // Program the voice with the instrument data:
 
     SetVoiceInstrument(voice, instrument, instrument_voice);
@@ -1041,7 +1068,7 @@
 
     // Update all voices that this channel is using.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < opl_voice_num; ++i)
     {
         if (voices[i].channel == channel)
         {
@@ -1050,6 +1077,39 @@
     }
 }
 
+static void SetChannelPan(opl_channel_data_t *channel, unsigned int pan)
+{
+    unsigned int reg_pan;
+    unsigned int i;
+
+    if (opl_new)
+    {
+        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 < opl_voice_num; 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 +1168,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 +1199,7 @@
 
     // Update all voices for this channel.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < opl_voice_num; ++i)
     {
         if (voices[i].channel == channel)
         {
@@ -1323,6 +1387,7 @@
 
     channel->instrument = &main_instrs[0];
     channel->volume = 127;
+    channel->pan = 0x30;
     channel->bend = 0;
 }
 
@@ -1397,7 +1462,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 < opl_voice_num; ++i)
     {
         if (voices[i].channel != NULL
          && voices[i].current_instr < percussion_instrs)
@@ -1434,7 +1499,7 @@
 
     // Free all voices.
 
-    for (i = 0; i < OPL_NUM_VOICES; ++i)
+    for (i = 0; i < opl_voice_num; ++i)
     {
         if (voices[i].channel != NULL)
         {
@@ -1581,13 +1646,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_new = 1;
+        opl_voice_num = OPL_NUM_VOICES * 2;
+    }
+    else
+    {
+        opl_new = 0;
+        opl_voice_num = OPL_NUM_VOICES;
+    }
+
+    // Initialize all registers.
+
+    OPL_InitRegisters(opl_new);
 
     // 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
@@ -142,26 +149,37 @@
     // 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)
+    if (snd_musicmode == 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);
+	}
+	else
+	{
+		TXT_InitTable(extra_table, 1);
+        if (snd_musicmode == MUSICMODE_GUS)
+        {
+            TXT_AddWidgets(extra_table,
+                           TXT_NewLabel("GUS patch path:"),
+                           TXT_NewFileSelector(&gus_patch_path, 30,
+                                               "Select path to GUS patches",
+                                               TXT_DIRECTORY),
+                           NULL);
+        }
 
-    if (snd_musicmode == MUSICMODE_NATIVE)
-    {
-        TXT_AddWidgets(extra_table,
-                       TXT_NewLabel("Timidity configuration file:"),
-                       TXT_NewFileSelector(&timidity_cfg_path, 30,
-                                           "Select Timidity config file",
-                                           cfg_extension),
-                       NULL);
+        if (snd_musicmode == MUSICMODE_NATIVE)
+        {
+            TXT_AddWidgets(extra_table,
+                           TXT_NewLabel("Timidity configuration file:"),
+                           TXT_NewFileSelector(&timidity_cfg_path, 30,
+                                               "Select Timidity config file",
+                                               cfg_extension),
+                           NULL);
+        }
     }
 }
 
@@ -324,6 +342,7 @@
 
     M_BindIntVariable("snd_cachesize",            &snd_cachesize);
     M_BindIntVariable("opl_io_port",              &opl_io_port);
+    M_BindIntVariable("opl_type",                 &opl_type);
 
     if (gamemission == strife)
     {