shithub: choc

Download patch

ref: 578fd9aafdee97bc95cb81811fdc63ca17dd2850
parent: e2e1069332289a714ebebc71db225510c6d8ebff
author: Simon Howard <fraggle@gmail.com>
date: Sun Mar 15 10:44:23 EDT 2009

Fix clipped sounds when using libsamplerate (thanks David Flater)

Subversion-branch: /trunk/chocolate-doom
Subversion-revision: 1479

--- a/src/i_sdlsound.c
+++ b/src/i_sdlsound.c
@@ -31,6 +31,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <assert.h>
+#include <math.h>
 #include "SDL.h"
 #include "SDL_mixer.h"
 
@@ -58,11 +59,12 @@
 static int mixer_freq;
 static Uint16 mixer_format;
 static int mixer_channels;
-static uint32_t (*ExpandSoundData)(byte *data, int samplerate, int length,
-                                   Mix_Chunk *destination) = NULL;
 
 int use_libsamplerate = 0;
 
+extern int mb_used;
+
+
 // When a sound stops, check if it is still playing.  If it is not, 
 // we can mark the sound data as CACHE to be freed back for other
 // means.
@@ -79,6 +81,12 @@
 
     channels_playing[channel] = sfx_None;
     
+#ifdef HAVE_LIBSAMPLERATE
+    // Don't allow precached sounds to be swapped out.
+    if (use_libsamplerate)
+        return;
+#endif
+
     for (i=0; i<NUM_CHANNELS; ++i)
     {
         // Playing on this channel? if so, don't release.
@@ -92,6 +100,7 @@
     Z_ChangeTag(sound_chunks[id].abuf, PU_CACHE);
 }
 
+
 #ifdef HAVE_LIBSAMPLERATE
 
 // Returns the conversion mode for libsamplerate to use.
@@ -121,101 +130,6 @@
     }
 }
 
-// libsamplerate-based generic sound expansion function for any sample rate
-//   unsigned 8 bits --> signed 16 bits
-//   mono --> stereo
-//   samplerate --> mixer_freq
-// Returns number of clipped samples.
-// DWF 2008-02-10 with cleanups by Simon Howard.
-
-static uint32_t ExpandSoundData_SRC(byte *data,
-                                    int samplerate,
-                                    int length,
-                                    Mix_Chunk *destination)
-{
-    SRC_DATA src_data;
-    uint32_t i, abuf_index=0, clipped=0;
-    int retn;
-    int16_t *expanded;
-
-    src_data.input_frames = length;
-    src_data.data_in = malloc(length * sizeof(float));
-    src_data.src_ratio = (double)mixer_freq / samplerate;
-
-    // We include some extra space here in case of rounding-up.
-    src_data.output_frames = src_data.src_ratio * length + (mixer_freq / 4);
-    src_data.data_out = malloc(src_data.output_frames * sizeof(float));
-
-    assert(src_data.data_in != NULL && src_data.data_out != NULL);
-
-    // Convert input data to floats
-
-    for (i=0; i<length; ++i)
-    {
-        // Unclear whether 128 should be interpreted as "zero" or whether a
-        // symmetrical range should be assumed.  The following assumes a
-        // symmetrical range.
-        src_data.data_in[i] = data[i] / 127.5 - 1;
-    }
-
-    // Do the sound conversion
-
-    retn = src_simple(&src_data, SRC_ConversionMode(), 1);
-    assert(retn == 0);
-
-    // Convert the result back into 16-bit integers.
-
-    destination->alen = src_data.output_frames_gen * 4;
-    destination->abuf = Z_Malloc(destination->alen, PU_STATIC, 
-                                 &destination->abuf);
-    expanded = (int16_t *) destination->abuf;
-
-    for (i=0; i<src_data.output_frames_gen; ++i)
-    {
-        // libsamplerate does not limit itself to the -1.0 .. 1.0 range on
-        // output, so a multiplier less than INT16_MAX (32767) is required
-        // to avoid overflows or clipping.  However, the smaller the
-        // multiplier, the quieter the sound effects get, and the more you
-        // have to turn down the music to keep it in balance.
-
-        // 22265 is the largest multiplier that can be used to resample all
-        // of the Vanilla DOOM sound effects to 48 kHz without clipping
-        // using SRC_SINC_BEST_QUALITY.  It is close enough (only slightly
-        // too conservative) for SRC_SINC_MEDIUM_QUALITY and
-        // SRC_SINC_FASTEST.  PWADs with interestingly different sound
-        // effects or target rates other than 48 kHz might still result in
-        // clipping--I don't know if there's a limit to it.
-
-        // As the number of clipped samples increases, the signal is
-        // gradually overtaken by noise, with the loudest parts going first.
-        // However, a moderate amount of clipping is often tolerated in the
-        // quest for the loudest possible sound overall.  The results of
-        // using INT16_MAX as the multiplier are not all that bad, but
-        // artifacts are noticeable during the loudest parts.
-
-        float   cvtval_f = src_data.data_out[i] * 22265;
-        int32_t cvtval_i = cvtval_f + (cvtval_f < 0 ? -0.5 : 0.5);
-
-        // Asymmetrical sound worries me, so we won't use -32768.
-        if (cvtval_i < -INT16_MAX) {
-          cvtval_i = -INT16_MAX;
-          ++clipped;
-        } else if (cvtval_i > INT16_MAX) {
-          cvtval_i = INT16_MAX;
-          ++clipped;
-        }
-
-        // Left and right channels
-
-        expanded[abuf_index++] = cvtval_i;
-        expanded[abuf_index++] = cvtval_i;
-    }
-
-    free(src_data.data_in);
-    free(src_data.data_out);
-    return clipped;
-}
-
 #endif
 
 static boolean ConvertibleRatio(int freq1, int freq2)
