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);
}
}