shithub: sox

Download patch

ref: 2ce9ae35fc417bb9fb66ce7ca3dec12135bfc7be
parent: fe37415768718214125ee0a4cf9ae8d3237755ea
author: robs <robs>
date: Sun Sep 21 16:46:07 EDT 2008

new loudness effect

--- a/AUTHORS
+++ b/AUTHORS
@@ -17,7 +17,7 @@
 		EFFECTS: tempo, pad, bass, treble, delay, new reverb, new
 		flanger, soft-knee companding, speed via resampling, filters
 		makeover inc. gnuplot & octave plotting, splice, remix, norm,
-		contrast, new rate, spectrogram, new pitch, riaa;
+		contrast, new rate, spectrogram, new pitch, riaa, loudness;
                 new effects chain with buffering and any # channels.
                 OTHERS: open input files via URL, file merge, play
                 multiple files with mixed format types, play with replay-gain,
--- a/ChangeLog
+++ b/ChangeLog
@@ -44,7 +44,9 @@
 
 Effects:
 
-  o New `riaa' effect; RIAA vinyl playback EQ.  (robs)
+  o New `riaa' effect: RIAA vinyl playback EQ.  (robs)
+  o New `loudness' effect: gain control with ISO 226 loudness
+    compensation.  (robs)
   o New -b option for the norm effect; can be used to fix stereo
     imbalance.  (robs)
   o Change default settings for `rate' effect; add -s -L options to
--- a/README
+++ b/README
@@ -127,6 +127,7 @@
     o dcshift		Apply or remove DC offset
     o fade		Apply a fade-in and/or fade-out to the audio
     o gain		Apply gain or attenuation
+    o loudness		Gain control with ISO 226 loudness compensation
     o mcompand		Multi-band compression/expansion/limiting
     o norm		Normalise to 0dB (or other), or fix imbalance
     o vol		Adjust audio volume
--- a/soxeffect.7
+++ b/soxeffect.7
@@ -652,6 +652,23 @@
 and one audio output port can be used.  If found, the environment varible
 LADSPA_PATH will be used as search path for plugins.
 .TP
+\fBloudness [\fIgain\fR [\fIreference\fR]]
+Loudness control\*msimilar to the
+.B gain
+effect, but provides equalisation for the human auditory system.  See
+http://en.wikipedia.org/wiki/Loudness for a detailed description of
+loudness.  The gain is adjusted by the given
+.I gain
+parameter (usually negative) and the signal equalised according to ISO
+226 w.r.t. a reference level of 65dB, though an alternative
+.I reference
+level may be given if the original audio has been equalised for some
+other optimal level.
+.SP
+See also the
+.B gain
+effect.
+.TP
 \fBlowpass\fR [\fB\-1\fR|\fB\-2\fR] \fIfrequency\fR[\fBk\fR]\fR [\fRwidth\fR[\fBq\fR\^|\^\fBo\fR\^|\^\fBh\fR\^|\^\fBk\fR]]
 Apply a low-pass filter.
 See the description of the \fBhighpass\fR effect for details.
@@ -967,7 +984,7 @@
 audio.  If no quality option is given, the quality level used is `high'.
 .SP
 The `quick' algorithm uses cubic interpolation; all others use
-band-width limited interpolation.  The `quick' and `low' quality
+band-limited interpolation.  The `quick' and `low' quality
 algorithms have a `linear' phase response; for `medium', `high' and
 `very high', the phase response is configurable (see below), but
 defaults to `intermediate'.
@@ -1010,7 +1027,7 @@
 Any phase response (0 = minimum, 25 = intermediate, 50 = linear, 100 = maximum)
 T}
 \-s	Steep filter (band-width = 99%)