@@ -248,12 +162,11 @@
 }
 
 // Generic sound expansion function for any sample rate.
-// Returns number of clipped samples (always 0).
 
-static uint32_t ExpandSoundData_SDL(byte *data,
-                                    int samplerate,
-                                    int length,
-                                    Mix_Chunk *destination)
+static void ExpandSoundData_SDL(byte *data,
+				int samplerate,
+				uint32_t length,
+				Mix_Chunk *destination)
 {
     SDL_AudioCVT convertor;
     uint32_t expanded_length;
@@ -265,7 +178,6 @@
     // Double up twice: 8 -> 16 bit and mono -> stereo
 
     expanded_length *= 4;
-
     destination->alen = expanded_length;
     destination->abuf 
         = Z_Malloc(expanded_length, PU_STATIC, &destination->abuf);
@@ -344,70 +256,93 @@
         }
 #endif /* #ifdef LOW_PASS_FILTER */
     }
-
-    return 0;
 }
 
-// Load and convert a sound effect
-// Returns true if successful
 
-static boolean CacheSFX(int sound)
+// Load and validate a sound effect lump.
+// Preconditions:
+//     S_sfx[sound].lumpnum has been set
+// Postconditions if sound is valid:
+//     returns true
+//     starred parameters are set, with data_ref pointing to start of sound
+//     caller is responsible for releasing the identified lump
+// Postconditions if sound is invalid:
+//     returns false
+//     starred parameters are garbage
+//     lump already released
+
+static boolean LoadSoundLump(int sound,
+			     int *lumpnum,
+			     int *samplerate,
+			     uint32_t *length,
+			     byte **data_ref)
 {
-    int lumpnum;
-    unsigned int lumplen;
-    int samplerate;
-    int clipped;
-    unsigned int length;
-    byte *data;
+    // Load the sound
 
-    // need to load the sound
+    *lumpnum    = S_sfx[sound].lumpnum;
+    *data_ref   = W_CacheLumpNum(*lumpnum, PU_STATIC);
+    int lumplen = W_LumpLength(*lumpnum);
+    byte *data  = *data_ref;
 
-    lumpnum = S_sfx[sound].lumpnum;
-    data = W_CacheLumpNum(lumpnum, PU_STATIC);
-    lumplen = W_LumpLength(lumpnum);
+    // Ensure this is a valid sound
 
-    // Check the header, and ensure this is a valid sound
-
-    if (lumplen < 8
-     || data[0] != 0x03 || data[1] != 0x00)
+    if (lumplen < 8 || data[0] != 0x03 || data[1] != 0x00)
     {
-        // Invalid sound
-
-        return false;
+	// Invalid sound
+	W_ReleaseLumpNum(*lumpnum);
+	return false;
     }
 
     // 16 bit sample rate field, 32 bit length field
 
-    samplerate = (data[3] << 8) | data[2];
-    length = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4];
+    *samplerate = (data[3] << 8) | data[2];
+    *length = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4];
 
-    // If the header specifies that the length of the sound is greater than
-    // the length of the lump itself, this is an invalid sound lump
+    // If the header specifies that the length of the sound is
+    // greater than the length of the lump itself, this is an invalid
+    // sound lump.
 
