shithub: choc

Download patch

ref: 93fe1124119faafe0704ea913ab4d5977b1f24b6
parent: 4780944f27780ad8287b39df3209076d6bce90ce
parent: 8a3621b237bdc5c7c2b9e7ea747045aa7950ad8c
author: Simon Howard <fraggle+github@gmail.com>
date: Wed Jul 8 17:29:32 EDT 2015

Merge pull request #567 from jmtd/pitch-shifting

Implementation of pitch shifting.

This appeared in early versions of Doom and (more importantly) in Heretic
and Hexen. Sound effects get pitched up and down to sound less repetitive.

--- a/src/doom/s_sound.c
+++ b/src/doom/s_sound.c
@@ -59,7 +59,6 @@
 
 #define S_STEREO_SWING (96 * FRACUNIT)
 
-#define NORM_PITCH 128
 #define NORM_PRIORITY 64
 #define NORM_SEP 128
 
@@ -74,6 +73,8 @@
     // handle of the sound being played
     int handle;
 
+    int pitch;
+
 } channel_t;
 
 // The set of channels available
@@ -141,6 +142,12 @@
         S_sfx[i].lumpnum = S_sfx[i].usefulness = -1;
     }
 
+    // Doom defaults to pitch-shifting off.
+    if(snd_pitchshift == -1)
+    {
+        snd_pitchshift = 0;
+    }
+
     I_AtExit(S_Shutdown, true);
 }
 
@@ -390,6 +397,21 @@
     return (*vol > 0);
 }
 
+// clamp supplied integer to the range 0 <= x <= 255.
+
+static int Clamp(int x)
+{
+    if (x < 0)
+    {
+        return 0;
+    }
+    else if (x > 255)
+    {
+        return 255;
+    }
+    return x;
+}
+
 void S_StartSound(void *origin_p, int sfx_id)
 {
     sfxinfo_t *sfx;
@@ -396,6 +418,7 @@
     mobj_t *origin;
     int rc;
     int sep;
+    int pitch;
     int cnum;
     int volume;
 
@@ -411,9 +434,11 @@
     sfx = &S_sfx[sfx_id];
 
     // Initialize sound parameters
+    pitch = NORM_PITCH;
     if (sfx->link)
     {
         volume += sfx->volume;
+        pitch = sfx->pitch;
 
         if (volume < 1)
         {
@@ -452,6 +477,17 @@
         sep = NORM_SEP;
     }
 
+    // hacks to vary the sfx pitches
+    if (sfx_id >= sfx_sawup && sfx_id <= sfx_sawhit)
+    {
+        pitch += 8 - (M_Random()&15);
+    }
+    else if (sfx_id != sfx_itemup && sfx_id != sfx_tink)
+    {
+        pitch += 16 - (M_Random()&31);
+    }
+    pitch = Clamp(pitch);
+
     // kill old sound
     S_StopSound(origin);
 
@@ -474,7 +510,8 @@
         sfx->lumpnum = I_GetSfxLumpNum(sfx);
     }
 
-    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep);
+    channels[cnum].pitch = pitch;
+    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep, channels[cnum].pitch);
 }
 
 //
--- a/src/heretic/s_sound.c
+++ b/src/heretic/s_sound.c
@@ -268,10 +268,8 @@
             sep = 512 - sep;
     }
 
-    // TODO: Play pitch-shifted sounds as in Vanilla Heretic
-
-    channel[i].pitch = (byte) (127 + (M_Random() & 7) - (M_Random() & 7));
-    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, vol, sep);
+    channel[i].pitch = (byte) (NORM_PITCH + (M_Random() & 7) - (M_Random() & 7));
+    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, vol, sep, channel[i].pitch);
     channel[i].mo = origin;
     channel[i].sound_id = sound_id;
     channel[i].priority = priority;
@@ -327,9 +325,8 @@
         S_sfx[sound_id].lumpnum = I_GetSfxLumpNum(&S_sfx[sound_id]);
     }
 
-    // TODO: Pitch shifting.
-    channel[i].pitch = (byte) (127 - (M_Random() & 3) + (M_Random() & 3));
-    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, volume, 128);
+    channel[i].pitch = (byte) (NORM_PITCH - (M_Random() & 3) + (M_Random() & 3));
+    channel[i].handle = I_StartSound(&S_sfx[sound_id], i, volume, 128, channel[i].pitch);
     channel[i].mo = origin;
     channel[i].sound_id = sound_id;
     channel[i].priority = 1;    //super low priority.
