Support 56kHz to 19.2kHz gain analysis (Patch v4)
authorEarl Chew <earl_chew@yahoo.com>
Thu, 23 Feb 2012 00:57:54 +0000 (16:57 -0800)
committerErik de Castro Lopo <erikd@mega-nerd.com>
Sun, 26 Feb 2012 01:19:56 +0000 (12:19 +1100)
This implementation uses decimation to generate an estimate of the
required ReplayGain adjustment for tracks sampled at high rates.

This approach avoids having to generate filters with commensurately more taps,
and also the subsequent effect on performance as these additional
taps are evaluated for high sample rate tracks.

Filter table entries with coefficients that are unchanged are
marked /* ORIGINAL */.

The remaining entries are new and have coefficient values obtained
from src/utils/loudness/loudness.sci. See:

        http://lists.xiph.org/pipermail/flac-dev/2012-February/003220.html

Because these filter coefficients can be generated from a known source,
they are preferred to the FooBar2000 coefficients whose provenance is
unknown.

Signed-off-by: Earl Chew <earl_chew@yahoo.com>
include/share/replaygain_analysis.h
src/share/grabbag/replaygain.c
src/share/replaygain_analysis/replaygain_analysis.c

index 6aef649..abb827f 100644 (file)
@@ -47,8 +47,8 @@ typedef float   Float_t;         /* Type used for filtering */
 extern Float_t ReplayGainReferenceLoudness; /* in dB SPL, currently == 89.0 */
 
 int     InitGainAnalysis ( long samplefreq );
+int     ValidGainFrequency ( long samplefreq );
 int     AnalyzeSamples   ( const Float_t* left_samples, const Float_t* right_samples, size_t num_samples, int num_channels );
-int            ResetSampleFrequency ( long samplefreq );
 Float_t GetTitleGain     ( void );
 Float_t GetAlbumGain     ( void );
 
