shithub: choc

Download patch

ref: ca7f823d48bb78eda9048750909cdb315836125c
parent: 3efee3d5e2b662df97aabc4c8fd275b60b6f08f4
author: Simon Howard <fraggle@gmail.com>
date: Tue Mar 10 17:21:16 EDT 2009

Initialise OPL registers on startup, initialise voices.

Subversion-branch: /branches/opl-branch
Subversion-revision: 1457

--- a/opl/opl.h
+++ b/opl/opl.h
@@ -36,6 +36,7 @@
 #define OPL_NUM_OPERATORS   21
 #define OPL_NUM_VOICES      9
 
+#define OPL_REG_WAVEFORM_ENABLE   0x01
 #define OPL_REG_TIMER1            0x02
 #define OPL_REG_TIMER2            0x03
 #define OPL_REG_TIMER_CTRL        0x04
@@ -47,11 +48,13 @@
 #define OPL_REGS_LEVEL            0x40
 #define OPL_REGS_ATTACK           0x60
 #define OPL_REGS_SUSTAIN          0x80
+#define OPL_REGS_WAVEFORM         0xE0
 
 // Voice registers (9 of each):
 
 #define OPL_REGS_FREQ_1           0xA0
 #define OPL_REGS_FREQ_2           0xB0
+#define OPL_REGS_FEEDBACK         0xC0
 
 // Initialise the OPL subsystem.
 
--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@ -77,14 +77,45 @@
     genmidi_voice_t opl3_voice;
 } PACKEDATTR genmidi_instr_t;
 
+typedef struct opl_voice_s opl_voice_t;
+
+struct opl_voice_s
+{
+    // Index of this voice:
+    int index;
+
+    // The operators used by this voice:
+    int op1, op2;
+
+    // Currently-loaded instrument data
+    genmidi_instr_t *current_instr;
+
+    // Next in freelist
+    opl_voice_t *next;
+};
+
+// Operators used by the different voices.
+
+static const int voice_operators[2][OPL_NUM_VOICES] = {
+    { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12 },
+    { 0x03, 0x04, 0x05, 0x0b, 0x0c, 0x0d, 0x13, 0x14, 0x15 }
+};
+
 static boolean music_initialised = false;
 
 //static boolean musicpaused = false;
 static int current_music_volume;
 
+// GENMIDI lump instrument data:
+
 static genmidi_instr_t *main_instrs;
 static genmidi_instr_t *percussion_instrs;
 
+// Voices:
+
+static opl_voice_t voices[OPL_NUM_VOICES];
+static opl_voice_t *voice_free_list;
+
 // Configuration file variable, containing the port number for the
 // adlib chip.
 
@@ -158,7 +189,48 @@
         && (result2 & 0xe0) == 0xc0;
 }
 
+// Initialise registers on startup
 
+static void InitRegisters(void)
+{
+    int r;
+
+    // Initialise level registers
+
+    for (r=OPL_REGS_LEVEL; r < OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
+    {
+        WriteRegister(r, 0x3f);
+    }
+
+    // Initialise other registers
+    // These two loops write to registers that actually don't exist,
+    // but this is what Doom does ...
+
+    for (r=OPL_REGS_ATTACK; r < OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
+    {
+        WriteRegister(r, 0x00);
+    }
+
+    // More registers ...
+
+    for (r=0; r < OPL_REGS_LEVEL; ++r)
+    {
+        WriteRegister(r, 0x00);
+    }
+
+    // Re-initialise the low registers:
+
+    // Reset both timers and enable interrupts:
+    WriteRegister(OPL_REG_TIMER_CTRL,      0x60);
+    WriteRegister(OPL_REG_TIMER_CTRL,      0x80);
+
+    // "Allow FM chips to control the waveform of each operator":
+    WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20);
+
+    // Keyboard split point on (?)
+    WriteRegister(OPL_REG_FM_MODE,         0x40);
+}
+
 // Load instrument table from GENMIDI lump:
 
 static boolean LoadInstrumentTable(void)
@@ -182,6 +254,109 @@
     return true;
 }
 
+// Get the next available voice from the freelist.
+
+static opl_voice_t *GetFreeVoice(void)
+{
+    opl_voice_t *result;
+
+    // None available?
+
+    if (voice_free_list == NULL)
+    {
+        return NULL;
+    }
+
+    result = voice_free_list;
+    voice_free_list = voice_free_list->next;
+
+    return result;
+}
+
+// Release a voice back to the freelist.
+
+static void ReleaseVoice(opl_voice_t *voice)
+{
+    opl_voice_t **rover;
+
+    // Search to the end of the freelist (This is how Doom behaves!)
+
+    rover = &voice_free_list;
+
+    while (*rover != NULL)
+    {
+        rover = &(*rover)->next;
+    }
+
+    *rover = voice;
+}
+
+// Load data to the specified operator
+
+static void LoadOperatorData(int operator, genmidi_op_t *data,
+                             boolean max_level)
+{
+    int level;
+
+    // The scale and level fields must be combined for the level register.
+    // For the carrier wave we always set the maximum level.
+
+    level = (data->scale & 0xc0) | (data->level & 0x3f);
+
+    if (max_level)
+    {
+        level |= 0x3f;
+    }
+
+    WriteRegister(OPL_REGS_LEVEL + operator, level);
+    WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo);
+    WriteRegister(OPL_REGS_ATTACK + operator, data->attack);
+    WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain);
+    WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform);
+}
+
+// Set the instrument for a particular voice.
+
+static void SetVoiceInstrument(opl_voice_t *voice, genmidi_voice_t *data)
+{
+    // Doom loads the second operator first, then the first.
+
+    LoadOperatorData(voice->op2, &data->carrier, true);
+    LoadOperatorData(voice->op1, &data->modulator, false);
+
+    // 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.
+
+    WriteRegister(OPL_REGS_FEEDBACK + voice->index,
+                  data->feedback | 0x30);
+}
+
+// Initialise the voice table and freelist
+
+static void InitVoices(void)
+{
+    int i;
+
+    // Start with an empty free list.
+
+    voice_free_list = NULL;
+
+    // Initialise each voice.
+
+    for (i=0; i<OPL_NUM_VOICES; ++i)
+    {
+        voices[i].index = i;
+        voices[i].op1 = voice_operators[0][i];
+        voices[i].op2 = voice_operators[1][i];
+        voices[i].current_instr = NULL;
+
+        // Add this voice to the freelist.
+
+        ReleaseVoice(&voices[i]);
+    }
+}
+
 // Shutdown music
 
 static void I_OPL_ShutdownMusic(void)
@@ -223,6 +398,9 @@
         OPL_Shutdown();
         return false;
     }
+
+    InitRegisters();
+    InitVoices();
 
     music_initialised = true;