@@ -526,6 +523,12 @@
     S_SetMaxVolume(true);
 
     I_AtExit(S_ShutDown, true);
+
+    // Heretic defaults to pitch-shifting on
+    if(snd_pitchshift == -1)
+    {
+        snd_pitchshift = 1;
+    }
 
     I_PrecacheSounds(S_sfx, NUMSFX);
 }
--- a/src/hexen/s_sound.c
+++ b/src/hexen/s_sound.c
@@ -498,17 +498,16 @@
 //              vol = SoundCurve[dist];
     }
 
-#if 0
-// TODO
-    if (S_sfx[sound_id].changePitch)
+    // if the sfxinfo_t is marked as 'can be pitch shifted'
+    if (S_sfx[sound_id].pitch)
     {
-        Channel[i].pitch = (byte) (127 + (M_Random() & 7) - (M_Random() & 7));
+        Channel[i].pitch = (byte) (NORM_PITCH + (M_Random() & 7) - (M_Random() & 7));
     }
     else
     {
-        Channel[i].pitch = 127;
+        Channel[i].pitch = NORM_PITCH;
     }
-#endif
+
     if (S_sfx[sound_id].lumpnum == 0)
     {
         S_sfx[sound_id].lumpnum = I_GetSfxLumpNum(&S_sfx[sound_id]);
@@ -517,7 +516,8 @@
     Channel[i].handle = I_StartSound(&S_sfx[sound_id],
                                      i,
                                      vol,
-                                     sep /* , Channel[i].pitch] */);
+                                     sep,
+                                     Channel[i].pitch);
     Channel[i].sound_id = sound_id;
     Channel[i].priority = priority;
     Channel[i].volume = volume;
@@ -771,7 +771,7 @@
                 if (sep > 192)
                     sep = 512 - sep;
             }
-            I_UpdateSoundParams(i, vol, sep /*, Channel[i].pitch */);
+            I_UpdateSoundParams(i, vol, sep);
             priority = S_sfx[Channel[i].sound_id].priority;
             priority *= PRIORITY_MAX_ADJUST - (dist / DIST_ADJUST);
             Channel[i].priority = priority;
@@ -798,6 +798,12 @@
     I_SetMusicVolume(snd_MusicVolume * 8);
 
     I_AtExit(S_ShutDown, true);
+
+    // Hexen defaults to pitch-shifting on
+    if(snd_pitchshift == -1)
+    {
+        snd_pitchshift = 1;
+    }
 
     I_PrecacheSounds(S_sfx, NUMSFX);
 
--- a/src/i_pcsound.c
+++ b/src/i_pcsound.c
@@ -174,7 +174,8 @@
 static int I_PCS_StartSound(sfxinfo_t *sfxinfo,
                             int channel,
                             int vol,
-                            int sep)
+                            int sep,
+                            int pitch)
 {
     int result;
 
--- a/src/i_sdlsound.c
+++ b/src/i_sdlsound.c
@@ -52,6 +52,7 @@
     sfxinfo_t *sfxinfo;
     Mix_Chunk chunk;
     int use_count;
+    int pitch;
     allocated_sound_t *prev, *next;
 };
 
@@ -59,7 +60,7 @@
 
 static boolean sound_initialized = false;
 
-static sfxinfo_t *channels_playing[NUM_CHANNELS];
+static allocated_sound_t *channels_playing[NUM_CHANNELS];
 
 static int mixer_freq;
 static Uint16 mixer_format;
@@ -136,10 +137,6 @@
 
     AllocatedSoundUnlink(snd);
 
-    // Unlink from higher-level code.
-
-    snd->sfxinfo->driver_data = NULL;
-
     // Keep track of the amount of allocated sound data:
 
     allocated_sounds_size -= snd->chunk.alen;
@@ -200,7 +197,7 @@
 
 // Allocate a block for a new sound effect.
 
-static Mix_Chunk *AllocateSound(sfxinfo_t *sfxinfo, size_t len)
+static allocated_sound_t *AllocateSound(sfxinfo_t *sfxinfo, size_t len)
 {
     allocated_sound_t *snd;
 
@@ -231,14 +228,11 @@
     snd->chunk.alen = len;
     snd->chunk.allocated = 1;
     snd->chunk.volume = MIX_MAX_VOLUME;
+    snd->pitch = NORM_PITCH;
 
     snd->sfxinfo = sfxinfo;
     snd->use_count = 0;
 
-    // driver_data pointer points to the allocated_sound structure.
-
-    sfxinfo->driver_data = snd;
-
     // Keep track of how much memory all these cached sounds are using...
 
     allocated_sounds_size += len;
@@ -245,7 +239,7 @@
 
     AllocatedSoundLink(snd);
 
-    return &snd->chunk;
+    return snd;
 }
 
 // Lock a sound, to indicate that it may not be freed.