-\-b\ 74\-99.7	Any band-width %
+\-b\ 74\-99\*d7	Any band-width %
 \-a	Allow aliasing above the pass-band
 .TE
 .DT
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -28,15 +28,16 @@
 
 # Format with: !xargs echo|tr ' ' '\n'|sort|column|expand|sed 's/^/  /'
 set(effects_srcs
-  biquad          earwax          mixer           polyphas        speed
-  biquads         echo            noiseprof       rate            splice
-  chorus          echos           noisered        remix           stat
-  compand         fade            normalise       repeat          swap
-  compandt        fft4g           output          resample        synth
-  contrast        filter          pad             reverb          tempo
-  dcshift         flanger         pan             reverse         tremolo
-  delay           input           phaser          silence         trim
-  dither          mcompand        pitch           skeleff         vol
+  biquad          echo            noiseprof       remix           swap
+  biquads         echos           noisered        repeat          synth
+  chorus          fade            normalise       resample        tempo
+  compand         fft4g           output          reverb          tremolo
+  compandt        filter          pad             reverse         trim
+  contrast        flanger         pan             silence         vol
+  dcshift         input           phaser          skeleff
+  delay           loudness        pitch           speed
+  dither          mcompand        polyphas        splice
+  earwax          mixer           rate            stat
 )
 set(formats_srcs
   8svx            dat             ima-fmt         s3-fmt          u4-fmt
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -252,7 +252,7 @@
 	band.h biquad.c biquad.h biquads.c chorus.c compand.c compandt.c \
 	compandt.h contrast.c dcshift.c delay.c dither.c earwax.c echo.c \
 	echos.c effects.c effects.h effects_i.c fade.c fft4g.c fft4g.h \
-	fifo.h filter.c flanger.c input.c ladspa.c mcompand.c \
+	fifo.h filter.c flanger.c input.c ladspa.c loudness.c mcompand.c \
 	mixer.c noiseprof.c noisered.c noisered.h normalise.c output.c pad.c \
 	pan.c phaser.c pitch.c polyphas.c rabbit.c rate.c \
 	rate_filters.h rate_half_fir.h rate_poly_fir0.h rate_poly_fir.h \
--- a/src/effects.h
+++ b/src/effects.h
@@ -41,6 +41,7 @@
 #ifdef HAVE_LADSPA_H
   EFFECT(ladspa)
 #endif
+  EFFECT(loudness)
   EFFECT(lowpass)
   EFFECT(mcompand)
   EFFECT(mixer)
--- a/src/effects_i.c
+++ b/src/effects_i.c
@@ -68,6 +68,56 @@
   return a * (b / lsx_gcd(a, b));
 }
 
+/* Numerical Recipes cubic spline */
+
+void lsx_prepare_spline3(double const * x, double const * y, int n,
+    double start_1d, double end_1d, double * y_2d)
+{
+  double p, qn, sig, un, * u = lsx_malloc((n - 1) * sizeof(*u));
+  int i;
+
+  if (start_1d == HUGE_VAL)
+    y_2d[0] = u[0] = 0;      /* Start with natural spline or */
+  else {                     /* set the start first derivative */
+    y_2d[0] = -.5;
+    u[0] = (3 / (x[1] - x[0])) * ((y[1] - y[0]) / (x[1] - x[0]) - start_1d);
+  }
+
+  for (i = 1; i < n - 1; ++i) {
+    sig = (x[i] - x[i - 1]) / (x[i + 1] - x[i - 1]);
+    p = sig * y_2d[i - 1] + 2;
+    y_2d[i] = (sig - 1) / p;
+    u[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]) -
+           (y[i] - y[i - 1]) / (x[i] - x[i - 1]);
+    u[i] = (6 * u[i] / (x[i + 1] - x[i - 1]) - sig * u[i - 1]) / p;
+  }
+  if (end_1d == HUGE_VAL)
+    qn = un = 0;             /* End with natural spline or */
+  else {                     /* set the end first derivative */
+    qn = .5;
+    un = 3 / (x[n - 1] - x[n - 2]) * (end_1d - (y[n - 1] - y[n - 2]) / (x[n - 1] - x[n - 2]));
+  }
+  y_2d[n - 1] = (un - qn * u[n - 2]) / (qn * y_2d[n - 2] + 1);
+  for (i = n - 2; i >= 0; --i)
+    y_2d[i] = y_2d[i] * y_2d[i + 1] + u[i];
+  free(u);
+}
+
+double lsx_spline3(double const * x, double const * y, double const * y_2d,
+    int n, double x1)
+{
+  int     t, i[2] = {0, 0};
+  double  d, a, b;
+
+  for (i[1] = n - 1; i[1] - i[0] > 1; t = (i[1] + i[0]) >> 1, i[x[t] > x1] = t);
+  d = x[i[1]] - x[i[0]];
+  assert(d != 0);
+  a = (x[i[1]] - x1) / d;
+  b = (x1 - x[i[0]]) / d;
+  return a * y[i[0]] + b * y[i[1]] +
+    ((a * a * a - a) * y_2d[i[0]] + (b * b * b - b) * y_2d[i[1]]) * d * d / 6;
+}
+
 enum_item const lsx_wave_enum[] = {
   ENUM_ITEM(SOX_WAVE_,SINE)
   ENUM_ITEM(SOX_WAVE_,TRIANGLE)
@@ -279,6 +329,15 @@
   return sum;
 }
 
