shithub: sox

Download patch

ref: 1fd5a4013de02f96e3b59514c745c67354ab33c3
parent: fa80a671a7ce6198fb0f904a7cc1b4d716d70d7e
author: robs <robs>
date: Mon Jan 26 13:36:28 EST 2009

new sinc filter effect

--- a/ChangeLog
+++ b/ChangeLog
@@ -26,6 +26,7 @@
   ated in  [F(ormat)] [E(ffect)]   Replacement             due after
   -------  ----------------------  ----------------------  -------
   14.3.0   E norm -b, norm -i      gain -B, gain -en       14.3.0 + 1 year
+  14.3.0   E filter                ~=sinc                  14.3.0 + 1 year
   14.3.0   F flac: libFLAC 1.1.2,3 libFLAC > 1.1.3         14.3.0 + 6 months
 
 Previously deprecated features (to be removed in future):
@@ -68,6 +69,7 @@
 
 Effects:
 
+  o New `sinc' FFT filter effect; replacement for `filter'.  (robs)
   o New `overdrive' effect.  (robs)
   o New `pluck' and `tpdf' types for `synth'.  (robs)
   o Can now set common parameters for multiple `synth' channels.  (robs)
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -28,16 +28,16 @@
 
 # Format with: !xargs echo|tr ' ' '\n'|sort|column|expand|sed 's/^/  /'
 set(effects_srcs
-  bend            dither          loudness        phaser          stat
-  biquad          earwax          lowfir          rate            stretch
-  biquads         echo            mcompand        remix           swap
-  chorus          echos           mixer           repeat          synth
-  compand         fade            noiseprof       reverb          tempo
-  compandt        fft4g           noisered        reverse         tremolo
-  contrast        filter          output          silence         trim
-  dcshift         flanger         overdrive       skeleff         vol
-  delay           gain            pad             speed
-  dft_filter      input           pan             splice
+  bend            dither          loudness        rate            stat
+  biquad          earwax          mcompand        remix           stretch
+  biquads         echo            mixer           repeat          swap
+  chorus          echos           noiseprof       reverb          synth
+  compand         fade            noisered        reverse         tempo
+  compandt        fft4g           output          silence         tremolo
+  contrast        filter          overdrive       sinc            trim
+  dcshift         flanger         pad             skeleff         vol
+  delay           gain            pan             speed
+  dft_filter      input           phaser          splice
 )
 set(formats_srcs
   8svx            dat             ima-fmt         s3-fmt          u3-fmt
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -279,7 +279,7 @@
 	rate_poly_fir0.h rate_poly_fir.h remix.c repeat.c reverb.c \
 	reverse.c silence.c skeleff.c speed.c splice.c stat.c swap.c stretch.c \
 	synth.c tempo.c tremolo.c trim.c vol.c overdrive.c effects_i_dsp.c \
-	dft_filter.c dft_filter.h lowfir.c
+	dft_filter.c dft_filter.h sinc.c
 if HAVE_PNG
     libsox_la_SOURCES += spectrogram.c
 endif
--- a/src/effects.h
+++ b/src/effects.h
@@ -44,7 +44,6 @@
   EFFECT(ladspa)
 #endif
   EFFECT(loudness)
-  EFFECT(lowfir)
   EFFECT(lowpass)
   EFFECT(mcompand)
   EFFECT(mixer)
@@ -68,6 +67,7 @@
   EFFECT(reverse)
   EFFECT(riaa)
   EFFECT(silence)
+  EFFECT(sinc)
 #ifdef HAVE_PNG
   EFFECT(spectrogram)
 #endif
--- a/src/effects_i_dsp.c
+++ b/src/effects_i_dsp.c
@@ -264,6 +264,18 @@
   return h;
 }
 
