From ed345c7d254c58f05d3f44e367f8b118584c67ef Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Tue, 27 Dec 2011 21:01:47 -0700 Subject: [PATCH 01/11] try linearly interpolating between samples for 11025->44100 upsampling. sounds like crap. --- Quake/snd_dma.c | 2 +- Quake/snd_mem.c | 61 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/Quake/snd_dma.c b/Quake/snd_dma.c index cd93411e..a31aaba8 100644 --- a/Quake/snd_dma.c +++ b/Quake/snd_dma.c @@ -74,7 +74,7 @@ cvar_t sfxvolume = {"volume", "0.7", true}; cvar_t precache = {"precache", "1"}; cvar_t loadas8bit = {"loadas8bit", "0"}; -cvar_t sndspeed = {"sndspeed", "11025"}; +cvar_t sndspeed = {"sndspeed", "44100"}; static float oldvolume = -1.0; diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index e7970a19..19e47c30 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -22,6 +22,23 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" +static int getsample(byte *data, int inwidth, int srcsample) +{ + if (inwidth == 2) + return LittleShort ( ((short *)data)[srcsample] ); + else + return (int)( (unsigned char)(data[srcsample]) - 128) << 8; +} + +static void putsample(byte *data, int outwidth, int i, int sample) +{ + if (outwidth == 2) + ((short *)data)[i] = sample; + else + ((signed char *)data)[i] = sample >> 8; +} + + /* ================ ResampleSfx @@ -29,11 +46,11 @@ ResampleSfx */ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) { - int outcount; + int incount, outcount; int srcsample; - float stepscale; + float stepscale, samplefrac; int i; - int sample, samplefrac, fracstep; + int sample; sfxcache_t *sc; sc = (sfxcache_t *) Cache_Check (&sfx->cache); @@ -42,6 +59,7 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) stepscale = (float)inrate / shm->speed; // this is usually 0.5, 1, or 2 + incount = sc->length; outcount = sc->length / stepscale; sc->length = outcount; if (sc->loopstart != -1) @@ -64,21 +82,30 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) } else { -// general case - samplefrac = 0; - fracstep = stepscale*256; - for (i = 0; i < outcount; i++) + if (stepscale < 1) { - srcsample = samplefrac >> 8; - samplefrac += fracstep; - if (inwidth == 2) - sample = LittleShort ( ((short *)data)[srcsample] ); - else - sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; - if (sc->width == 2) - ((short *)sc->data)[i] = sample; - else - ((signed char *)sc->data)[i] = sample >> 8; +// upsampling + for (i = 0, samplefrac = 0; i < outcount; i++, samplefrac += stepscale) + { + int srcsample1 = CLAMP(0, floor(samplefrac), incount - 1); + int srcsample2 = CLAMP(0, ceil(samplefrac), incount - 1); + + float srcsample1weight = samplefrac - floor(samplefrac); + float srcsample2weight = 1 - srcsample1weight; + + sample = (srcsample1weight * getsample(data, inwidth, srcsample1)) + + (srcsample2weight * getsample(data, inwidth, srcsample2)); + putsample(sc->data, sc->width, i, sample); + } + } + else + { +// general case / downsampling + for (i = 0, samplefrac = 0; i < outcount; i++, samplefrac += stepscale) + { + sample = getsample(data, inwidth, (int)samplefrac); + putsample(sc->data, sc->width, i, sample); + } } } } From b8b5a7ed1c76c295a3c91c04ca98e419c1bca6be Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Tue, 27 Dec 2011 21:23:33 -0700 Subject: [PATCH 02/11] fix stupid bug and linear interpolation sounds OK but not amazing, as expected --- Quake/snd_mem.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index 19e47c30..dd7e2d6f 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -47,7 +47,6 @@ ResampleSfx static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) { int incount, outcount; - int srcsample; float stepscale, samplefrac; int i; int sample; @@ -90,8 +89,8 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) int srcsample1 = CLAMP(0, floor(samplefrac), incount - 1); int srcsample2 = CLAMP(0, ceil(samplefrac), incount - 1); - float srcsample1weight = samplefrac - floor(samplefrac); - float srcsample2weight = 1 - srcsample1weight; + float srcsample2weight = samplefrac - floor(samplefrac); + float srcsample1weight = 1 - srcsample2weight; sample = (srcsample1weight * getsample(data, inwidth, srcsample1)) + (srcsample2weight * getsample(data, inwidth, srcsample2)); From 0264ed1e22316c429f8b134b6c059f1026cd7e0a Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Tue, 27 Dec 2011 23:18:00 -0700 Subject: [PATCH 03/11] add a "low-pass filter" --- Quake/snd_mem.c | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index dd7e2d6f..5780ca28 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -22,7 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" -static int getsample(byte *data, int inwidth, int srcsample) +static int getsamplefromfile(byte *data, int inwidth, int srcsample) { if (inwidth == 2) return LittleShort ( ((short *)data)[srcsample] ); @@ -30,6 +30,14 @@ static int getsample(byte *data, int inwidth, int srcsample) return (int)( (unsigned char)(data[srcsample]) - 128) << 8; } +static int getsample(byte *data, int inwidth, int srcsample) +{ + if (inwidth == 2) + return ((short *)data)[srcsample]; + else + return (int)(((signed char *)data)[srcsample]) << 8; +} + static void putsample(byte *data, int outwidth, int i, int sample) { if (outwidth == 2) @@ -87,22 +95,46 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) for (i = 0, samplefrac = 0; i < outcount; i++, samplefrac += stepscale) { int srcsample1 = CLAMP(0, floor(samplefrac), incount - 1); - int srcsample2 = CLAMP(0, ceil(samplefrac), incount - 1); + int srcsample2 = CLAMP(0, ceil(samplefrac), incount - 1); - float srcsample2weight = samplefrac - floor(samplefrac); - float srcsample1weight = 1 - srcsample2weight; - - sample = (srcsample1weight * getsample(data, inwidth, srcsample1)) - + (srcsample2weight * getsample(data, inwidth, srcsample2)); + // how far between the samples. in [0, 1]. + float mu = samplefrac - floor(samplefrac); + + float srcsample1weight = 1 - mu; + float srcsample2weight = mu; + + sample = (srcsample1weight * getsamplefromfile(data, inwidth, srcsample1)) + + (srcsample2weight * getsamplefromfile(data, inwidth, srcsample2)); putsample(sc->data, sc->width, i, sample); } + + // poor man's low pass filter: + // s[i] = (s[i] + s[i+1] + s[i+2] + s[i+3]) / 4 + + const int avg = 4; + char temp[sc->width * sc->length]; + memset(temp, 0, sc->width * sc->length); + for (i = 0; i < outcount; i++) + { + int64_t sum = 0; + int j; + for (j=0; jdata, sc->width, pos); + } + sum /= avg; + putsample(temp, sc->width, i, sum); + } + + memcpy(sc->data, temp, sc->width * sc->length); } else { // general case / downsampling for (i = 0, samplefrac = 0; i < outcount; i++, samplefrac += stepscale) { - sample = getsample(data, inwidth, (int)samplefrac); + sample = getsamplefromfile(data, inwidth, (int)samplefrac); putsample(sc->data, sc->width, i, sample); } } From 873cacda47cdf25d77d859ad89c6d69a728a2ca3 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Wed, 28 Dec 2011 00:43:31 -0700 Subject: [PATCH 04/11] force loading samples as 16-bit --- Quake/snd_mem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index 5780ca28..06f16674 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -76,7 +76,7 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) if (loadas8bit.value) sc->width = 1; else - sc->width = inwidth; + sc->width = 2; sc->stereo = 0; // resample / decimate to the current source rate @@ -195,7 +195,7 @@ sfxcache_t *S_LoadSound (sfx_t *s) stepscale = (float)info.rate / shm->speed; len = info.samples / stepscale; - len = len * info.width * info.channels; + len = len * 2 * info.channels; if (info.samples == 0 || len == 0) { From 34079f1ae179d3bbf743f09a6dfe2cb79b9f3caa Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Wed, 28 Dec 2011 01:18:17 -0700 Subject: [PATCH 05/11] lowpass filter: read samples centered around the dest sample --- Quake/snd_mem.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index 06f16674..4d4563e9 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -109,7 +109,6 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) } // poor man's low pass filter: - // s[i] = (s[i] + s[i+1] + s[i+2] + s[i+3]) / 4 const int avg = 4; char temp[sc->width * sc->length]; @@ -118,9 +117,9 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) { int64_t sum = 0; int j; - for (j=0; jdata, sc->width, pos); } sum /= avg; From ecaa3007b201a0f4302b6f0366d09b25772c0d15 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Wed, 28 Dec 2011 20:56:27 -0700 Subject: [PATCH 06/11] add a comment --- Quake/snd_mem.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index 4d4563e9..adf94b11 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -92,6 +92,11 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) if (stepscale < 1) { // upsampling + + // linearly interpolate between the two closest source samples. + // this alone sounds much better than id's method, but still produces + // high-frequency junk. + for (i = 0, samplefrac = 0; i < outcount; i++, samplefrac += stepscale) { int srcsample1 = CLAMP(0, floor(samplefrac), incount - 1); From 2367fe2c23c95874756708d4351e8195c1fa4b76 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Wed, 28 Dec 2011 21:39:31 -0700 Subject: [PATCH 07/11] make the box filter work in-place (O(box-width) memory instead of O(sound-length) memory) --- Quake/snd_mem.c | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index adf94b11..393a3887 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -113,25 +113,36 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) putsample(sc->data, sc->width, i, sample); } - // poor man's low pass filter: + // box filter + // FIXME: doesn't handle the start or end of the sound properly - const int avg = 4; - char temp[sc->width * sc->length]; - memset(temp, 0, sc->width * sc->length); + const int left = 2; + const int right = 2; + const int box_width = left + right + 1; + int history[left]; + memset(history, 0, sizeof(history)); + int box_sum = 0; for (i = 0; i < outcount; i++) { - int64_t sum = 0; + const int sample_at_i = getsample(sc->data, sc->width, i); + + box_sum += sample_at_i; + box_sum -= history[0]; + + const int newsample = box_sum / box_width; + + const int write_loc = CLAMP(0, i - right, outcount - 1); + + // before writing the sample at write_loc, copy it to the end of the history buffer int j; - for (j = (i-(avg/2)); j < (i-(avg/2)) + avg; j++) + for (j=0; j<(left-1); j++) { - int pos = CLAMP(0, j, outcount - 1); - sum += getsample(sc->data, sc->width, pos); + history[j] = history[j+1]; } - sum /= avg; - putsample(temp, sc->width, i, sum); + history[left-1] = getsample(sc->data, sc->width, write_loc); + + putsample(sc->data, sc->width, write_loc, newsample); } - - memcpy(sc->data, temp, sc->width * sc->length); } else { From a8e2811dc2d6031b5b8640b40ea282c6fc7b1853 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Thu, 29 Dec 2011 19:33:43 -0700 Subject: [PATCH 08/11] choose box filter width based on ratio of sampling rates --- Quake/snd_mem.c | 58 ++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index 393a3887..fc4b9ba4 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -116,32 +116,40 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) // box filter // FIXME: doesn't handle the start or end of the sound properly - const int left = 2; - const int right = 2; - const int box_width = left + right + 1; - int history[left]; - memset(history, 0, sizeof(history)); - int box_sum = 0; - for (i = 0; i < outcount; i++) - { - const int sample_at_i = getsample(sc->data, sc->width, i); - - box_sum += sample_at_i; - box_sum -= history[0]; - - const int newsample = box_sum / box_width; - - const int write_loc = CLAMP(0, i - right, outcount - 1); - - // before writing the sample at write_loc, copy it to the end of the history buffer - int j; - for (j=0; j<(left-1); j++) - { - history[j] = history[j+1]; + // box_half_width is the number of samples on each side of a given sample + // that are averaged together. + // for the most common case, 11025Hz => 44100Hz, i.e. stepscale = 0.25, + // I determined that a box width of 5 (i.e. a box_half_width of 2) produces + // the best sounding results. + const int box_half_width = CLAMP(0, (1 / (stepscale * 2)), 8); + + if (box_half_width > 0) + { + const int box_width = (2 * box_half_width) + 1; + int history[box_half_width]; + memset(history, 0, sizeof(history)); + int box_sum = 0; + for (i = 0; i < outcount; i++) + { + const int sample_at_i = getsample(sc->data, sc->width, i); + + box_sum += sample_at_i; + box_sum -= history[0]; + + const int newsample = box_sum / box_width; + + const int write_loc = CLAMP(0, i - box_half_width, outcount - 1); + + // before writing the sample at write_loc, copy it to the end of the history buffer + int j; + for (j=0; j<(box_half_width-1); j++) + { + history[j] = history[j+1]; + } + history[box_half_width-1] = getsample(sc->data, sc->width, write_loc); + + putsample(sc->data, sc->width, write_loc, newsample); } - history[left-1] = getsample(sc->data, sc->width, write_loc); - - putsample(sc->data, sc->width, write_loc, newsample); } } else From 07532b1a375f6ba9df2993fbe36363d1af7384c7 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Fri, 30 Dec 2011 11:41:39 -0700 Subject: [PATCH 09/11] box filter: handle start and end of sample by repeating the first and last sample --- Quake/snd_mem.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index fc4b9ba4..79809cf6 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -114,7 +114,6 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) } // box filter - // FIXME: doesn't handle the start or end of the sound properly // box_half_width is the number of samples on each side of a given sample // that are averaged together. @@ -129,26 +128,26 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) int history[box_half_width]; memset(history, 0, sizeof(history)); int box_sum = 0; - for (i = 0; i < outcount; i++) + for (i = 0; i < (outcount + box_half_width); i++) { - const int sample_at_i = getsample(sc->data, sc->width, i); - + const int sample_at_i = getsample(sc->data, sc->width, CLAMP(0, i, outcount - 1)); box_sum += sample_at_i; box_sum -= history[0]; - const int newsample = box_sum / box_width; - const int write_loc = CLAMP(0, i - box_half_width, outcount - 1); - // before writing the sample at write_loc, copy it to the end of the history buffer int j; for (j=0; j<(box_half_width-1); j++) { history[j] = history[j+1]; } - history[box_half_width-1] = getsample(sc->data, sc->width, write_loc); + const int write_loc = i - box_half_width; + history[box_half_width-1] = getsample(sc->data, sc->width, CLAMP(0, write_loc, outcount - 1)); - putsample(sc->data, sc->width, write_loc, newsample); + if (write_loc >= 0 && write_loc < outcount) + { + putsample(sc->data, sc->width, write_loc, newsample); + } } } } From caab884744ae5d27be8c3243528e13bdd3bc5801 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Fri, 30 Dec 2011 11:48:18 -0700 Subject: [PATCH 10/11] box filter: set the box width only based on the final sample frequency. we don't care what the original frequency was. --- Quake/snd_mem.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index 79809cf6..ae9c5699 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -117,10 +117,9 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) // box_half_width is the number of samples on each side of a given sample // that are averaged together. - // for the most common case, 11025Hz => 44100Hz, i.e. stepscale = 0.25, - // I determined that a box width of 5 (i.e. a box_half_width of 2) produces - // the best sounding results. - const int box_half_width = CLAMP(0, (1 / (stepscale * 2)), 8); + // for 44100Hz output, a box width of 5 (i.e. a box_half_width of 2) seems + // to sound the best + const int box_half_width = CLAMP(0, sc->speed / 22050, 4); if (box_half_width > 0) { From 6b337014049bde23cc8522dea7b94fa00be038eb Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Fri, 30 Dec 2011 11:55:20 -0700 Subject: [PATCH 11/11] comment code a bit better --- Quake/snd_mem.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c index ae9c5699..380be115 100644 --- a/Quake/snd_mem.c +++ b/Quake/snd_mem.c @@ -129,20 +129,25 @@ static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) int box_sum = 0; for (i = 0; i < (outcount + box_half_width); i++) { + // calculate the new sample we will write const int sample_at_i = getsample(sc->data, sc->width, CLAMP(0, i, outcount - 1)); box_sum += sample_at_i; box_sum -= history[0]; const int newsample = box_sum / box_width; - // before writing the sample at write_loc, copy it to the end of the history buffer + // shift the entries in the history buffer left, discarding the entry + // at history[0] and leaving a space at history[box_half_width-1] int j; for (j=0; j<(box_half_width-1); j++) { history[j] = history[j+1]; } + + // save the sample we are going to overwrite at history[box_half_width-1] const int write_loc = i - box_half_width; history[box_half_width-1] = getsample(sc->data, sc->width, CLAMP(0, write_loc, outcount - 1)); + // only write the new sample if it lies within the bounds of the output array if (write_loc >= 0 && write_loc < outcount) { putsample(sc->data, sc->width, write_loc, newsample);