+int lsx_set_dft_length(int num_taps) /* Set to 4 x nearest power of 2 */
+{
+  int result, n = num_taps;
+  for (result = 8; n > 2; result <<= 1, n >>= 1);
+  result = range_limit(result, 4096, 131072);
+  assert(num_taps * 2 < result);
+  return result;
+}
+
 #include "fft4g.h"
 int * lsx_fft_br;
 double * lsx_fft_sc;
@@ -381,6 +440,24 @@
   int i, m = num_points - 1;
   for (i = 0; i < num_points; ++i) {
     h[i] *= 2. / m * (m / 2. - fabs(i - m / 2.));
+  }
+}
+
+void lsx_apply_blackman(double h[], const int num_points, double alpha)
+{
+  int i, m = num_points - 1;
+  for (i = 0; i < num_points; ++i) {
+    double x = 2 * M_PI * i / m;
+    h[i] *= (1 - alpha) *.5 - .5 * cos(x) + alpha * .5 * cos(2 * x);
+  }
+}
+
+void lsx_apply_blackman_nutall(double h[], const int num_points)
+{
+  int i, m = num_points - 1;
+  for (i = 0; i < num_points; ++i) {
+    double x = 2 * M_PI * i / m;
+    h[i] *= .3635819 - .4891775 * cos(x) + .1365995 * cos(2 * x) - .0106411 * cos(3 * x);
   }
 }
 