@@ -279,6 +273,67 @@
     //printf("-- %s: Use count=%i\n", snd->sfxinfo->name, snd->use_count);
 }
 
+// Search through the list of allocated sounds and return the one that matches
+// the supplied sfxinfo entry and pitch level.
+
+static allocated_sound_t * GetAllocatedSoundBySfxInfoAndPitch(sfxinfo_t *sfxinfo, int pitch)
+{
+    allocated_sound_t * p = allocated_sounds_head;
+
+    while(p != NULL)
+    {
+        if(p->sfxinfo == sfxinfo && p->pitch == pitch)
+        {
+            return p;
+        }
+        p = p->next;
+    }
+    return NULL;
+}
+
+// Allocate a new sound chunk and pitch-shift an existing sound up-or-down
+// into it.
+
+static allocated_sound_t * PitchShift(allocated_sound_t *insnd, int pitch)
+{
+    allocated_sound_t * outsnd;
+    Sint16 *inp, *outp;
+    Sint16 *srcbuf, *dstbuf;
+    Uint32 srclen, dstlen;
+
+    srcbuf = (Sint16 *)insnd->chunk.abuf;
+    srclen = insnd->chunk.alen;
+
+    // determine ratio pitch:NORM_PITCH and apply to srclen, then invert.
+    // This is an approximation of vanilla behaviour based on measurements
+    dstlen = (int)((1 + (1 - (float)pitch / NORM_PITCH)) * srclen);
+
+    // ensure that the new buffer is an even length
+    if( (dstlen % 2) == 0)
+    {
+        dstlen++;
+    }
+
+    outsnd = AllocateSound(insnd->sfxinfo, dstlen);
+
+    if(!outsnd)
+    {
+        return NULL;
+    }
+
+    outsnd->pitch = pitch;
+    dstbuf = (Sint16 *)outsnd->chunk.abuf;
+
+    // loop over output buffer. find corresponding input cell, copy over
+    for(outp = dstbuf; outp < dstbuf + dstlen/2; ++outp)
+    {
+        inp = srcbuf + (int)((float)(outp - dstbuf) / dstlen * srclen);
+        *outp = *inp;
+    }
+
+    return outsnd;
+}
+
 // 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.
@@ -285,9 +340,9 @@
 
 static void ReleaseSoundOnChannel(int channel)
 {
-    sfxinfo_t *sfxinfo = channels_playing[channel];
+    allocated_sound_t *snd = channels_playing[channel];
 
-    if (sfxinfo == NULL)
+    if (snd == NULL)
     {
         return;
     }
@@ -294,7 +349,14 @@
 
     channels_playing[channel] = NULL;
 
-    UnlockAllocatedSound(sfxinfo->driver_data);
+    UnlockAllocatedSound(snd);
+
+    // if the sound is a pitch-shift and it's not in use, immediately
+    // free it
+    if(snd->pitch != NORM_PITCH && snd->use_count <= 0)
+    {
+        FreeAllocatedSound(snd);
+    }
 }
 
 #ifdef HAVE_LIBSAMPLERATE
@@ -343,6 +405,7 @@
 //    uint32_t alen;
     int retn;
     int16_t *expanded;
+    allocated_sound_t *snd;
     Mix_Chunk *chunk;
 
     src_data.input_frames = length;
@@ -374,13 +437,14 @@
 
 //    alen = src_data.output_frames_gen * 4;
 
-    chunk = AllocateSound(sfxinfo, src_data.output_frames_gen * 4);
+    snd = AllocateSound(sfxinfo, src_data.output_frames_gen * 4);
 
-    if (chunk == NULL)
+    if (snd == NULL)
     {
         return false;
     }
 
+    chunk = &snd->chunk;
     expanded = (int16_t *) chunk->abuf;
 
     // Convert the result back into 16-bit integers.