+int lsx_lpf_num_taps(double att, double tr_bw, int k)
+{                    /* TODO this could be cleaner, esp. for k != 0 */
+  int n;
+  if (att <= 80)
+    n = .25 / M_PI * (att - 7.95) / (2.285 * tr_bw) + .5;
+  else {
+    double n160 = (.0425* att - 1.4) / tr_bw;   /* Half order for att = 160 */
+    n = n160 * (16.556 / (att - 39.6) + .8625) + .5;  /* For att [80,160) */
+  }
+  return k? 2 * n : 2 * (n + (n & 1)) + 1; /* =1 %4 (0 phase 1/2 band) */
+}
+
 double * lsx_design_lpf(
     double Fp,      /* End of pass-band; ~= 0.01dB point */
     double Fc,      /* Start of stop-band */
@@ -280,18 +292,9 @@
   Fp /= Fn, Fc /= Fn;        /* Normalise to Fn = 1 */
   tr_bw = LSX_TO_6dB * (Fc-Fp); /* Transition band-width: 6dB to stop points */
 
-  if (*num_taps == 0) {        /* TODO this could be cleaner, esp. for k != 0 */
-    int n;
-    if (att <= 80)
-      n = .25 / M_PI * (att - 7.95) / (2.285 * tr_bw) + .5;
-    else {
-      double n160 = (.0425* att - 1.4) / tr_bw;   /* Half order for att = 160 */
-      n = n160 * (16.556 / (att - 39.6) + .8625) + .5;  /* For att [80,160) */
-    }
-    *num_taps = k? 2 * n : 2 * (n + (n & 1)) + 1; /* =1 %4 (0 phase 1/2 band) */
-  }
-  beta = att < 21 ? 0 : att < 50 ? .5842 * pow((att - 21), .4) + .07886 *
-      (att - 21) : att < 100 ? .1102 * (att - 8.7) : .1117 * att - 1.11;
+  if (!*num_taps)
+    *num_taps = lsx_lpf_num_taps(att, tr_bw, k);
+  beta = lsx_kaiser_beta(att);
   if (k)
     *num_taps = *num_taps * k - 1;
   else k = 1;
@@ -299,11 +302,27 @@
   return lsx_make_lpf(*num_taps, (Fc - tr_bw) / k, beta, (double)k);
 }
 
