shithub: choc

Download patch

ref: a6a747e0ab96c45abe1a563f278f3ebc05fdec55
parent: cc0cc9fb0c806f6533dca794b1e829a626323d8b
author: Simon Howard <fraggle@gmail.com>
date: Sat Sep 12 13:22:58 EDT 2009

Discard an existing voice when no voices are available (based on
research with Vanilla).

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

--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@ -154,7 +154,8 @@
     unsigned int carrier_volume;
     unsigned int modulator_volume;
 
-    // Next in freelist
+    // Next in linked list; a voice is always either in the
+    // free list or the allocated list.
     opl_voice_t *next;
 };
 
@@ -318,6 +319,7 @@
 
 static opl_voice_t voices[OPL_NUM_VOICES];
 static opl_voice_t *voice_free_list;
+static opl_voice_t *voice_alloced_list;
 
 // Track data for playing tracks:
 
@@ -505,12 +507,42 @@
         return NULL;
     }
 
+    // Remove from free list
+
     result = voice_free_list;
     voice_free_list = voice_free_list->next;
 
+    // Add to allocated list
+
+    result->next = voice_alloced_list;
+    voice_alloced_list = result;
+
     return result;
 }
 
+// Remove a voice from the allocated voices list.
+
+static void RemoveVoiceFromAllocedList(opl_voice_t *voice)
+{
+    opl_voice_t **rover;
+
+    rover = &voice_alloced_list;
+
+    // Search the list until we find the voice, then remove it.
+
+    while (*rover != NULL)
+    {
+        if (*rover == voice)
+        {
+            *rover = voice->next;
+            voice->next = NULL;
+            break;
+        }
+
+        rover = &(*rover)->next;
+    }
+}
+
 // Release a voice back to the freelist.
 
 static void ReleaseVoice(opl_voice_t *voice)
@@ -520,6 +552,10 @@
     voice->channel = NULL;
     voice->note = 0;
 
+    // Remove from alloced list.
+
+    RemoveVoiceFromAllocedList(voice);
+
     // Search to the end of the freelist (This is how Doom behaves!)
 
     rover = &voice_free_list;
@@ -831,6 +867,66 @@
     }
 }
 
+// When all voices are in use, we must discard an existing voice to
+// play a new note.  Find and free an existing voice.  The channel
+// passed to the function is the channel for the new note to be
+// played.
+
+static opl_voice_t *ReplaceExistingVoice(opl_channel_data_t *channel)
+{
+    opl_voice_t *rover;
+    opl_voice_t *result;
+
+    // Check the allocated voices, if we find an instrument that is
+    // of a lower priority to the new instrument, discard it.
+    // Priority is determined by MIDI instrument number; old
+
+    result = NULL;
+
+    for (rover = voice_alloced_list; rover != NULL; rover = rover->next)
+    {
+        if (rover->current_instr > channel->instrument)
+        {
+            result = rover;
+            break;
+        }
+    }
+
+    // If we didn't find a voice, find an existing voice being used to
+    // play a note on the same channel, and use that.
+
+    if (result == NULL)
+    {
+        for (rover = voice_alloced_list; rover != NULL; rover = rover->next)
+        {
+            if (rover->channel == channel)
+            {
+                result = rover;
+                break;
+            }
+        }
+    }
+
+    // Still nothing found?  Give up and just use the first voice in
+    // the list.
+
+    if (result == NULL)
+    {
+        result = voice_alloced_list;
+    }
+
+    // Stop playing this voice playing and release it back to the free
+    // list.
+
+    VoiceKeyOff(result);
+    ReleaseVoice(result);
+
+    // Re-allocate the voice again and return it.
+
+    return GetFreeVoice();
+}
+
+
 static unsigned int FrequencyForVoice(opl_voice_t *voice)
 {
     unsigned int freq_index;
@@ -899,10 +995,21 @@
 
     voice = GetFreeVoice();
 
+    // If there are no more voices left, we must decide what to do.
+    // If this is the first voice of the instrument, free an existing
+    // voice and use that.  Otherwise, if this is the second voice,
+    // it isn't as important; just discard it.
+
     if (voice == NULL)
     {
-        printf("\tno free voice for voice %i of instrument\n", instrument_voice);
-        return;
+        if (instrument_voice == 0)
+        {
+            voice = ReplaceExistingVoice(channel);
+        }
+        else
+        {
+            return;
+        }
     }
 
     voice->channel = channel;