@@ -533,11 +597,12 @@
                                    int length)
 {
     SDL_AudioCVT convertor;
+    allocated_sound_t *snd;
     Mix_Chunk *chunk;
     uint32_t expanded_length;
- 
-    // Calculate the length of the expanded version of the sample.    
 
+    // Calculate the length of the expanded version of the sample.
+
     expanded_length = (uint32_t) ((((uint64_t) length) * mixer_freq) / samplerate);
 
     // Double up twice: 8 -> 16 bit and mono -> stereo
@@ -546,13 +611,15 @@
 
     // Allocate a chunk in which to expand the sound
 
-    chunk = AllocateSound(sfxinfo, expanded_length);
+    snd = AllocateSound(sfxinfo, expanded_length);
 
-    if (chunk == NULL)
+    if (snd == NULL)
     {
         return false;
     }
 
+    chunk = &snd->chunk;
+
     // If we can, use the standard / optimized SDL conversion routines.
 
     if (samplerate <= mixer_freq
@@ -696,11 +763,12 @@
 #ifdef DEBUG_DUMP_WAVS
     {
         char filename[16];
+        allocated_sound_t * snd;
 
         M_snprintf(filename, sizeof(filename), "%s.wav",
-                   DEH_String(S_sfx[sound].name));
-        WriteWAV(filename, sound_chunks[sound].abuf,
-                 sound_chunks[sound].alen, mixer_freq);
+                   DEH_String(sfxinfo->name));
+        snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH);
+        WriteWAV(filename, snd->chunk.abuf, snd->chunk.alen,mixer_freq);
     }
 #endif
 
@@ -786,8 +854,7 @@
 static boolean LockSound(sfxinfo_t *sfxinfo)
 {
     // If the sound isn't loaded, load it now
-
-    if (sfxinfo->driver_data == NULL)
+    if (GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH) == NULL)
     {
         if (!CacheSFX(sfxinfo))
         {
@@ -795,7 +862,7 @@
         }
     }
 
-    LockAllocatedSound(sfxinfo->driver_data);
+    LockAllocatedSound(GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH));
 
     return true;
 }
@@ -857,7 +924,7 @@
 //  is set, but currently not used by mixing.
 //
 
-static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
+static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch)
 {
     allocated_sound_t *snd;
 
@@ -878,13 +945,41 @@
         return -1;
     }
 
-    snd = sfxinfo->driver_data;
+    snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, pitch);
 
+    if(snd == NULL)
+    {
+        allocated_sound_t *newsnd;
+        // fetch the base sound effect, un-pitch-shifted
+        snd = GetAllocatedSoundBySfxInfoAndPitch(sfxinfo, NORM_PITCH);
+
+        if(!snd)
+        {
+            return -1;
+        }
+
+        if(snd_pitchshift)
+        {
+            newsnd = PitchShift(snd, pitch);
+
+            if(newsnd)
+            {
+                LockAllocatedSound(newsnd);
+                UnlockAllocatedSound(snd);
+                snd = newsnd;
+            }
+        }
+    }
+    else
+    {
+        LockAllocatedSound(snd);
+    }
+
     // play sound
 
     Mix_PlayChannel(channel, &snd->chunk, 0);
 
-    channels_playing[channel] = sfxinfo;
+    channels_playing[channel] = snd;
 
     // set separation, etc.
 
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@ -48,6 +48,11 @@
 
 char *snd_musiccmd = "";
 
+// Whether to vary the pitch of sound effects
+// Each game will set the default differently
+
+int snd_pitchshift = -1;
+
 // Low-level sound and music modules we are using
 
 static sound_module_t *sound_module;
@@ -308,12 +313,12 @@
     }
 }
 
-int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
+int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch)
 {
     if (sound_module != NULL)
     {
         CheckVolumeSeparation(&vol, &sep);
-        return sound_module->StartSound(sfxinfo, channel, vol, sep);
+        return sound_module->StartSound(sfxinfo, channel, vol, sep, pitch);
     }
     else
     {
@@ -448,6 +453,7 @@
     M_BindIntVariable("snd_samplerate",          &snd_samplerate);
     M_BindIntVariable("snd_cachesize",           &snd_cachesize);
     M_BindIntVariable("opl_io_port",             &opl_io_port);
+    M_BindIntVariable("snd_pitchshift",          &snd_pitchshift);
 
     M_BindStringVariable("timidity_cfg_path",    &timidity_cfg_path);
     M_BindStringVariable("gus_patch_path",       &gus_patch_path);
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -22,6 +22,8 @@
 
 #include "doomtype.h"
 
+// so that the individual game logic and sound driver code agree
+#define NORM_PITCH 127
 
 //
 // SoundFX struct.
@@ -44,7 +46,7 @@
     // referenced sound if a link
     sfxinfo_t *link;
 
-    // pitch if a link
+    // pitch if a link (Doom), whether to pitch-shift (Hexen)
     int pitch;
 
     // volume if a link
@@ -76,13 +78,13 @@
 
     // lump number of music
     int lumpnum;
-    
+
     // music data
     void *data;
 
     // music handle once registered
     void *handle;
-    
+
 } musicinfo_t;
 
 typedef enum 
@@ -133,7 +135,7 @@
     // Start a sound on a given channel.  Returns the channel id
     // or -1 on failure.
 
-    int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep);
+    int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch);
 
     // Stop the sound playing on the given channel.
 