--- /dev/null
+++ b/src/loudness.c
@@ -1,0 +1,221 @@
+/* Effect: loudness filter     Copyright (c) 2008 robs@users.sourceforge.net
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "sox_i.h"
+#include "fft4g.h"
+#define  FIFO_SIZE_T int
+#include "fifo.h"
+#include <string.h>
+
+typedef struct {
+  int        dft_length, num_taps;
+  double   * coefs;
+} filter_t;
+
+typedef struct {
+  double     delta, start;
+  int        n;
+  size_t     samples_in, samples_out;
+  fifo_t     input_fifo, output_fifo;
+  filter_t   filter, * filter_ptr;
+} priv_t;
+
+static int create(sox_effect_t * effp, int argc, char **argv)
+{
+  priv_t * p = (priv_t *)effp->priv;
+  p->filter_ptr = &p->filter;
+  p->delta = -10;
+  p->start = 65;
+  p->n = 1023;
+  do {                    /* break-able block */
+    NUMERIC_PARAMETER(delta,-50 , 15)
+    NUMERIC_PARAMETER(start, 50 , 75)
+    NUMERIC_PARAMETER(n    ,127 ,2047)
+  } while (0);
+  p->n = 2 * p->n + 1;
+  return argc?  lsx_usage(effp) : SOX_SUCCESS;
+}
+
+static double * make_filter(int n, double start, double delta, double rate)
+{
+  static const struct {double f, af, lu, tf;} iso226_table[] = {
+    {   20,0.532,-31.6,78.5},{   25,0.506,-27.2,68.7},{ 31.5,0.480,-23.0,59.5},
+    {   40,0.455,-19.1,51.1},{   50,0.432,-15.9,44.0},{   63,0.409,-13.0,37.5},
+    {   80,0.387,-10.3,31.5},{  100,0.367, -8.1,26.5},{  125,0.349, -6.2,22.1},
+    {  160,0.330, -4.5,17.9},{  200,0.315, -3.1,14.4},{  250,0.301, -2.0,11.4},
+    {  315,0.288, -1.1, 8.6},{  400,0.276, -0.4, 6.2},{  500,0.267,  0.0, 4.4},
+    {  630,0.259,  0.3, 3.0},{  800,0.253,  0.5, 2.2},{ 1000,0.250,  0.0, 2.4},
+    { 1250,0.246, -2.7, 3.5},{ 1600,0.244, -4.1, 1.7},{ 2000,0.243, -1.0,-1.3},
+    { 2500,0.243,  1.7,-4.2},{ 3150,0.243,  2.5,-6.0},{ 4000,0.242,  1.2,-5.4},
+    { 5000,0.242, -2.1,-1.5},{ 6300,0.245, -7.1, 6.0},{ 8000,0.254,-11.2,12.6},
+    {10000,0.271,-10.7,13.9},{12500,0.301, -3.1,12.3},
+  };
+  #define LEN (array_length(iso226_table) + 2)
+  #define SPL(phon, t) (10 / t.af * log10(4.47e-3 * (pow(10., .025 * (phon)) - \
+          1.15) + pow(.4 * pow(10., (t.tf + t.lu) / 10 - 9), t.af)) - t.lu + 94)
+  double fs[LEN], spl[LEN], d[LEN], * work, * h;
+  int i, work_len;
+  
+  fs[0] = log(1.);
+  spl[0] = delta * .2;
+  for (i = 0; i < (int)LEN - 2; ++i) {
+    spl[i + 1] = SPL(start + delta, iso226_table[i]) -
+                 SPL(start        , iso226_table[i]);
+    fs[i + 1] = log(iso226_table[i].f);
+  }
+  fs[i + 1] = log(100000.);
+  spl[i + 1] = spl[0];
+  lsx_prepare_spline3(fs, spl, LEN, HUGE_VAL, HUGE_VAL, d);
+
+  for (work_len = 8192; work_len < rate / 2; work_len <<= 1);
+  work = lsx_calloc(work_len, sizeof(*work));
+  h = lsx_calloc(n, sizeof(*h));
+
+  for (i = 1; i <= work_len / 2; ++i) {
+    double f = rate * i / work_len;
+    double spl1 = f < 1? spl[0] : lsx_spline3(fs, spl, d, LEN, log(f));
+    work[i < work_len / 2 ? 2 * i : 1] = dB_to_linear(spl1);
+  }
+  lsx_safe_rdft(work_len, -1, work);
+  for (i = 0; i < n; ++i)
+    h[i] = work[(work_len - n / 2 + i) % work_len] * 2. / work_len;
+  lsx_apply_kaiser(h, n, lsx_kaiser_beta(40 + 2./3 * fabs(delta)));
+
+  free(work);
+  return h;
+  #undef SPL
+  #undef LEN
+}
+
+static int start(sox_effect_t * effp)
+{
+  priv_t * p = (priv_t *) effp->priv;
+  int i, half = p->n / 2, dft_length = lsx_set_dft_length(p->n);
+  double * h;
+
+  if (p->delta == 0)
+    return SOX_EFF_NULL;
+
+  if (!p->filter_ptr->num_taps) {
+    h = make_filter(p->n, p->start, p->delta, effp->in_signal.rate);
+    p->filter_ptr->coefs = lsx_calloc(dft_length, sizeof(*p->filter_ptr->coefs));
+    for (i = 0; i < p->n; ++i)
+      p->filter_ptr->coefs[(i + dft_length - p->n + 1) & (dft_length - 1)]
+          = h[i] / dft_length * 2;
+    free(h);
+    p->filter_ptr->num_taps = p->n;
+    p->filter_ptr->dft_length = dft_length;
+    lsx_safe_rdft(dft_length, 1, p->filter_ptr->coefs);
+  }
+  fifo_create(&p->input_fifo, (int)sizeof(double));
+  memset(fifo_reserve(&p->input_fifo, half), 0, sizeof(double) * half);
+  fifo_create(&p->output_fifo, (int)sizeof(double));
+  return SOX_SUCCESS;
+}
+
+static void filter(priv_t * p)
+{
+  int i, num_in = max(0, fifo_occupancy(&p->input_fifo));
+  filter_t const * f = p->filter_ptr;
+  int const overlap = f->num_taps - 1;
+  double * output;
+
+  while (num_in >= f->dft_length) {
+    double const * input = fifo_read_ptr(&p->input_fifo);
+    fifo_read(&p->input_fifo, f->dft_length - overlap, NULL);
+    num_in -= f->dft_length - overlap;
+
+    output = fifo_reserve(&p->output_fifo, f->dft_length);
+    fifo_trim_by(&p->output_fifo, overlap);
+    memcpy(output, input, f->dft_length * sizeof(*output));
+
+    lsx_rdft(f->dft_length, 1, output, lsx_fft_br, lsx_fft_sc);
+    output[0] *= f->coefs[0];
+    output[1] *= f->coefs[1];
+    for (i = 2; i < f->dft_length; i += 2) {
+      double tmp = output[i];
+      output[i  ] = f->coefs[i  ] * tmp - f->coefs[i+1] * output[i+1];
+      output[i+1] = f->coefs[i+1] * tmp + f->coefs[i  ] * output[i+1];
+    }
+    lsx_rdft(f->dft_length, -1, output, lsx_fft_br, lsx_fft_sc);
+  }
+}
+
+static int flow(sox_effect_t * effp, const sox_sample_t * ibuf,
+                sox_sample_t * obuf, size_t * isamp, size_t * osamp)
+{
+  priv_t * p = (priv_t *)effp->priv;
+  size_t i, odone = min(*osamp, (size_t)fifo_occupancy(&p->output_fifo));
+  double const * s = fifo_read(&p->output_fifo, (int)odone, NULL);
+
+  for (i = 0; i < odone; ++i)
+    *obuf++ = SOX_FLOAT_64BIT_TO_SAMPLE(*s++, effp->clips);
+  p->samples_out += odone;
+
+  if (*isamp && odone < *osamp) {
+    double * t = fifo_write(&p->input_fifo, (int)*isamp, NULL);
+    p->samples_in += (int)*isamp;
+
+    for (i = *isamp; i; --i)
+      *t++ = SOX_SAMPLE_TO_FLOAT_64BIT(*ibuf++, effp->clips);
+    filter(p);
+  }
+  else *isamp = 0;
+  *osamp = odone;
+  return SOX_SUCCESS;
+}
+
+static int drain(sox_effect_t * effp, sox_sample_t * obuf, size_t * osamp)
+{
+  priv_t * p = (priv_t *)effp->priv;
+  static size_t isamp = 0;
+  size_t samples_out = p->samples_in;
+  size_t remaining = samples_out - p->samples_out;
+  double * buff = lsx_calloc(1024, sizeof(*buff));
+
+  if ((int)remaining > 0) {
+    while ((size_t)fifo_occupancy(&p->output_fifo) < remaining) {
+      fifo_write(&p->input_fifo, 1024, buff);
+      p->samples_in += 1024;
+      filter(p);
+    }
+    fifo_trim_to(&p->output_fifo, (int)remaining);
+    p->samples_in = 0;
+  }
+  free(buff);
+  return flow(effp, 0, obuf, &isamp, osamp);
+}
+
+static int stop(sox_effect_t * effp)
+{
+  priv_t * p = (priv_t *) effp->priv;
+
+  fifo_delete(&p->input_fifo);
+  fifo_delete(&p->output_fifo);
+  free(p->filter_ptr->coefs);
+  memset(p->filter_ptr, 0, sizeof(*p->filter_ptr));
+  return SOX_SUCCESS;
+}
+
+sox_effect_handler_t const * sox_loudness_effect_fn(void)
+{
+  static sox_effect_handler_t handler = {
+    "loudness", "[gain [ref]]", 0,
+    create, start, flow, drain, stop, NULL, sizeof(priv_t)
+  };
+  return &handler;
+}
--- a/src/rate.c
+++ b/src/rate.c
@@ -301,15 +301,6 @@
   free(work);
 }
 