-    if (length > lumplen - 8)
+    if (*length > lumplen - 8)
     {
-        return false;
+	W_ReleaseLumpNum(*lumpnum);
+	return false;
     }
 
+    // Prune header
+    *data_ref += 8;
+
+    return true;
+}
+
+
+// Load and convert a sound effect
+// Returns true if successful
+
+static boolean CacheSFX_SDL(int sound)
+{
+    int lumpnum;
+    int samplerate;
+    uint32_t length;
+    byte *data;
+
+#ifdef HAVE_LIBSAMPLERATE
+    assert(!use_libsamplerate); // Should be using I_PrecacheSounds_SRC instead
+#endif
+
+    if (!LoadSoundLump(sound, &lumpnum, &samplerate, &length, &data))
+        return false;
+
     // Sample rate conversion
-    // DWF 2008-02-10:  sound_chunks[sound].alen and abuf are determined
-    // by ExpandSoundData.
+    // sound_chunks[sound].alen and abuf are determined by ExpandSoundData.
 
     sound_chunks[sound].allocated = 1;
     sound_chunks[sound].volume = MIX_MAX_VOLUME;
 
-    clipped = ExpandSoundData(data + 8, 
-                              samplerate, 
-                              length, 
-                              &sound_chunks[sound]);
+    ExpandSoundData_SDL(data,
+			samplerate, 
+			length, 
+			&sound_chunks[sound]);
 
-    if (clipped)
-    {
-        fprintf(stderr, "Sound %d: clipped %u samples (%0.2f %%)\n", 
-                        sound, clipped,
-                        400.0 * clipped / sound_chunks[sound].alen);
-    }
-
     // don't need the original lump any more
   
     W_ReleaseLumpNum(lumpnum);
@@ -415,50 +350,169 @@
     return true;
 }
 
+
 #ifdef HAVE_LIBSAMPLERATE
 
-// Preload all the sound effects - stops nasty ingame freezes
+// Preload and resample all sound effects with libsamplerate.
 