+#if 0
+static void printf_vector(FILE * f, double h[], int num_points)
+{
+  int i;
+  printf(
+  "# name: b\n"
+  "# type: matrix\n"
+  "# rows: %i\n"
+  "# columns: 1\n", num_points);
+  for (i = 0; i < num_points; ++i)
+    printf("%24.16e\n", h[i]);
+  exit(0);
+}
+#endif
+
 void lsx_fir_to_phase(double * * h, int * len,
     int * post_len, double phase0)
 {
   double * work, phase = (phase0 > 50 ? 100 - phase0 : phase0) / 50;
-  int work_len, begin, end, peak = 0, i = *len;
+  int work_len, begin, end, abs_peak = 0, peak = 0, i = *len;
+  double sum = 0, peak_sum = 0;
 
   for (work_len = 32; i > 1; work_len <<= 1, i >>= 1);
   work = calloc((size_t)work_len /* + 2 */, sizeof(*work)); /* +2: (UN)PACK */
@@ -310,9 +329,9 @@
   for (i = 0; i < *len; ++i) work[i] = (*h)[i];
 
   lsx_safe_rdft(work_len, 1, work); /* Cepstral: */
-  work[0] = log(fabs(work[0])), work[1] = log(fabs(work[1]));
+  work[0] = safe_log(work[0]), work[1] = safe_log(work[1]);
   for (i = 2; i < work_len; i += 2) {
-    work[i] = log(sqrt(sqr(work[i]) + sqr(work[i + 1])));
+    work[i] = safe_log(sqrt(sqr(work[i]) + sqr(work[i + 1])));
     work[i + 1] = 0;
   }
   lsx_safe_rdft(work_len, -1, work);
@@ -329,7 +348,7 @@
   UNPACK(work, work_len);
   unwrap_phase(work, work_len);
   PACK(work, work_len);
-  octave_out(stdout, work, work_len);
+  printf_vector(work, work_len);
 #endif
 
   for (i = 2; i < work_len; i += 2) /* Interpolate between linear & min phase */
@@ -344,10 +363,19 @@
   lsx_safe_rdft(work_len, -1, work);
   for (i = 0; i < work_len; ++i) work[i] *= 2. / work_len;
 
-  for (i = 1; i < work_len; ++i) if (work[i] > work[peak])  /* Find peak pos. */
-    peak = i;                                           /* N.B. peak val. > 0 */
-
-  lsx_debug("peak-pos=%i", peak);
+  for (i = 0; i < work_len; ++i) {  /* Find peak pos. */
+    sum += work[i];          
+    if (fabs(sum) > peak_sum) {
+      peak_sum = fabs(sum);
+      peak = i;
+    }
+    if (work[i] > work[abs_peak]) /* For debug check only */
+      abs_peak = i;
+  }
+  while (fabs(work[peak-1]) > fabs(work[peak]) && work[peak-1] * work[peak] > 0)
+    --peak;
+  lsx_debug("peak: sum@work[%i]=%g (val@work[%i]=%g)",
+      peak, peak_sum, abs_peak, work[abs_peak]);
   if (phase == 0)
     begin = 0;
   else if (phase == 1)
@@ -369,4 +397,56 @@
     (*h)[i] = work[begin + (phase0 > 50 ? *len - 1 - i : i)];
   *post_len = phase0 > 50 ? peak - begin : begin + *len - (peak + 1);
   free(work);
+}
+
+void lsx_plot_fir(double * h, int num_points, sox_rate_t rate, sox_plot_t type, char const * title, double y1, double y2)
+{
+  int i, N = lsx_set_dft_length(num_points);
+  if (type == sox_plot_gnuplot) {
+    double * h1 = lsx_calloc(N, sizeof(*h1));
+    double * H = lsx_malloc((N / 2 + 1) * sizeof(*H));
+    memcpy(h1, h, sizeof(*h1) * num_points);
+    lsx_power_spectrum(N, h1, H);
+    printf(
+        "# gnuplot file\n"
+        "set title '%s'\n"
+        "set xlabel 'Frequency (Hz)'\n"
+        "set ylabel 'Amplitude Response (dB)'\n"
+        "set grid xtics ytics\n"
+        "set key off\n"
+        "plot '-' with lines\n"
+        , title);
+    for (i = 0; i <= N/2; ++i)
+      printf("%g %g\n", i * rate / N, 10 * log10(H[i]));
+    printf(
+        "e\n"
+        "pause -1 'Hit return to continue'\n");
+    free(H);
+    free(h1);
+  }
+  else if (type == sox_plot_octave) {
+    printf("%% GNU Octave file (may also work with MATLAB(R) )\nb=[");
+    for (i = 0; i < num_points; ++i)
+      printf("%24.16e\n", h[i]);
+    printf("];\n"
+        "[h,w]=freqz(b,1,%i);\n"
+        "plot(%g*w/pi,20*log10(h))\n"
+        "title('%s')\n"
+        "xlabel('Frequency (Hz)')\n"
+        "ylabel('Amplitude Response (dB)')\n"
+        "grid on\n"
+        "axis([0 %g %g %g])\n"
+        "disp('Hit return to continue')\n"
+        "pause\n"
+        , N, rate * .5, title, rate * .5, y1, y2);
+  }
+  else if (type == sox_plot_data) {
+    printf("# %s\n"
+        "# name: b\n"
+        "# type: matrix\n"
+        "# rows: %i\n"
+        "# columns: 1\n", title, num_points);
+    for (i = 0; i < num_points; ++i)
+      printf("%24.16e\n", h[i]);
+  }
 }
--- a/src/filter.c
+++ b/src/filter.c
@@ -200,13 +200,6 @@
         return (SOX_SUCCESS);
 }
 
-static int p2(long n)
-{
-  int N;
-  for (N = 1; n; n >>= 1, N <<= 1);
-  return max(N, 1024);
-}
-
 /*
  * Prepare processing.
  */