-static int set_dft_length(int num_taps) /* Set to 4 x nearest power of 2 */
-{
-  int result, n = num_taps;
-  for (result = 8; n > 2; result <<= 1, n >>= 1);
-  result = range_limit(result, 4096, 131072);
-  assert(num_taps * 2 < result);
-  return result;
-}
-
 static void half_band_filter_init(rate_shared_t * p, unsigned which,
     int num_taps, sample_t const h[], double Fp, double atten, int multiplier,
     double phase, sox_bool allow_aliasing)
@@ -320,7 +311,7 @@
   if (f->num_taps)
     return;
   if (h) {
-    dft_length = set_dft_length(num_taps);
+    dft_length = lsx_set_dft_length(num_taps);
     f->coefs = calloc(dft_length, sizeof(*f->coefs));
     for (i = 0; i < num_taps; ++i)
       f->coefs[(i + dft_length - num_taps + 1) & (dft_length - 1)]
@@ -337,7 +328,7 @@
       fir_to_phase(&h, &num_taps, &f->post_peak, phase);
     else f->post_peak = num_taps / 2;
 
-    dft_length = set_dft_length(num_taps);
+    dft_length = lsx_set_dft_length(num_taps);
     f->coefs = calloc(dft_length, sizeof(*f->coefs));
     for (i = 0; i < num_taps; ++i)
       f->coefs[(i + dft_length - num_taps + 1) & (dft_length - 1)]
--- a/src/sox_i.h
+++ b/src/sox_i.h
@@ -61,7 +61,13 @@
 unsigned lsx_gcd(unsigned a, unsigned b);
 unsigned lsx_lcm(unsigned a, unsigned b);
 
+void lsx_prepare_spline3(double const * x, double const * y, int n,
+    double start_1d, double end_1d, double * y_2d);
+double lsx_spline3(double const * x, double const * y, double const * y_2d,
+    int n, double x1);
+
 double lsx_bessel_I_0(double x);
+int lsx_set_dft_length(int num_taps);
 extern int * lsx_fft_br;
 extern double * lsx_fft_sc;
 void lsx_safe_rdft(int len, int type, double * d);
@@ -72,6 +78,8 @@
 void lsx_apply_hann(double h[], const int num_points);
 void lsx_apply_hamming(double h[], const int num_points);
 void lsx_apply_bartlett(double h[], const int num_points);
+void lsx_apply_blackman(double h[], const int num_points, double alpha);
+void lsx_apply_blackman_nutall(double h[], const int num_points);
 double lsx_kaiser_beta(double att);
 void lsx_apply_kaiser(double h[], const int num_points, double beta);
 
--- a/src/test-srcs
+++ b/src/test-srcs
@@ -20,15 +20,7 @@
   # Create input file
   $SOX -r $1 -n -twavpcm $IN synth 20 sin 0:`expr $1 / 2` gain -2 
   for src in \
-      "polyphase" \
-      "rabbit -c0" \
-      "rabbit -c1" \
-      "rate -h" \
-      "rate -v" \
-      "resample -q" \
-      "resample -ql" \
-      "ssrc" \
-      "ssrc_hp" \
+      "rate -vsL" \
       ; do
     echo $src
     case "$src" in