-static void I_PrecacheSounds(void)
+static void I_PrecacheSounds_SRC(void)
 {
     char namebuf[9];
-    int i;
+    uint32_t sound_i, sample_i;
+    boolean good_sound[NUMSFX];
+    float *resampled_sound[NUMSFX];
+    uint32_t resampled_sound_length[NUMSFX];
+    float norm_factor;
+    float max_amp = 0;
 
-    printf("I_PrecacheSounds: Precaching all sound effects..");
+    assert(use_libsamplerate);
 
-    for (i=sfx_pistol; i<NUMSFX; ++i)
+    if (mb_used < 32)
     {
-        if ((i % 6) == 0)
+        fprintf(stderr,
+                "WARNING: low memory.  Heap size is only %d MiB.\n"
+                "WARNING: use_libsamplerate needs more heap!\n"
+                "WARNING: put -mb 64 on the command line to avoid "
+                "\"Error: Z_Malloc: failed on allocation of X bytes\" !\n",
+                mb_used);
+    }
+
+    printf("I_PrecacheSounds_SRC: Precaching all sound effects..");
+
+    // Pass 1:  resample all sounds and determine maximum amplitude.
+
+    for (sound_i=sfx_pistol; sound_i<NUMSFX; ++sound_i)
+    {
+        good_sound[sound_i] = false;
+
+        if ((sound_i % 6) == 0)
         {
             printf(".");
             fflush(stdout);
         }
 
-        sprintf(namebuf, "ds%s", DEH_String(S_sfx[i].name));
+        sprintf(namebuf, "ds%s", DEH_String(S_sfx[sound_i].name));
+        S_sfx[sound_i].lumpnum = W_CheckNumForName(namebuf);
+        if (S_sfx[sound_i].lumpnum != -1)
+        {
+	    int lumpnum;
+	    int samplerate;
+	    uint32_t length;
+	    byte *data;
+            double of_temp;
+            int retn;
+            float *rsound;
+            uint32_t rlen;
+	    SRC_DATA src_data;
 
-        S_sfx[i].lumpnum = W_CheckNumForName(namebuf);
+	    if (!LoadSoundLump(sound_i, &lumpnum, &samplerate, &length, &data))
+		continue;
 
-        if (S_sfx[i].lumpnum != -1)
+            assert(length <= LONG_MAX);
+	    src_data.input_frames = length;
+	    src_data.data_in = malloc(length * sizeof(float));
+	    src_data.src_ratio = (double)mixer_freq / samplerate;
+
+	    // mixer_freq / 4 adds a quarter-second safety margin.
+
+            of_temp = src_data.src_ratio * length + (mixer_freq / 4);
+            assert(of_temp <= LONG_MAX);
+	    src_data.output_frames = of_temp;
+	    src_data.data_out = malloc(src_data.output_frames * sizeof(float));
+	    assert(src_data.data_in != NULL && src_data.data_out != NULL);
+
+	    // Convert input data to floats
+
+	    for (sample_i=0; sample_i<length; ++sample_i)
+	    {
+		// Unclear whether 128 should be interpreted as "zero" or
+		// whether a symmetrical range should be assumed.  The
+		// following assumes a symmetrical range.
+
+		src_data.data_in[sample_i] = data[sample_i] / 127.5 - 1;
+	    }
+
+            // don't need the original lump any more
+
+            W_ReleaseLumpNum(lumpnum);
+
+	    // Resample
+
+	    retn = src_simple(&src_data, SRC_ConversionMode(), 1);
+	    assert(retn == 0);
+            assert(src_data.output_frames_gen > 0);
+            resampled_sound[sound_i] = src_data.data_out;
+            resampled_sound_length[sound_i] = src_data.output_frames_gen;
+            free(src_data.data_in);
+            good_sound[sound_i] = true;
+
+            // Track maximum amplitude for later normalization
+
+            rsound = resampled_sound[sound_i];
+            rlen = resampled_sound_length[sound_i];
+	    for (sample_i=0; sample_i<rlen; ++sample_i)
+	    {
+	        float fabs_amp = fabsf(rsound[sample_i]);
+                if (fabs_amp > max_amp)
+  		    max_amp = fabs_amp;
+            }
+        }
+    }
+
+    // Pass 2:  normalize and convert to signed 16-bit stereo.
+
+    if (max_amp <= 0)
+        max_amp = 1;
+    norm_factor = INT16_MAX / max_amp;
+
+    for (sound_i=sfx_pistol; sound_i<NUMSFX; ++sound_i)
+    {
+        if (good_sound[sound_i])
         {
-            CacheSFX(i);
+            uint32_t rlen = resampled_sound_length[sound_i];
+            int16_t *expanded;
+            uint32_t abuf_index;
+            float *rsound;
 
-            if (sound_chunks[i].abuf != NULL)
+            sound_chunks[sound_i].allocated = 1;
+            sound_chunks[sound_i].volume = MIX_MAX_VOLUME;
+            sound_chunks[sound_i].alen = rlen * 4;
+            sound_chunks[sound_i].abuf = Z_Malloc(sound_chunks[sound_i].alen,
+                                                  PU_STATIC,
+                                                  &sound_chunks[sound_i].abuf);
+            expanded = (int16_t *) sound_chunks[sound_i].abuf;
+            abuf_index=0;
+
+            rsound = resampled_sound[sound_i];
+            for (sample_i=0; sample_i<rlen; ++sample_i)
             {
-                Z_ChangeTag(sound_chunks[i].abuf, PU_CACHE);
+                float   cvtval_f = norm_factor * rsound[sample_i];
+                int16_t cvtval_i = cvtval_f + (cvtval_f < 0 ? -0.5 : 0.5);
+
+                // Left and right channels
+
+                expanded[abuf_index++] = cvtval_i;
+                expanded[abuf_index++] = cvtval_i;
             }
+            free(rsound);
         }
     }
 
-    printf("\n");
+    printf(" norm factor = %f\n", norm_factor);
 }
 
 #endif
 
+
 static Mix_Chunk *GetSFXChunk(int sound_id)
 {
     if (sound_chunks[sound_id].abuf == NULL)
     {
-        if (!CacheSFX(sound_id))
+#ifdef HAVE_LIBSAMPLERATE
+        if (use_libsamplerate != 0)
+            return NULL;   /* If valid, it should have been precached */
+#endif
+        if (!CacheSFX_SDL(sound_id))
             return NULL;
     }
     else
@@ -641,8 +695,6 @@
         return false;
     }
 
-    ExpandSoundData = ExpandSoundData_SDL;
-
     Mix_QuerySpec(&mixer_freq, &mixer_format, &mixer_channels);
 
 #ifdef HAVE_LIBSAMPLERATE
@@ -654,9 +706,7 @@
                     use_libsamplerate);
         }
 
-        ExpandSoundData = ExpandSoundData_SRC;
-
-        I_PrecacheSounds();
+        I_PrecacheSounds_SRC();
     }
 #else
     if (use_libsamplerate != 0)