index 4243796..8191a5c 100644 (file)
@@ -118,25 +118,7 @@ static FLAC__bool append_tag_(FLAC__StreamMetadata *block, const char *format, c
 
 FLAC__bool grabbag__replaygain_is_valid_sample_frequency(unsigned sample_frequency)
 {
-       static const unsigned valid_sample_rates[] = {
-               8000,
-               11025,
-               12000,
-               16000,
-               22050,
-               24000,
-               32000,
-               44100,
-               48000
-       };
-       static const unsigned n_valid_sample_rates = sizeof(valid_sample_rates) / sizeof(valid_sample_rates[0]);
-
-       unsigned i;
-
-       for(i = 0; i < n_valid_sample_rates; i++)
-               if(sample_frequency == valid_sample_rates[i])
-                       return true;
-       return false;
+        return ValidGainFrequency( sample_frequency );
 }
 
 FLAC__bool grabbag__replaygain_init(unsigned sample_frequency)
index 5b61716..5bd4965 100644 (file)
@@ -110,43 +110,29 @@ typedef signed int      Int32_t;
 #define YULE_ORDER         10
 #define BUTTER_ORDER        2
 #define RMS_PERCENTILE      0.95        /* percentile which is louder than the proposed level */
-#define MAX_SAMP_FREQ   48000           /* maximum allowed sample frequency [Hz] */
 #define RMS_WINDOW_TIME    50           /* Time slice size [ms] */
 #define STEPS_per_dB      100.          /* Table entries per dB */
 #define MAX_dB            120.          /* Table entries for 0...MAX_dB (normal max. values are 70...80 dB) */
 
 #define MAX_ORDER               (BUTTER_ORDER > YULE_ORDER ? BUTTER_ORDER : YULE_ORDER)
-/* [JEC] the following was originally #defined as:
- *   (size_t) (MAX_SAMP_FREQ * RMS_WINDOW_TIME / 1000)
- * but that seemed to fail to take into account the ceil() part of the
- * sampleWindow calculation in ResetSampleFrequency(), and was causing
- * buffer overflows for 48kHz analysis, hence the +1.
- */
-/* [JEC] WATCHOUT: if MAX_SAMP_FREQ * RMS_WINDOW_TIME / 1000 is not an
- * integer it must be manually rounded up.  there is a limit on the
- * kind of calculation that can be done in array size definition (e.g.
- * Sun Forte compiler cannot handle any floating point terms).
- */
-#define MAX_SAMPLES_PER_WINDOW  (size_t) (MAX_SAMP_FREQ * RMS_WINDOW_TIME / 1000 + 1)   /* max. Samples per Time slice */
 #define PINK_REF                64.82 /* 298640883795 */                          /* calibration value */
 
 static Float_t          linprebuf [MAX_ORDER * 2];
 static Float_t*         linpre;                                          /* left input samples, with pre-buffer */
-static Float_t          lstepbuf  [MAX_SAMPLES_PER_WINDOW + MAX_ORDER];
+static Float_t*         lstepbuf;
 static Float_t*         lstep;                                           /* left "first step" (i.e. post first filter) samples */
-static Float_t          loutbuf   [MAX_SAMPLES_PER_WINDOW + MAX_ORDER];
+static Float_t*         loutbuf;
 static Float_t*         lout;                                            /* left "out" (i.e. post second filter) samples */
 static Float_t          rinprebuf [MAX_ORDER * 2];
 static Float_t*         rinpre;                                          /* right input samples ... */
-static Float_t          rstepbuf  [MAX_SAMPLES_PER_WINDOW + MAX_ORDER];
+static Float_t*         rstepbuf;
 static Float_t*         rstep;
-static Float_t          routbuf   [MAX_SAMPLES_PER_WINDOW + MAX_ORDER];
+static Float_t*         routbuf;
 static Float_t*         rout;
 static unsigned int              sampleWindow;                           /* number of samples required to reach number of milliseconds required for RMS window */
 static unsigned long    totsamp;
 static double           lsum;
 static double           rsum;
-static int              freqindex;
 #if 0
 static Uint32_t  A [(size_t)(STEPS_per_dB * MAX_dB)];
 static Uint32_t  B [(size_t)(STEPS_per_dB * MAX_dB)];
@@ -156,59 +142,128 @@ static Uint32_t  A [120 * 100];
 static Uint32_t  B [120 * 100];
 #endif
 
-/* for each filter:
-   [0] 48 kHz, [1] 44.1 kHz, [2] 32 kHz, [3] 24 kHz, [4] 22050 Hz, [5] 16 kHz, [6] 12 kHz, [7] is 11025 Hz, [8] 8 kHz */
-
 #ifdef _MSC_VER
 #pragma warning ( disable : 4305 )
 #endif
 
-static const Float_t  AYule [9] [11] = {
-    { 1., -3.84664617118067,  7.81501653005538,-11.34170355132042, 13.05504219327545,-12.28759895145294,  9.48293806319790, -5.87257861775999,  2.75465861874613, -0.86984376593551, 0.13919314567432 },
-    { 1., -3.47845948550071,  6.36317777566148, -8.54751527471874,  9.47693607801280, -8.81498681370155,  6.85401540936998, -4.39470996079559,  2.19611684890774, -0.75104302451432, 0.13149317958808 },
-    { 1., -2.37898834973084,  2.84868151156327, -2.64577170229825,  2.23697657451713, -1.67148153367602,  1.00595954808547, -0.45953458054983,  0.16378164858596, -0.05032077717131, 0.02347897407020 },
-    { 1., -1.61273165137247,  1.07977492259970, -0.25656257754070, -0.16276719120440, -0.22638893773906,  0.39120800788284, -0.22138138954925,  0.04500235387352,  0.02005851806501, 0.00302439095741 },
-    { 1., -1.49858979367799,  0.87350271418188,  0.12205022308084, -0.80774944671438,  0.47854794562326, -0.12453458140019, -0.04067510197014,  0.08333755284107, -0.04237348025746, 0.02977207319925 },
-    { 1., -0.62820619233671,  0.29661783706366, -0.37256372942400,  0.00213767857124, -0.42029820170918,  0.22199650564824,  0.00613424350682,  0.06747620744683,  0.05784820375801, 0.03222754072173 },
-    { 1., -1.04800335126349,  0.29156311971249, -0.26806001042947,  0.00819999645858,  0.45054734505008, -0.33032403314006,  0.06739368333110, -0.04784254229033,  0.01639907836189, 0.01807364323573 },
-    { 1., -0.51035327095184, -0.31863563325245, -0.20256413484477,  0.14728154134330,  0.38952639978999, -0.23313271880868, -0.05246019024463, -0.02505961724053,  0.02442357316099, 0.01818801111503 },
-    { 1., -0.25049871956020, -0.43193942311114, -0.03424681017675, -0.04678328784242,  0.26408300200955,  0.15113130533216, -0.17556493366449, -0.18823009262115,  0.05477720428674, 0.04704409688120 }
-};
-
-static const Float_t  BYule [9] [11] = {
-    { 0.03857599435200, -0.02160367184185, -0.00123395316851, -0.00009291677959, -0.01655260341619,  0.02161526843274, -0.02074045215285,  0.00594298065125,  0.00306428023191,  0.00012025322027,  0.00288463683916 },
-    { 0.05418656406430, -0.02911007808948, -0.00848709379851, -0.00851165645469, -0.00834990904936,  0.02245293253339, -0.02596338512915,  0.01624864962975, -0.00240879051584,  0.00674613682247, -0.00187763777362 },
-    { 0.15457299681924, -0.09331049056315, -0.06247880153653,  0.02163541888798, -0.05588393329856,  0.04781476674921,  0.00222312597743,  0.03174092540049, -0.01390589421898,  0.00651420667831, -0.00881362733839 },
-    { 0.30296907319327, -0.22613988682123, -0.08587323730772,  0.03282930172664, -0.00915702933434, -0.02364141202522, -0.00584456039913,  0.06276101321749, -0.00000828086748,  0.00205861885564, -0.02950134983287 },
-    { 0.33642304856132, -0.25572241425570, -0.11828570177555,  0.11921148675203, -0.07834489609479, -0.00469977914380, -0.00589500224440,  0.05724228140351,  0.00832043980773, -0.01635381384540, -0.01760176568150 },
-    { 0.44915256608450, -0.14351757464547, -0.22784394429749, -0.01419140100551,  0.04078262797139, -0.12398163381748,  0.04097565135648,  0.10478503600251, -0.01863887810927, -0.03193428438915,  0.00541907748707 },
-    { 0.56619470757641, -0.75464456939302,  0.16242137742230,  0.16744243493672, -0.18901604199609,  0.30931782841830, -0.27562961986224,  0.00647310677246,  0.08647503780351, -0.03788984554840, -0.00588215443421 },
-    { 0.58100494960553, -0.53174909058578, -0.14289799034253,  0.17520704835522,  0.02377945217615,  0.15558449135573, -0.25344790059353,  0.01628462406333,  0.06920467763959, -0.03721611395801, -0.00749618797172 },
-    { 0.53648789255105, -0.42163034350696, -0.00275953611929,  0.04267842219415, -0.10214864179676,  0.14590772289388, -0.02459864859345, -0.11202315195388, -0.04060034127000,  0.04788665548180, -0.02217936801134 }
+struct ReplayGainFilter {
+    long rate;
+    unsigned downsample;
+    Float_t BYule[YULE_ORDER+1];
+    Float_t AYule[YULE_ORDER+1];
+    Float_t BButter[BUTTER_ORDER+1];
+    Float_t AButter[BUTTER_ORDER+1];
 };
 
-static const Float_t  AButter [9] [3] = {
-    { 1., -1.97223372919527, 0.97261396931306 },
-    { 1., -1.96977855582618, 0.97022847566350 },
-    { 1., -1.95835380975398, 0.95920349965459 },
-    { 1., -1.95002759149878, 0.95124613669835 },
-    { 1., -1.94561023566527, 0.94705070426118 },
-    { 1., -1.92783286977036, 0.93034775234268 },
-    { 1., -1.91858953033784, 0.92177618768381 },
-    { 1., -1.91542108074780, 0.91885558323625 },
-    { 1., -1.88903307939452, 0.89487434461664 }
-};
+static struct ReplayGainFilter *replaygainfilter;
+
+static const struct ReplayGainFilter ReplayGainFilters[] = {
+
+    {
+        48000, 0, /* ORIGINAL */
+        { 0.03857599435200,  -0.02160367184185,  -0.00123395316851,  -0.00009291677959,  -0.01655260341619,   0.02161526843274,  -0.02074045215285,   0.00594298065125,   0.00306428023191,   0.00012025322027,   0.00288463683916 },
+        { 1.00000000000000,  -3.84664617118067,   7.81501653005538, -11.34170355132042,  13.05504219327545, -12.28759895145294,   9.48293806319790, -5.87257861775999,   2.75465861874613,   -0.86984376593551,   0.13919314567432 },
+        { 0.98621192462708,  -1.97242384925416,   0.98621192462708 },
+        { 1.00000000000000,  -1.97223372919527,   0.97261396931306 },
+    },
+
+    {
+        44100, 0, /* ORIGINAL */
+        { 0.05418656406430,  -0.02911007808948,  -0.00848709379851,  -0.00851165645469,  -0.00834990904936,   0.02245293253339,  -0.02596338512915,   0.01624864962975,  -0.00240879051584,   0.00674613682247,  -0.00187763777362 },
+        { 1.00000000000000,  -3.47845948550071,   6.36317777566148,  -8.54751527471874,   9.47693607801280,  -8.81498681370155,   6.85401540936998,  -4.39470996079559,   2.19611684890774,  -0.75104302451432,   0.13149317958808 },
+        { 0.98500175787242,  -1.97000351574484,   0.98500175787242 },
+        { 1.00000000000000,  -1.96977855582618,   0.97022847566350 },
+    },
+
+    {
+        37800, 0,
+        { 0.10296717174470,  -0.04877975583256,  -0.02878009075237,  -0.03519509188311,   0.02888717172493,  -0.00609872684844,   0.00209851217112,   0.00911704668543,   0.01154404718589,  -0.00630293688700,   0.00107527155228 },
+        { 1.00000000000000,  -2.64848054923531,   3.58406058405771,  -3.83794914179161,   3.90142345804575,  -3.50179818637243,   2.67085284083076,  -1.82581142372418,   1.09530368139801,  -0.47689017820395,   0.11171431535905 },
+        { 0.98252400815195,  -1.96504801630391,   0.98252400815195 },
+        { 1.00000000000000,  -1.96474258269041,   0.96535344991740 },
+    },
+
+    {
+        36000, 0,
+        { 0.11572297028613,  -0.04120916051252,  -0.04977731768022,  -0.01047308680426,   0.00750863219157,   0.00055507694408,   0.00140344192886,   0.01286095246036,   0.00998223033885,  -0.00725013810661,   0.00326503346879 },
+        { 1.00000000000000,  -2.43606802820871,   3.01907406973844,  -2.90372016038192,   2.67947188094303,  -2.17606479220391,   1.44912956803015,  -0.87785765549050,   0.53592202672557,  -0.26469344817509,   0.07495878059717 },
+        { 0.98165826840326,  -1.96331653680652,   0.98165826840326 },
+        { 1.00000000000000,  -1.96298008938934,   0.96365298422371 },
+    },
+
+    {
+        32000, 0, /* ORIGINAL */
+        { 0.15457299681924,  -0.09331049056315,  -0.06247880153653,   0.02163541888798,  -0.05588393329856,   0.04781476674921,   0.00222312597743,   0.03174092540049,  -0.01390589421898,   0.00651420667831,  -0.00881362733839 },
+        { 1.00000000000000,  -2.37898834973084,   2.84868151156327,  -2.64577170229825,   2.23697657451713,  -1.67148153367602,   1.00595954808547,  -0.45953458054983,   0.16378164858596,  -0.05032077717131,   0.02347897407020 },
+        { 0.97938932735214,  -1.95877865470428,   0.97938932735214 },
+        { 1.00000000000000,  -1.95835380975398,   0.95920349965459 },
+    },
+
+    {
+        28000, 0,
+        { 0.23882392323383,  -0.22007791534089,  -0.06014581950332,   0.05004458058021,  -0.03293111254977,   0.02348678189717,   0.04290549799671,  -0.00938141862174,   0.00015095146303,  -0.00712601540885,  -0.00626520210162 },
+        { 1.00000000000000,  -2.06894080899139,   1.76944699577212,  -0.81404732584187,   0.25418286850232,  -0.30340791669762,   0.35616884070937,  -0.14967310591258,  -0.07024154183279,   0.11078404345174,  -0.03551838002425 },
+        { 0.97647981663949,  -1.95295963327897,   0.97647981663949 },
+        { 1.00000000000000,  -1.95240635772520,   0.95351290883275 },
+
+    },
+
+    {
+        24000, 0, /* ORIGINAL */
+        { 0.30296907319327,  -0.22613988682123,  -0.08587323730772,   0.03282930172664,  -0.00915702933434,  -0.02364141202522,  -0.00584456039913,   0.06276101321749,  -0.00000828086748,   0.00205861885564,  -0.02950134983287 },
+        { 1.00000000000000,  -1.61273165137247,   1.07977492259970,  -0.25656257754070,  -0.16276719120440,  -0.22638893773906,   0.39120800788284,  -0.22138138954925,   0.04500235387352,   0.02005851806501,   0.00302439095741 },
+        { 0.97531843204928,  -1.95063686409857,   0.97531843204928 },
+        { 1.00000000000000,  -1.95002759149878,   0.95124613669835 },
+    },
+
+    {
+        22050, 0, /* ORIGINAL */
+        { 0.33642304856132,  -0.25572241425570,  -0.11828570177555,   0.11921148675203,  -0.07834489609479,  -0.00469977914380,  -0.00589500224440,   0.05724228140351,   0.00832043980773,  -0.01635381384540,  -0.01760176568150 },
+        { 1.00000000000000,  -1.49858979367799,   0.87350271418188,   0.12205022308084,  -0.80774944671438,   0.47854794562326,  -0.12453458140019,  -0.04067510197014,   0.08333755284107,  -0.04237348025746,   0.02977207319925 },
+        { 0.97316523498161,  -1.94633046996323,   0.97316523498161 },
+        { 1.00000000000000,  -1.94561023566527,   0.94705070426118 },
+    },
+
+    {
+        18900, 0,
+        { 0.38412657295385,  -0.44533729608120,   0.20426638066221,  -0.28031676047946,   0.31484202614802,  -0.26078311203207,   0.12925201224848,  -0.01141164696062,   0.03036522115769,  -0.03776339305406,   0.00692036603586 },
+        { 1.00000000000000,  -1.74403915585708,   1.96686095832499,  -2.10081452941881,   1.90753918182846,  -1.83814263754422,   1.36971352214969,  -0.77883609116398,   0.39266422457649,  -0.12529383592986,   0.05424760697665 },
+        { 0.96535326815829,  -1.93070653631658,   0.96535326815829 },
+        { 1.00000000000000,  -1.92950577983524,   0.93190729279793 },
+    },
+
+    {
+        16000, 0, /* ORIGINAL */
+        { 0.44915256608450,  -0.14351757464547,  -0.22784394429749,  -0.01419140100551,   0.04078262797139,  -0.12398163381748,   0.04097565135648,   0.10478503600251,  -0.01863887810927,  -0.03193428438915,   0.00541907748707 },
+        { 1.00000000000000,  -0.62820619233671,   0.29661783706366,  -0.37256372942400,   0.00213767857124,  -0.42029820170918,   0.22199650564824,   0.00613424350682,   0.06747620744683,   0.05784820375801,   0.03222754072173 },
+        { 0.96454515552826,  -1.92909031105652,   0.96454515552826 },
+        { 1.00000000000000,  -1.92783286977036,   0.93034775234268 },
+    },
+
+    {
+        12000, 0, /* ORIGINAL */
+        { 0.56619470757641,  -0.75464456939302,   0.16242137742230,   0.16744243493672,  -0.18901604199609,   0.30931782841830,  -0.27562961986224,   0.00647310677246,   0.08647503780351,  -0.03788984554840,  -0.00588215443421 },
+        { 1.00000000000000,  -1.04800335126349,   0.29156311971249,  -0.26806001042947,   0.00819999645858,   0.45054734505008,  -0.33032403314006,   0.06739368333110,  -0.04784254229033,   0.01639907836189,   0.01807364323573 },
+        { 0.96009142950541,  -1.92018285901082,   0.96009142950541 },
+        { 1.00000000000000,  -1.91858953033784,   0.92177618768381 },
+    },
+    
+    {
+        11025, 0, /* ORIGINAL */
+        { 0.58100494960553,  -0.53174909058578,  -0.14289799034253,   0.17520704835522,   0.02377945217615,   0.15558449135573,  -0.25344790059353,   0.01628462406333,   0.06920467763959,  -0.03721611395801,  -0.00749618797172 },
+        { 1.00000000000000,  -0.51035327095184,  -0.31863563325245,  -0.20256413484477,   0.14728154134330,   0.38952639978999,  -0.23313271880868,  -0.05246019024463,  -0.02505961724053,   0.02442357316099,   0.01818801111503 },
+        { 0.95856916599601,  -1.91713833199203,   0.95856916599601 },
+        { 1.00000000000000,  -1.91542108074780,   0.91885558323625 },
+    },
+
+    {
+        8000, 0, /* ORIGINAL */
+        { 0.53648789255105,  -0.42163034350696,  -0.00275953611929,   0.04267842219415,  -0.10214864179676,   0.14590772289388,  -0.02459864859345,  -0.11202315195388,  -0.04060034127000,   0.04788665548180,  -0.02217936801134 },
+        { 1.00000000000000,  -0.25049871956020,  -0.43193942311114,  -0.03424681017675,  -0.04678328784242,   0.26408300200955,   0.15113130533216,  -0.17556493366449,  -0.18823009262115,   0.05477720428674,   0.04704409688120 },
+        { 0.94597685600279,  -1.89195371200558,   0.94597685600279 },
+        { 1.00000000000000,  -1.88903307939452,   0.89487434461664 },
+    },
 
-static const Float_t  BButter [9] [3] = {
-    { 0.98621192462708, -1.97242384925416, 0.98621192462708 },
-    { 0.98500175787242, -1.97000351574484, 0.98500175787242 },
-    { 0.97938932735214, -1.95877865470428, 0.97938932735214 },
-    { 0.97531843204928, -1.95063686409857, 0.97531843204928 },
-    { 0.97316523498161, -1.94633046996323, 0.97316523498161 },
-    { 0.96454515552826, -1.92909031105652, 0.96454515552826 },
-    { 0.96009142950541, -1.92018285901082, 0.96009142950541 },
-    { 0.95856916599601, -1.91713833199203, 0.95856916599601 },
-    { 0.94597685600279, -1.89195371200558, 0.94597685600279 }
 };
 
 #ifdef _MSC_VER
@@ -218,44 +273,112 @@ static const Float_t  BButter [9] [3] = {
 /* When calling this procedure, make sure that ip[-order] and op[-order] point to real data! */
 
 static void
-filter ( const Float_t* input, Float_t* output, size_t nSamples, const Float_t* a, const Float_t* b, size_t order )
+filter ( const Float_t* input, Float_t* output, size_t nSamples, const Float_t* a, const Float_t* b, size_t order, unsigned downsample )
 {
     double  y;
     size_t  i;
     size_t  k;
 
-    for ( i = 0; i < nSamples; i++ ) {
-        y = input[i] * b[0];
-        for ( k = 1; k <= order; k++ )
-            y += input[i-k] * b[k] - output[i-k] * a[k];
+    const Float_t* input_head = input;
+    const Float_t* input_tail;
+
+    Float_t* output_head = output;
+    Float_t* output_tail;
+
+    for ( i = 0; i < nSamples; i++, input_head += downsample, ++output_head ) {
+
+        input_tail = input_head;
+        output_tail = output_head;
+
+        y = *input_head * b[0];
+
+        for ( k = 1; k <= order; k++ ) {
+            input_tail -= downsample;
+            --output_tail;
+            y += *input_tail * b[k] - *output_tail * a[k];
+        }
+
         output[i] = (Float_t)y;
     }
 }
 
 /* returns a INIT_GAIN_ANALYSIS_OK if successful, INIT_GAIN_ANALYSIS_ERROR if not */
 
-int
+static struct ReplayGainFilter*
+CreateGainFilter ( long samplefreq )
+{
+    unsigned i;
+    long maxrate = 0;
+    unsigned downsample = 1;
+    struct ReplayGainFilter* gainfilter = malloc(sizeof(*gainfilter));
+
+    if ( !gainfilter )
+        return 0;
+
+    while (1) {
+        for ( i = 0; i < sizeof(ReplayGainFilters)/sizeof(ReplayGainFilters[0]); ++i ) {
+            if (maxrate < ReplayGainFilters[i].rate)
+                maxrate = ReplayGainFilters[i].rate;
+
+            if ( ReplayGainFilters[i].rate == samplefreq ) {
+                *gainfilter = ReplayGainFilters[i];
+                gainfilter->downsample = downsample;
+                return gainfilter;
+            }
+        }
+
+        if (samplefreq < maxrate)
+            break;
+
+        while (samplefreq > maxrate) {
+            downsample *= 2;
+            samplefreq /= 2;
+        }
+    }
+
+    free(gainfilter);
+
+    return 0;
+}
+
+static void*
+ReallocateWindowBuffer(unsigned window_size, Float_t **window_buffer)
+{
+    void *p = realloc(
+        *window_buffer, sizeof(**window_buffer) * (window_size + MAX_ORDER));
+
+    if (p)
+        *window_buffer = p;
+
+    return p;
+}
+
+static int
 ResetSampleFrequency ( long samplefreq ) {
     int  i;
 
-    /* zero out initial values */
-    for ( i = 0; i < MAX_ORDER; i++ )
-        linprebuf[i] = lstepbuf[i] = loutbuf[i] = rinprebuf[i] = rstepbuf[i] = routbuf[i] = 0.;
+    free(replaygainfilter);
 
-    switch ( (int)(samplefreq) ) {
-        case 48000: freqindex = 0; break;
-        case 44100: freqindex = 1; break;
-        case 32000: freqindex = 2; break;
-        case 24000: freqindex = 3; break;
-        case 22050: freqindex = 4; break;
-        case 16000: freqindex = 5; break;
-        case 12000: freqindex = 6; break;
-        case 11025: freqindex = 7; break;
-        case  8000: freqindex = 8; break;
-        default:    return INIT_GAIN_ANALYSIS_ERROR;
-    }
+    replaygainfilter = CreateGainFilter( samplefreq );
+
+    if ( ! replaygainfilter)
+        return INIT_GAIN_ANALYSIS_ERROR;
 
     sampleWindow = (int) ceil ((double)samplefreq * (double)RMS_WINDOW_TIME / 1000.0);
+    sampleWindow =
+        (replaygainfilter->rate * RMS_WINDOW_TIME + 1000-1) / 1000;
+
+    if ( ! ReallocateWindowBuffer(sampleWindow, &lstepbuf) ||
+         ! ReallocateWindowBuffer(sampleWindow, &rstepbuf) ||
+         ! ReallocateWindowBuffer(sampleWindow, &loutbuf)  ||
+         ! ReallocateWindowBuffer(sampleWindow, &routbuf) ) {
+
+        return INIT_GAIN_ANALYSIS_ERROR;
+    }
+
+    /* zero out initial values */
+    for ( i = 0; i < MAX_ORDER; i++ )
+        linprebuf[i] = lstepbuf[i] = loutbuf[i] = rinprebuf[i] = rstepbuf[i] = routbuf[i] = 0.;
 
     lsum         = 0.;
     rsum         = 0.;
@@ -263,15 +386,25 @@ ResetSampleFrequency ( long samplefreq ) {
 
     memset ( A, 0, sizeof(A) );
 
-       return INIT_GAIN_ANALYSIS_OK;
+    return INIT_GAIN_ANALYSIS_OK;
+}
+
+int
+ValidGainFrequency ( long samplefreq )
+{
+    struct ReplayGainFilter* gainfilter = CreateGainFilter( samplefreq );
+
+    free(gainfilter);
+
+    return gainfilter != 0;
 }
 
 int
 InitGainAnalysis ( long samplefreq )
 {
-       if (ResetSampleFrequency(samplefreq) != INIT_GAIN_ANALYSIS_OK) {
-               return INIT_GAIN_ANALYSIS_ERROR;
-       }
+    if (ResetSampleFrequency(samplefreq) != INIT_GAIN_ANALYSIS_OK) {
+            return INIT_GAIN_ANALYSIS_ERROR;
+    }
 
     linpre       = linprebuf + MAX_ORDER;
     rinpre       = rinprebuf + MAX_ORDER;
@@ -290,13 +423,17 @@ InitGainAnalysis ( long samplefreq )
 int
 AnalyzeSamples ( const Float_t* left_samples, const Float_t* right_samples, size_t num_samples, int num_channels )
 {
+    unsigned        downsample = replaygainfilter->downsample;
     const Float_t*  curleft;
     const Float_t*  curright;
+    long            prebufsamples;
     long            batchsamples;
     long            cursamples;
     long            cursamplepos;
     int             i;
 
+    num_samples /= downsample;
+
     if ( num_samples == 0 )
         return GAIN_ANALYSIS_OK;
 
@@ -309,33 +446,35 @@ AnalyzeSamples ( const Float_t* left_samples, const Float_t* right_samples, size
     default: return GAIN_ANALYSIS_ERROR;
     }
 
-    if ( num_samples < MAX_ORDER ) {
-        memcpy ( linprebuf + MAX_ORDER, left_samples , num_samples * sizeof(Float_t) );
-        memcpy ( rinprebuf + MAX_ORDER, right_samples, num_samples * sizeof(Float_t) );
-    }
-    else {
-        memcpy ( linprebuf + MAX_ORDER, left_samples,  MAX_ORDER   * sizeof(Float_t) );
-        memcpy ( rinprebuf + MAX_ORDER, right_samples, MAX_ORDER   * sizeof(Float_t) );
+    prebufsamples = MAX_ORDER;
+    if (prebufsamples > num_samples)
+        prebufsamples = num_samples;
+
+    for ( i = 0; i < prebufsamples; ++i ) {
+        linprebuf[i+MAX_ORDER] = left_samples [i * downsample];
+        rinprebuf[i+MAX_ORDER] = right_samples[i * downsample];
     }
 
     while ( batchsamples > 0 ) {
         cursamples = batchsamples > (long)(sampleWindow-totsamp)  ?  (long)(sampleWindow - totsamp)  :  batchsamples;
         if ( cursamplepos < MAX_ORDER ) {
+            downsample = 1;
             curleft  = linpre+cursamplepos;
             curright = rinpre+cursamplepos;
             if (cursamples > MAX_ORDER - cursamplepos )
                 cursamples = MAX_ORDER - cursamplepos;
         }
         else {
-            curleft  = left_samples  + cursamplepos;
-            curright = right_samples + cursamplepos;
+            downsample = replaygainfilter->downsample;
+            curleft  = left_samples  + cursamplepos * downsample;
+            curright = right_samples + cursamplepos * downsample;
         }
 
-        filter ( curleft , lstep + totsamp, cursamples, AYule[freqindex], BYule[freqindex], YULE_ORDER );
-        filter ( curright, rstep + totsamp, cursamples, AYule[freqindex], BYule[freqindex], YULE_ORDER );
+        filter ( curleft , lstep + totsamp, cursamples, replaygainfilter->AYule, replaygainfilter->BYule, YULE_ORDER, downsample );
+        filter ( curright, rstep + totsamp, cursamples, replaygainfilter->AYule, replaygainfilter->BYule, YULE_ORDER, downsample );
 
-        filter ( lstep + totsamp, lout + totsamp, cursamples, AButter[freqindex], BButter[freqindex], BUTTER_ORDER );
-        filter ( rstep + totsamp, rout + totsamp, cursamples, AButter[freqindex], BButter[freqindex], BUTTER_ORDER );
+        filter ( lstep + totsamp, lout + totsamp, cursamples, replaygainfilter->AButter, replaygainfilter->BButter, BUTTER_ORDER, 1 );
+        filter ( rstep + totsamp, rout + totsamp, cursamples, replaygainfilter->AButter, replaygainfilter->BButter, BUTTER_ORDER, 1 );
 
         for ( i = 0; i < cursamples; i++ ) {             /* Get the squared values */
             lsum += lout [totsamp+i] * lout [totsamp+i];
@@ -361,6 +500,7 @@ AnalyzeSamples ( const Float_t* left_samples, const Float_t* right_samples, size
         if ( totsamp > sampleWindow )   /* somehow I really screwed up: Error in programming! Contact author about totsamp > sampleWindow */
             return GAIN_ANALYSIS_ERROR;
     }
+
     if ( num_samples < MAX_ORDER ) {
         memmove ( linprebuf,                           linprebuf + num_samples, (MAX_ORDER-num_samples) * sizeof(Float_t) );
         memmove ( rinprebuf,                           rinprebuf + num_samples, (MAX_ORDER-num_samples) * sizeof(Float_t) );
@@ -368,8 +508,15 @@ AnalyzeSamples ( const Float_t* left_samples, const Float_t* right_samples, size
         memcpy  ( rinprebuf + MAX_ORDER - num_samples, right_samples,         num_samples             * sizeof(Float_t) );
     }
     else {
-        memcpy  ( linprebuf, left_samples  + num_samples - MAX_ORDER, MAX_ORDER * sizeof(Float_t) );
-        memcpy  ( rinprebuf, right_samples + num_samples - MAX_ORDER, MAX_ORDER * sizeof(Float_t) );
+        downsample = replaygainfilter->downsample;
+
+        left_samples  += (num_samples - MAX_ORDER) * downsample;
+        right_samples += (num_samples - MAX_ORDER) * downsample;
+
+        for ( i = 0; i < MAX_ORDER; ++i ) {
+            linprebuf[i] = left_samples [i * downsample];
+            rinprebuf[i] = right_samples[i * downsample];
+        }
     }
 
     return GAIN_ANALYSIS_OK;