@@ -277,53 +270,20 @@
         f->Xh = Xh;
         f->Xt = Xh;
 
-   if (effp->global_info->plot == sox_plot_gnuplot) {
-     int N = p2(2 * Xh + 1);
-     double * h = lsx_calloc(N, sizeof(*h));
-     double * H = lsx_malloc((N / 2 + 1) * sizeof(*H));
+   if (effp->global_info->plot != sox_plot_off) {
+     int n = 2 * Xh + 1;
+     double * h = lsx_malloc(n * sizeof(*h));
+     char title[100];
+     sprintf(title, "SoX effect: filter frequency=%g-%g", f->freq0, f->freq1);
      for (i = 1; i < Xh + 1; ++i)
        h[i - 1] = f->Fp[Xh + 1 - i];
      for (i = 0; i < Xh + 1; ++i)
        h[Xh + i] = f->Fp[i];
-     lsx_power_spectrum(N, h, H);
+     lsx_plot_fir(h, n, effp->in_signal.rate, effp->global_info->plot, title, -f->beta * 10 - 20, 10.);
      free(h);
-     printf(
-       "# gnuplot file\n"
-       "set title 'SoX effect: filter frequency=%g-%g'\n"
-       "set xlabel 'Frequency (Hz)'\n"
-       "set ylabel 'Amplitude Response (dB)'\n"
-       "set grid xtics ytics\n"
-       "set key off\n"
-       "plot '-' with lines\n"
-       , f->freq0, f->freq1);
-     for (i = 0; i <= N/2; ++i)
-       printf("%g %g\n", i * effp->in_signal.rate / N, 10 * log10(H[i]));
-     printf(
-       "e\n"
-       "pause -1 'Hit return to continue'\n");
-     free(H);
      return SOX_EOF;
    }
 
-   if (effp->global_info->plot == sox_plot_octave) {
-     printf("%% GNU Octave file (may also work with MATLAB(R) )\nb=[");
-     for (i = 1; i < Xh + 1; ++i)
-       printf("%24.16e\n", f->Fp[Xh + 1 - i]);
-     for (i = 0; i < Xh + 1; ++i)
-       printf("%24.16e\n", f->Fp[i]);
-     printf("];\n"
-       "[h,w]=freqz(b);\n"
-       "plot(%g*w/pi,20*log10(h))\n"
-       "title('SoX effect: filter frequency=%g-%g')\n"
-       "xlabel('Frequency (Hz)')\n"
-       "ylabel('Amplitude Response (dB)')\n"
-       "grid on\n"
-       "disp('Hit return to continue')\n"
-       "pause\n"
-       , effp->in_signal.rate / 2, f->freq0, f->freq1);
-     return SOX_EOF;
-   }
-
         f->X = lsx_malloc(sizeof(double) * (2*BUFFSIZE + 2*Xh));
         f->Y = f->X + BUFFSIZE + 2*Xh;
 
@@ -469,7 +429,7 @@
 static sox_effect_handler_t sox_filter_effect = {
   "filter",
   "low-high [ windowlength [ beta ] ]",
-  0,
+  SOX_EFF_DEPRECATED,
   sox_filter_getopts,
   sox_filter_start,
   sox_filter_flow,
--- a/src/gain.c
+++ b/src/gain.c
@@ -227,8 +227,23 @@
 sox_effect_handler_t const * sox_gain_effect_fn(void)
 {
   static sox_effect_handler_t handler = {
-    "gain", "[-e|-b|-B|-r] [-n] [-l|-h] [gain-dB]", SOX_EFF_GAIN,
+    "gain", NULL, SOX_EFF_GAIN,
     create, start, flow, drain, stop, NULL, sizeof(priv_t)};
+  static char const * lines[] = {
+    "[-e|-b|-B|-r] [-n] [-l|-h] [gain-dB]",
+    "-e\tEqualise channels: peak to that with max peak;",
+    "-B\tBalance channels: rms to that with max rms; no clip protection",
+    "-b\tBalance channels: rms to that with max rms; clip protection",
+    "\t  Note -Bn = -bn",
+    "-r\tReclaim headroom (as much as possible without clipping); see -h",
+    "-n\tNorm file to 0dBfs(output precision); gain-dB, if present, usually <0",
+    "-l\tUse simple limiter",
+    "-h\tApply attenuation for headroom for subsequent effects;",
+    "\t  gain-dB, if present is subject to reclaim by a subsequent gain -r",
+    "gain-dB\tApply gain in dB",
+  };
+  static char * usage;
+  handler.usage = lsx_usage_lines(&usage, lines, array_length(lines));
   return &handler;
 }
 
--- a/src/getopt.c
+++ b/src/getopt.c
@@ -900,7 +900,7 @@
     char *temp = my_index (optstring, c);
 
     /* Increment `optind' when we start to process its last character.  */
-    if (*nextchar == '\0')
+    if (temp && *nextchar == '\0') /* SoX */
       ++optind;
 
     if (temp == NULL || c == ':')
@@ -944,6 +944,7 @@
               }
 #endif
           }