@@ -154,7 +156,7 @@
 int I_GetSfxLumpNum(sfxinfo_t *sfxinfo);
 void I_UpdateSound(void);
 void I_UpdateSoundParams(int channel, int vol, int sep);
-int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep);
+int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep, int pitch);
 void I_StopSound(int channel);
 boolean I_SoundIsPlaying(int channel);
 void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds);
@@ -231,6 +233,7 @@
 extern int snd_cachesize;
 extern int snd_maxslicetime_ms;
 extern char *snd_musiccmd;
+extern int snd_pitchshift;
 
 void I_BindSoundVariables(void);
 
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -809,6 +809,14 @@
     CONFIG_VARIABLE_INT(snd_maxslicetime_ms),
 
     //!
+    // If non-zero, sound effects will have their pitch varied up or
+    // down by a random amount during play. If zero, sound effects
+    // play back at their default pitch. The default is zero.
+    //
+
+    CONFIG_VARIABLE_INT(snd_pitchshift),
+
+    //!
     // External command to invoke to perform MIDI playback. If set to
     // the empty string, SDL_mixer's internal MIDI playback is used.
     // This only has any effect when snd_musicdevice is set to General
--- a/src/setup/sound.c
+++ b/src/setup/sound.c
@@ -85,6 +85,7 @@
 int snd_cachesize = 64 * 1024 * 1024;
 int snd_maxslicetime_ms = 28;
 char *snd_musiccmd = "";
+int snd_pitchshift = 0;
 
 static int numChannels = 8;
 static int sfxVolume = 8;
@@ -293,6 +294,14 @@
         num_music_modes = NUM_MUSICMODES - 1;
     }
 
+    // All versions of Heretic and Hexen did pitch-shifting.
+    // Most versions of Doom did not and Strife never did.
+
+    if(gamemission == heretic || gamemission == hexen)
+    {
+        snd_pitchshift = 1;
+    }
+
     // Build the window
 
     window = TXT_NewWindow("Sound configuration");
@@ -320,6 +329,14 @@
                    TXT_NewSpinControl(&sfxVolume, 0, 15),
                    NULL);
 
+    // strife did not implement pitch shifting at all, so hide the option.
+
+    if (gamemission != strife)
+    {
+        TXT_AddWidget(sfx_table,
+                   TXT_NewCheckBox("Pitch-shift sounds", &snd_pitchshift));
+    }
+
     if (gamemission == strife)
     {
         TXT_AddWidgets(sfx_table,
@@ -383,6 +400,8 @@
 
     M_BindIntVariable("snd_cachesize",            &snd_cachesize);
     M_BindIntVariable("opl_io_port",              &opl_io_port);
+
+    M_BindIntVariable("snd_pitchshift",           &snd_pitchshift);
 
     if (gamemission == strife)
     {
--- a/src/strife/s_sound.c
+++ b/src/strife/s_sound.c
@@ -60,7 +60,6 @@
 
 #define S_STEREO_SWING (96 * FRACUNIT)
 
-#define NORM_PITCH 128
 #define NORM_PRIORITY 64
 #define NORM_SEP 128
 
@@ -74,6 +73,8 @@
 
     // handle of the sound being played
     int handle;
+
+    int pitch;
     
 } channel_t;
 
@@ -408,6 +409,7 @@
     mobj_t *origin;
     int rc;
     int sep;
+    int pitch;
     int cnum;
     int volume;
 
@@ -467,6 +469,7 @@
     {
         sep = NORM_SEP;
     }
+    pitch = NORM_PITCH;
 
     // kill old sound [STRIFE] - nope!
     //S_StopSound(origin);
@@ -490,7 +493,7 @@
         sfx->lumpnum = I_GetSfxLumpNum(sfx);
     }
 
-    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep);
+    channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep, pitch);
 }
 
 
@@ -617,7 +620,7 @@
         i_voicehandle = S_GetChannel(NULL, &voice->sfx, true);
 
         channels[i_voicehandle].handle 
-            = I_StartSound(&voice->sfx, i_voicehandle, snd_VoiceVolume, NORM_SEP);
+            = I_StartSound(&voice->sfx, i_voicehandle, snd_VoiceVolume, NORM_SEP, NORM_PITCH);
     }
 }