+        nextchar = (char *) "";  /* SoX */
         optopt = c;
         return '?';
       }
--- a/src/lowfir.c
+++ /dev/null
@@ -1,85 +1,0 @@
-/* Effect: lowfir 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
- */
-
-/* This is W.I.P. hence marked SOX_EFF_DEPRECATED for now.
- * Need to add other filter types (and rename file), and add a decent
- * user interface.  When this is done, it should be a more user-friendly
- * & more capable version of the `filter' effect.
- */
-
-#include "sox_i.h"
-#include "dft_filter.h"
-#include <string.h>
-
-typedef struct {
-  dft_filter_priv_t base;
-  double Fp, Fc, Fn, att, phase;
-  int allow_aliasing, num_taps, k;
-} priv_t;
-
-static int create(sox_effect_t * effp, int argc, char **argv)
-{
-  priv_t * p = (priv_t *)effp->priv;
-  dft_filter_priv_t * b = &p->base;
-  dft_filter_filter_t * f = &b->filter;
-  double * h;
-  int i;
-  b->filter_ptr = &b->filter;
-  --argc, ++argv;
-  do {                    /* break-able block */
-    NUMERIC_PARAMETER(Fp, 0, 100);
-    NUMERIC_PARAMETER(Fc, 0, 100);
-    NUMERIC_PARAMETER(Fn, 0, 100);
-    NUMERIC_PARAMETER(allow_aliasing, 0, 1);
-    NUMERIC_PARAMETER(att, 80, 200);
-    NUMERIC_PARAMETER(num_taps, 0, 65536);
-    NUMERIC_PARAMETER(k, 0, 512);
-    NUMERIC_PARAMETER(phase, 0, 100);
-  } while (0);
-
-  if (p->phase != 0 && p->phase != 50 && p->phase != 100)
-    p->att *= 34./33; /* negate degradation with intermediate phase */
-  h = lsx_design_lpf(
-      p->Fp, p->Fc, p->Fn, p->allow_aliasing, p->att, &p->num_taps, p->k);
-  f->num_taps = p->num_taps;
-
-  if (p->phase != 50)
-    lsx_fir_to_phase(&h, &f->num_taps, &f->post_peak, p->phase);
-  else f->post_peak = f->num_taps / 2;
-  lsx_debug("%i %i %g%%",
-      f->num_taps, f->post_peak, 100 - 100. * f->post_peak / (f->num_taps - 1));
-
-  f->dft_length = lsx_set_dft_length(f->num_taps);
-  f->coefs = lsx_calloc(f->dft_length, sizeof(*f->coefs));
-  for (i = 0; i < f->num_taps; ++i)
-    f->coefs[(i + f->dft_length - f->num_taps + 1) & (f->dft_length - 1)] = h[i] / f->dft_length * 2;
-  free(h);
-  lsx_safe_rdft(f->dft_length, 1, f->coefs);
-  return argc? lsx_usage(effp) : SOX_SUCCESS;
-}
-
-sox_effect_handler_t const * sox_lowfir_effect_fn(void)
-{
-  static sox_effect_handler_t handler;
-  handler = *sox_dft_filter_effect_fn();
-  handler.name = "lowfir";
-  handler.usage = "[options]";
-  handler.flags |= SOX_EFF_DEPRECATED;
-  handler.getopts = create;
-  handler.priv_size = sizeof(priv_t);
-  return &handler;
-}
--- /dev/null
+++ b/src/sinc.c
@@ -1,0 +1,143 @@
+/* Effect: sinc filters     Copyright (c) 2008-9 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 "dft_filter.h"
+#include "getopt.h"
+#include <string.h>
+
+typedef struct {
+  dft_filter_priv_t base;
+  double att, phase, freq0, freq1, tbw0, tbw1;
+  int num_taps[2];
+} priv_t;
+
+static int create(sox_effect_t * effp, int argc, char * * argv)
+{
+  priv_t * p = (priv_t *)effp->priv;
+  dft_filter_priv_t * b = &p->base;
+  char * parse_ptr = argv[0];
+  int i = 0;
+
+  b->filter_ptr = &b->filter;
+  p->phase = 50;
+  p->att = 120;
+  while (i < 2) {
+    int c = 1;
+    while (c && (c = getopt(argc, argv, "+a:p:t:n:MIL")) != -1) switch (c) {
+      GETOPT_NUMERIC('a', att,  40 , 200)
+      GETOPT_NUMERIC('p', phase, 0, 100);
+      GETOPT_NUMERIC('n', num_taps[1], 10, 32767);
+      case 't': p->tbw1 = lsx_parse_frequency(optarg, &parse_ptr);
+        if (p->tbw1 < 1 || *parse_ptr) return lsx_usage(effp);
+        break;
+      case 'M': p->phase =  0; break;
+      case 'I': p->phase = 25; break;
+      case 'L': p->phase = 50; break;
+      default: c = 0;
+    }
+    if (!i++ && !(p->tbw1 && p->num_taps[1])) {
+      p->tbw0 = p->tbw1, p->num_taps[0] = p->num_taps[1];
+      if (optind < argc) {
+        parse_ptr = argv[optind++];
+        if (*parse_ptr != '-')
+          p->freq0 = lsx_parse_frequency(parse_ptr, &parse_ptr);
+        if (*parse_ptr == '-')
+          p->freq1 = lsx_parse_frequency(parse_ptr+1, &parse_ptr);
+      }
+    }
+  }
+  return optind != argc || p->freq0 < 0 || p->freq1 < 0 || *parse_ptr ?
+      lsx_usage(effp) : SOX_SUCCESS;
+}
+
+static void invert(double * h, int n)
+{
+  int i;
+  for (i = 0; i < n; ++i)
+    h[i] = -h[i];
+  h[(n - 1) / 2] += 1;
+}
+
+static double * lpf(sox_effect_t * effp, double Fc, double tbw, int * num_taps, double att, double phase)
+{
+  double Fn = effp->in_signal.rate * .5;
+  if (!Fc) {
+    *num_taps = 0;
+    return NULL;
+  }
+  if (phase != 0 && phase != 50 && phase != 100)
+    att *= 34./33; /* negate degradation with intermediate phase */
+  *num_taps = *num_taps? *num_taps |= 1 :
+      min(32767, lsx_lpf_num_taps(att, (tbw? tbw / Fn : .05) * .5, 0));
+  lsx_report("num taps = %i", *num_taps);
+  return lsx_make_lpf(*num_taps, Fc / Fn, lsx_kaiser_beta(att), 1.);
+}
+
+static int start(sox_effect_t * effp)
+{
+  priv_t * p = (priv_t *)effp->priv;
+  dft_filter_filter_t * f = p->base.filter_ptr;
+
+  if (!f->num_taps) {
+    double * h[2];
+    int i, longer;
+    h[0] = lpf(effp, p->freq0, p->tbw0, &p->num_taps[0], p->att, p->phase);
+    h[1] = lpf(effp, p->freq1, p->tbw1, &p->num_taps[1], p->att, p->phase);
+    if (h[0]) invert(h[0], p->num_taps[0]);
+    longer = p->num_taps[1] > p->num_taps[0];
+    f->num_taps = p->num_taps[longer];
+    if (h[0] && h[1]) {
+      for (i = 0; i < p->num_taps[!longer]; ++i)
+        h[longer][i + (f->num_taps - p->num_taps[!longer])/2] += h[!longer][i];
+      free(h[!longer]);
+      if (p->freq0 < p->freq1)
+        invert(h[longer], f->num_taps);
+    }
+    if (p->phase != 50)
+      lsx_fir_to_phase(&h[longer], &f->num_taps, &f->post_peak, p->phase);
+    else f->post_peak = f->num_taps / 2;
+    lsx_debug("%i %i %g%%", f->num_taps, f->post_peak,
+        100 - 100. * f->post_peak / (f->num_taps - 1));
+    if (effp->global_info->plot != sox_plot_off) {
+      char title[100];
+      sprintf(title, "SoX effect: sinc filter freq=%g-%g", p->freq0, p->freq1);
+      lsx_plot_fir(h[longer], f->num_taps, effp->in_signal.rate,
+          effp->global_info->plot, title, -p->att - 20, 10.);
+      return SOX_EOF;
+    }
+    f->dft_length = lsx_set_dft_length(f->num_taps);
+    f->coefs = lsx_calloc(f->dft_length, sizeof(*f->coefs));
+    for (i = 0; i < f->num_taps; ++i)
+      f->coefs[(i + f->dft_length - f->num_taps + 1) & (f->dft_length - 1)] = h[longer][i] / f->dft_length * 2;
+    free(h[longer]);
+    lsx_safe_rdft(f->dft_length, 1, f->coefs);
+  }
+  return sox_dft_filter_effect_fn()->start(effp);
+}
+
+sox_effect_handler_t const * sox_sinc_effect_fn(void)
+{
+  static sox_effect_handler_t handler;
+  handler = *sox_dft_filter_effect_fn();
+  handler.name = "sinc";
+  handler.usage = "[-a att] [-p phase|-M|-I|-L] [-t tbw|-n taps] freq1[-freq2 [-t ...|-n ...]]";
+  handler.getopts = create;
+  handler.start = start;
+  handler.priv_size = sizeof(priv_t);
+  return &handler;
+}
--- a/src/sox.c
+++ b/src/sox.c
@@ -2005,6 +2005,7 @@
   LSX_ENUM_ITEM(sox_plot_,off)
   LSX_ENUM_ITEM(sox_plot_,octave)
   LSX_ENUM_ITEM(sox_plot_,gnuplot)
+  LSX_ENUM_ITEM(sox_plot_,data)
   {0, 0}};
 
 enum {
--- a/src/sox.h
+++ b/src/sox.h
@@ -451,7 +451,7 @@
 #define SOX_EFF_GAIN     128         /* Effect does not support gain -r */
 #define SOX_EFF_MODIFY   256         /* Effect does not modify samples */
 
-typedef enum {sox_plot_off, sox_plot_octave, sox_plot_gnuplot} sox_plot_t;
+typedef enum {sox_plot_off, sox_plot_octave, sox_plot_gnuplot, sox_plot_data} sox_plot_t;
 typedef struct sox_effect sox_effect_t;
 struct sox_effects_globals { /* Global parameters (for effects) */
   sox_plot_t plot;         /* To help the user choose effect & options */
--- a/src/sox_i.h
+++ b/src/sox_i.h
@@ -108,6 +108,7 @@
 double lsx_kaiser_beta(double att);
 void lsx_apply_kaiser(double h[], const int num_points, double beta);
 double * lsx_make_lpf(int num_taps, double Fc, double beta, double scale);
+int lsx_lpf_num_taps(double att, double tr_bw, int k);
 double * lsx_design_lpf(
     double Fp,      /* End of pass-band; ~= 0.01dB point */
     double Fc,      /* Start of stop-band */
@@ -124,6 +125,7 @@
 #define LSX_MAX_TBW0A (LSX_MAX_TBW0 / (1 + LSX_TO_3dB))
 #define LSX_MAX_TBW3 floor(LSX_MAX_TBW0 * LSX_TO_3dB)
 #define LSX_MAX_TBW3A floor(LSX_MAX_TBW0A * LSX_TO_3dB)
+void lsx_plot_fir(double * h, int num_points, sox_rate_t rate, sox_plot_t type, char const * title, double y1, double y2);
 
 #ifndef HAVE_STRCASECMP
 int strcasecmp(const char *s1, const char *s2);
--- a/src/util.h
+++ b/src/util.h
@@ -19,6 +19,8 @@
 
 #include "xmalloc.h"
 
+/*---------------------------- Portability stuff -----------------------------*/
+
 #ifdef __GNUC__
 #define NORET __attribute__((noreturn))
 #define PRINTF __attribute__ ((format (printf, 1, 2)))
@@ -64,29 +66,39 @@
   #define SET_BINARY_MODE(file)
 #endif
 
-#ifdef min
-#undef min
+#ifdef WORDS_BIGENDIAN
+#define MACHINE_IS_BIGENDIAN 1
+#define MACHINE_IS_LITTLEENDIAN 0
+#else
+#define MACHINE_IS_BIGENDIAN 0
+#define MACHINE_IS_LITTLEENDIAN 1
 #endif
-#define min(a, b) ((a) <= (b) ? (a) : (b))
 
-#ifdef max
-#undef max
-#endif
-#define max(a, b) ((a) >= (b) ? (a) : (b))
+/*--------------------------- Language extensions ----------------------------*/
 
 /* Compile-time ("static") assertion */
 /*   e.g. assert_static(sizeof(int) >= 4, int_type_too_small)    */
 #define assert_static(e,f) enum {assert_static__##f = 1/(e)}
-#define range_limit(x, lower, upper) (min(max(x, lower), upper))
 #define array_length(a) (sizeof(a)/sizeof(a[0]))
 #define field_offset(type, field) ((size_t)&(((type *)0)->field))
-#define sqr(a) ((a) * (a))
+#define unless(x) if (!(x))
 
-#define dB_to_linear(x) exp((x) * M_LN10 * 0.05)
-#define linear_to_dB(x) (log10(x) * 20)
+/*------------------------------- Maths stuff --------------------------------*/
 
 #include <math.h>
 
+#ifdef min
+#undef min
+#endif
+#define min(a, b) ((a) <= (b) ? (a) : (b))
+
+#ifdef max
+#undef max
+#endif
+#define max(a, b) ((a) >= (b) ? (a) : (b))
+
+#define range_limit(x, lower, upper) (min(max(x, lower), upper))
+
 #ifndef M_PI
 #define M_PI    3.14159265358979323846
 #endif
@@ -100,16 +112,13 @@
 #define M_SQRT2  sqrt(2.)
 #endif
 
+#define sqr(a) ((a) * (a))
 #define sign(x) ((x) < 0? -1 : 1)
+#define safe_log(x) ((x) <= 0? -99 : log(x)) /* exp --> 0 (for single prec.) */
 
 /* Numerical Recipes in C, p. 284 */
 #define ranqd1(x) ((x) = 1664525L * (x) + 1013904223L) /* int32_t x */
 #define dranqd1(x) (ranqd1(x) * (1. / (65536. * 32768.))) /* [-1,1) */
 
-#ifdef WORDS_BIGENDIAN
-#define MACHINE_IS_BIGENDIAN 1
-#define MACHINE_IS_LITTLEENDIAN 0
-#else
-#define MACHINE_IS_BIGENDIAN 0
-#define MACHINE_IS_LITTLEENDIAN 1
-#endif
+#define dB_to_linear(x) exp((x) * M_LN10 * 0.05)
+#define linear_to_dB(x) (log10(x) * 20)