shithub: sox

Download patch

ref: 69d3c4df5460ab7daa0e3f371d433ed9fb51a634
parent: 5d2a07fe5c656961e9ee9d3e9bead8eb584ae0b6
author: robs <robs>
date: Mon Oct 22 19:40:50 EDT 2007

freeverb

--- a/ChangeLog
+++ b/ChangeLog
@@ -15,7 +15,6 @@
   Bug fixes:
 
   o Fix Sndtool read error causing noise at start.  (Reynir Stefánsson)
-  o Fix [bug #1804772] Reverb effect locks up.  (Reynir Stefánsson)
   o Fix loss of 1 decoded FLAC block when using "trim 0 ...".  (robs)
   o Let "make distcheck" run some automated test scripts.
   o Distribute missing cmake files.
@@ -22,6 +21,11 @@
   o Fix ogg vorbis compile error on some platforms.
   o Remove unused libltdl that could cause header mismatch with
     installed libltdl.
+
+  Effects:
+
+  o Reimplemented reverb using freeverb.  (robs)
+
 
 sox-14.0.0
 ----------
--- a/soxeffect.7
+++ b/soxeffect.7
@@ -753,20 +753,13 @@
 To be precise, this is done when both input-rate < output-rate, and
 output-rate \(di gcd(input-rate, output-rate) \(<= 511.
 .TP
-\fBreverb \fIgain-out reverb-time\fR <\fIdelay\fR>
-Add reverberation to the audio.  Each
-.I delay
-is given
-in milliseconds and its feedback is depending on the
-.I reverb-time
-in milliseconds.  Each
-.I delay
-should be in
-the range of half to quarter of
-.I reverb-time
-to get a realistic reverberation.
-.I gain-out
-is the volume of the output.
+\fBreverb [\fB-w\fR|\fB--wet-only\fR] [\fIreverberance\fR (50%) [\fIHF-damping\fR (50%)
+[\fIroom-scale\fR (100%) [\fIstereo-depth\fR (100%)
+.br
+[\fIpre-delay\fR (0ms) [\fIwet-gain\fR (0dB)]]]]]]
+.SP
+Add reverberation to the audio using the freeverb algorithm.
+Default values shown in parenthesis.
 .TP
 \fBreverse\fR
 Reverse the audio completely.
--- a/src/reverb.c
+++ b/src/reverb.c
@@ -1,295 +1,297 @@
 /*
- * August 24, 1998
- * Copyright (C) 1998 Juergen Mueller And Sundry Contributors
- * This source code is freely redistributable and may be used for
- * any purpose.  This copyright notice must be maintained. 
- * Lance Norskog And Sundry Contributors are not responsible for 
- * the consequences of using this software.
- */
-
-/*
-**      Echo effect. based on:
-**
-** echoplex.c - echo generator
-**
-** Copyright (C) 1989 by Jef Poskanzer.
-**
-** Permission to use, copy, modify, and distribute this software and its
-** documentation for any purpose and without fee is hereby granted, provided
-** that the above copyright notice appear in all copies and that both that
-** copyright notice and this permission notice appear in supporting
-** documentation.  This software is provided "as is" without express or
-** implied warranty.
-*/
-
-
-/*
- * Changes to old "echo.c" now called "reverb.c":
+ * Effect: reverb           Copyright (c) 2007 robs@users.sourceforge.net
+ * Algorithm based on freeverb by Jezar @ Dreampoint.
  *
- * The effect name changes from "echo" to "reverb" (see Guitar FX FAQ) for
- * the difference in its defintion.
- * The idea of the echoplexer is modified and enhanceb by an automatic
- * setting of each decay for realistic reverb.
- * Some bugs are fixed concerning xmalloc and fade-outs.
- * Added an output volume (gain-out) avoiding saturation or clipping.
+ * 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 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.
  *
- * Reverb effect for dsp.
- *
- * Flow diagram scheme for n delays ( 1 <= n <= MAXREVERB )
- *
- *        * gain-in  +---+                        * gain-out
- * ibuff ----------->|   |------------------------------------> obuff
- *                   |   |  * decay 1
- *                   |   |<------------------------+
- *                   | + |  * decay 2              |
- *                   |   |<--------------------+   |
- *                   |   |  * decay n          |   |
- *                   |   |<----------------+   |   |
- *                   +---+                 |   |   |
- *                     |      _________    |   |   |
- *                     |     |         |   |   |   |
- *                     +---->| delay n |---+   |   |
- *                     .     |_________|       |   |
- *                     .                       |   |
- *                     .      _________        |   |
- *                     |     |         |       |   |
- *                     +---->| delay 2 |-------+   |
- *                     |     |_________|           |
- *                     |                           |
- *                     |      _________            |
- *                     |     |         |           |
- *                     +---->| delay 1 |-----------+
- *                           |_________|
- *
- *
- *
- * Usage:
- *   reverb gain-out reverb-time delay-1 [ delay-2 ... delay-n ]
- *
- * Where:
- *   gain-out :  0.0 ...      volume
- *   reverb-time :  > 0.0 msec
- *   delay-1 ... delay-n :  > 0.0 msec
- *
- * Note:
- *   gain-in is automatically adjusted avoiding saturation and clipping of
- *   the output. decay-1 to decay-n are computed such that at reverb-time
- *   the input will be 60 dB of the original input for the given delay-1 
- *   to delay-n. delay-1 to delay-n specify the time when the first bounce
- *   of the input will appear. A proper setting for delay-1 to delay-n 
- *   depends on the choosen reverb-time (see hint).
- *
- * Hint:
- *   a realstic reverb effect can be obtained using for a given reverb-time "t"
- *   delays in the range of "t/2 ... t/4". Each delay should not be an integer
- *   of any other.
- *
-*/
-
-/*
- * libSoX reverb effect file.
+ * 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,
+ * Fifth Floor, 51 Franklin Street, Boston, MA 02111-1301, USA.
  */
 
-#include <stdlib.h> /* Harmless, and prototypes atof() etc. --dgc */
-#include <math.h>
 #include "sox_i.h"
+#include "xmalloc.h"
+#include <math.h>
+#include <string.h>
 
-#define REVERB_FADE_THRESH 10
-#define DELAY_BUFSIZ ( 50 * SOX_MAXRATE )
-#define MAXREVERBS 8
+#define FLOAT float
+#define filter_create(p, n) (p)->ptr=Xcalloc((p)->buffer, (p)->size=(size_t)(n))
+#define filter_delete(p) free((p)->buffer)
+#define ADVANCE_PTR(ptr) if (--p->ptr < p->buffer) p->ptr += p->size
 
-/* Private data for SKEL file */
-typedef struct reverbstuff {
-        int     counter;                        
-        size_t  numdelays;
-        float   *reverbbuf;
-        float   in_gain, out_gain, time;
-        float   delay[MAXREVERBS], decay[MAXREVERBS];
-        size_t  samples[MAXREVERBS], maxsamples;
-        sox_ssample_t pl, ppl, pppl;
-} *reverb_t;
+typedef struct {
+  size_t  size;
+  FLOAT   * buffer, * ptr;
+  FLOAT   store;
+} filter_t;
 
-/*
- * Process options
- */
-static int sox_reverb_getopts(sox_effect_t * effp, int n, char **argv) 
+static FLOAT comb_process(filter_t * p,
+    FLOAT input, FLOAT feedback, FLOAT hf_damping)
 {
-        reverb_t reverb = (reverb_t) effp->priv;
-        int i;
+  FLOAT output = *p->ptr;
+  p->store = output + (p->store - output) * hf_damping;
+  *p->ptr = input + p->store * feedback;
+  ADVANCE_PTR(ptr);
+  return output;
+}
 
-        reverb->numdelays = 0;
-        reverb->maxsamples = 0;
+static FLOAT allpass_process(filter_t * p,
+    FLOAT input, FLOAT feedback)
+{
+  FLOAT output = *p->ptr;
+  *p->ptr = input + output * feedback;
+  ADVANCE_PTR(ptr);
+  return output - input;
+}
 
-        if ( n < 3 )
-          return sox_usage(effp);
+static const size_t /* Filter lengths in samples (for 44100Hz sample-rate) */
+  comb_lengths[] = {1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617},
+  allpass_lengths[] = {225, 341, 441, 556};
+#define stereo_adjust 12
 
-        if ( n - 2 > MAXREVERBS )
-        {
-            sox_fail("reverb: to many dalays, use less than %i delays",
-                        MAXREVERBS);
-            return (SOX_EOF);
-        }
+typedef struct filter_array {
+  filter_t comb   [array_length(comb_lengths)];
+  filter_t allpass[array_length(allpass_lengths)];
+} filter_array_t;
 
-        i = 0;
-        sscanf(argv[i++], "%f", &reverb->out_gain);
-        sscanf(argv[i++], "%f", &reverb->time);
-        while (i < n) {
-                /* Linux bug and it's cleaner. */
-                sscanf(argv[i++], "%f", &reverb->delay[reverb->numdelays]);
-                reverb->numdelays++;
-        }
-        return (SOX_SUCCESS);
+static void filter_array_create(filter_array_t * p, double rate,
+    double scale, double offset)
+{
+  size_t i;
+  double r = rate * (1 / 44100.);
+
+  for (i = 0; i < array_length(comb_lengths); ++i, offset = -offset)
+    filter_create(&p->comb[i], scale * r * (comb_lengths[i] + stereo_adjust * offset) + .5);
+  for (i = 0; i < array_length(allpass_lengths); ++i, offset = -offset)
+    filter_create(&p->allpass[i], r * (allpass_lengths[i] + stereo_adjust * offset) + .5);
 }
 
-/*
- * Prepare for processing.
- */
-static int sox_reverb_start(sox_effect_t * effp)
+static void filter_array_delete(filter_array_t * p)
 {
-        reverb_t reverb = (reverb_t) effp->priv;
-        size_t i;
+  size_t i;
 
-        reverb->in_gain = 1.0;
+  for (i = 0; i < array_length(allpass_lengths); ++i)
+    filter_delete(&p->allpass[i]);
+  for (i = 0; i < array_length(comb_lengths); ++i)
+    filter_delete(&p->comb[i]);
+}
 
-        if ( reverb->out_gain < 0.0 )
-        {
-                sox_fail("reverb: gain-out must be positive");
-                return (SOX_EOF);
-        }
-        if ( reverb->out_gain > 1.0 )
-                sox_warn("reverb: warnig >>> gain-out can cause saturation of output <<<");
-        if ( reverb->time < 0.0 )
-        {
-                sox_fail("reverb: reverb-time must be positive");
-                return (SOX_EOF);
-        }
-        for(i = 0; i < reverb->numdelays; i++) {
-                reverb->samples[i] = reverb->delay[i] * effp->ininfo.rate / 1000.0;
-                if ( reverb->samples[i] < 1 )
-                {
-                    sox_fail("reverb: delay must be positive!");
-                    return (SOX_EOF);
-                }
-                if ( reverb->samples[i] > DELAY_BUFSIZ )
-                {
-                        sox_fail("reverb: delay must be less than %g seconds!",
-                                DELAY_BUFSIZ / effp->ininfo.rate );
-                        return(SOX_EOF);
-                }
-                /* Compute a realistic decay */
-                reverb->decay[i] = (float) pow(10.0,(-3.0 * reverb->delay[i] / reverb->time));
-                if ( reverb->samples[i] > reverb->maxsamples )
-                    reverb->maxsamples = reverb->samples[i];
-        }
-        reverb->reverbbuf = (float *) xmalloc(sizeof (float) * reverb->maxsamples);
-        for ( i = 0; i < reverb->maxsamples; ++i )
-                reverb->reverbbuf[i] = 0.0;
-        reverb->pppl = reverb->ppl = reverb->pl = 0x7fffff;             /* fade-outs */
-        reverb->counter = 0;
-        /* Compute the input volume carefully */
-        for ( i = 0; i < reverb->numdelays; i++ )
-                reverb->in_gain *= 
-                        ( 1.0 - ( reverb->decay[i] * reverb->decay[i] ));
-        return (SOX_SUCCESS);
+static void filter_array_process(filter_array_t * p,
+    size_t length, FLOAT const * input, FLOAT * output,
+    FLOAT feedback, FLOAT hf_damping, FLOAT gain)
+{
+  while (length--) {
+    FLOAT out = 0, in = *input++;
+
+    size_t i = array_length(comb_lengths) - 1;
+    do out += comb_process(p->comb + i, in, feedback, hf_damping);
+    while (i--);
+
+    i = array_length(allpass_lengths) - 1;
+    do out = allpass_process(p->allpass + i, out, .5f);
+    while (i--);
+
+    *output++ = out * gain;
+  }
+}
+
+static const size_t block_size = 1024;
+
+typedef struct reverb {
+  FLOAT feedback;
+  FLOAT hf_damping;
+  FLOAT gain;
+  size_t delay, num_delay_blocks;
+  FLOAT * * in, * out[2];
+  filter_array_t chan[2];
+} reverb_t;
+
+static FLOAT * reverb_create(reverb_t * p, double sample_rate_Hz,
+    double wet_gain_dB,
+    double room_scale,     /* % */
+    double reverberance,   /* % */
+    double hf_damping,     /* % */
+    double pre_delay_ms,
+    FLOAT * * out,
+    double stereo_depth)
+{
+  size_t i;
+  double scale = room_scale / 100 * .9 + .1;
+  double depth = stereo_depth / 100;
+  double a, b;
+
+  memset(p, 0, sizeof(*p));
+
+  b = -1 / log(1 - .5); a = 100 / (1 + log(1 - .98) * b); b *= a;
+  p->feedback = 1 - exp((reverberance - a) / b);
+  p->hf_damping = hf_damping / 100 * .3 + .2;
+  p->gain = exp(wet_gain_dB / 20 * log(10)) * .015;
+  p->delay = pre_delay_ms / 1000 * sample_rate_Hz + .5;
+  p->num_delay_blocks = (p->delay + block_size - 1) / block_size;
+  Xcalloc(p->in, 1 + p->num_delay_blocks);
+  for (i = 0; i <= p->num_delay_blocks; ++i)
+    Xcalloc(p->in[i], block_size);
+  for (i = 0; i <= ceil(depth); ++i) {
+    filter_array_create(p->chan + i, sample_rate_Hz, scale, i * depth);
+    out[i] = Xcalloc(p->out[i], block_size);
+  }
+  return p->in[0];
+}
+
+static void reverb_delete(reverb_t * p)
+{
+  size_t i;
+  for (i = 0; i < 2 && p->out[i]; ++i) {
+    free(p->out[i]);
+    filter_array_delete(p->chan + i);
+  }
+  for (i = 0; i <= p->num_delay_blocks; ++i)
+    free(p->in[i]);
+  free(p->in);
 }
 
-/*
- * Processed signed long samples from ibuf to obuf.
- * Return number of samples processed.
- */
-static int sox_reverb_flow(sox_effect_t * effp, const sox_ssample_t *ibuf, sox_ssample_t *obuf, 
-                   sox_size_t *isamp, sox_size_t *osamp)
+static FLOAT * reverb_process(reverb_t * p, size_t length)
 {
-        reverb_t reverb = (reverb_t) effp->priv;
-        size_t i = reverb->counter, j;
-        float d_in, d_out;
-        sox_ssample_t out;
-        sox_size_t len = min(*isamp, *osamp);
-        *isamp = *osamp = len;
+  FLOAT * oldest_in = p->in[p->num_delay_blocks];
+  size_t len1 = p->delay % block_size;
+  size_t len2 = length - len1, i;
 
-        while (len--) {
-                /* Store delays as 24-bit signed longs */
-                d_in = (float) *ibuf++ / 256;
-                d_in = d_in * reverb->in_gain;
-                /* Mix decay of delay and input as output */
-                for ( j = 0; j < reverb->numdelays; j++ )
-                        d_in +=
-reverb->reverbbuf[(i + reverb->maxsamples - reverb->samples[j]) % reverb->maxsamples] * reverb->decay[j];
-                d_out = d_in * reverb->out_gain;
-                out = SOX_24BIT_CLIP_COUNT((sox_ssample_t) d_out, effp->clips);
-                *obuf++ = out * 256;
-                reverb->reverbbuf[i] = d_in;
-                i++;            /* FIXME need a % maxsamples here ? */
-                i %= reverb->maxsamples;
-        }
-        reverb->counter = i;
-        /* processed all samples */
-        return (SOX_SUCCESS);
+  for (i = 0; i < 2 && p->out[i]; ++i) {
+    FLOAT * * b = p->in + p->num_delay_blocks;
+
+    if (len1)
+      filter_array_process(p->chan + i, len1, *b-- + block_size - len1, p->out[i], p->feedback, p->hf_damping, p->gain);
+    filter_array_process(p->chan + i, len2, *b, p->out[i] + len1, p->feedback, p->hf_damping, p->gain);
+  }
+  for (i = p->num_delay_blocks; i; --i)
+    p->in[i] = p->in[i - 1];
+  return p->in[i] = oldest_in;
 }
 
-/*
- * Drain out reverb lines. 
- */
-static int sox_reverb_drain(sox_effect_t * effp, sox_ssample_t *obuf, sox_size_t *osamp)
+/*------------------------------- SoX Wrapper --------------------------------*/
+
+typedef struct priv {
+  double reverberance, hf_damping, pre_delay_ms;
+  double stereo_depth, wet_gain_dB, room_scale;
+  sox_bool wet_only;
+
+  size_t ichannels, ochannels;
+  struct {
+    reverb_t reverb;
+    FLOAT * dry, * dry1, * wet[2];
+  } f[2];
+} priv_t;
+
+assert_static(sizeof(struct priv) <= SOX_MAX_EFFECT_PRIVSIZE,
+              /* else */ EFFECT_PRIVSIZE_too_big);
+
+static int getopts(sox_effect_t * effp, int argc, char **argv)
 {
-        reverb_t reverb = (reverb_t) effp->priv;
-        float d_in, d_out;
-        sox_ssample_t out, l;
-        size_t i, j;
-        sox_size_t done;
-        sox_bool notfaded;
+  priv_t * p = (priv_t *) effp->priv;
 
-        i = reverb->counter;
-        done = 0;
-        /* drain out delay samples */
-        do {
-                d_in = 0;
-                d_out = 0;
-                for ( j = 0; j < reverb->numdelays; ++j )
-                        d_in += 
-reverb->reverbbuf[(i + reverb->maxsamples - reverb->samples[j]) % reverb->maxsamples] * reverb->decay[j];
-                d_out = d_in * reverb->out_gain;
-                out = SOX_24BIT_CLIP_COUNT((sox_ssample_t) d_out, effp->clips);
-                obuf[done++] = out * 256;
-                reverb->reverbbuf[i] = d_in;
-                l = SOX_24BIT_CLIP_COUNT((sox_ssample_t) d_in, effp->clips);
-                reverb->pppl = reverb->ppl;
-                reverb->ppl = reverb->pl;
-                reverb->pl = l;
-                i = (i + 1) % reverb->maxsamples;
-                notfaded = (abs(reverb->pl) + abs(reverb->ppl) + abs(reverb->pppl) > REVERB_FADE_THRESH);
-        } while (done < *osamp && notfaded);
-        reverb->counter = i;
-        *osamp = done;
-        return notfaded? SOX_SUCCESS : SOX_EOF;
+  p->reverberance = p->hf_damping = 50; /* Set non-zero defaults */
+  p->stereo_depth = p->room_scale = 100;
+
+  p->wet_only = argc && (!strcmp(*argv, "-w") || !strcmp(*argv, "--wet-only"))
+    && (--argc, ++argv, sox_true);
+  do {  /* break-able block */
+    NUMERIC_PARAMETER(reverberance, 0, 100)
+    NUMERIC_PARAMETER(hf_damping, 0, 100)
+    NUMERIC_PARAMETER(room_scale, 0, 100)
+    NUMERIC_PARAMETER(stereo_depth, 0, 100)
+    NUMERIC_PARAMETER(pre_delay_ms, 0, 500)
+    NUMERIC_PARAMETER(wet_gain_dB, -10, 10)
+  } while (0);
+
+  return argc ? sox_usage(effp) : SOX_SUCCESS;
 }
 
-/*
- * Clean up reverb effect.
- */
-static int sox_reverb_stop(sox_effect_t * effp)
+static int start(sox_effect_t * effp)
 {
-        reverb_t reverb = (reverb_t) effp->priv;
+  priv_t * p = (priv_t *) effp->priv;
+  size_t i;
+  
+  p->ichannels = p->ochannels = 1;
 
-        free((char *) reverb->reverbbuf);
-        reverb->reverbbuf = (float *) -1;   /* guaranteed core dump */
-        return (SOX_SUCCESS);
+  effp->outinfo.rate = effp->ininfo.rate;
+  if (effp->ininfo.channels > 2 && p->stereo_depth) {
+    sox_warn("stereo-depth not applicable with >2 channels");
+    p->stereo_depth = 0;
+  }
+  if (effp->ininfo.channels == 1 && p->stereo_depth)
+    effp->outinfo.channels = p->ochannels = 2;
+  else effp->outinfo.channels = effp->ininfo.channels;
+  if (effp->ininfo.channels == 2 && p->stereo_depth)
+    p->ichannels = p->ochannels = 2;
+  else effp->flows = effp->ininfo.channels;
+  for (i = 0; i < p->ichannels; ++i)
+    p->f[i].dry = reverb_create(&p->f[i].reverb, effp->ininfo.rate,
+        p->wet_gain_dB, p->room_scale, p->reverberance, p->hf_damping,
+        p->pre_delay_ms, p->f[i].wet, p->stereo_depth);
+  return sox_effect_set_imin(effp, block_size);
 }
 
-static sox_effect_handler_t sox_reverb_effect = {
-  "reverb",
-  "gain-out reverb-time delay [ delay ... ]",
-  SOX_EFF_LENGTH,
-  sox_reverb_getopts,
-  sox_reverb_start,
-  sox_reverb_flow,
-  sox_reverb_drain,
-  sox_reverb_stop,
-  NULL
-};
+static int flow(sox_effect_t * effp, const sox_ssample_t * ibuf,
+                sox_ssample_t * obuf, sox_size_t * isamp, sox_size_t * osamp)
+{
+  priv_t * p = (priv_t *) effp->priv;
+  sox_size_t c, i, w;
 
-const sox_effect_handler_t *sox_reverb_effect_fn(void)
+  if (*isamp < block_size || *osamp < block_size * p->ochannels / p->ichannels)
+    *isamp = *osamp = 0;
+  else {
+    *osamp = (*isamp = block_size) * p->ochannels / p->ichannels;
+
+    for (i = 0; i < *isamp; ++i) for (c = 0; c < p->ichannels; ++c) 
+      p->f[c].dry[i] = SOX_SAMPLE_TO_FLOAT_32BIT(*ibuf++, effp->clips);
+    for (c = 0; c < p->ichannels; ++c) {
+      p->f[c].dry1 = p->f[c].dry;
+      p->f[c].dry = reverb_process(&p->f[c].reverb, *isamp / p->ichannels);
+    }
+    if (p->ichannels == 2)
+      for (i = 0; i < *isamp; ++i) for (w = 0; w < 2; ++w) {
+        FLOAT x = (1 - p->wet_only) * p->f[w].dry1[i] + .5 *
+          (p->f[0].wet[w][i] + p->f[1].wet[w][i]);
+        *obuf++ = SOX_FLOAT_32BIT_TO_SAMPLE(x, effp->clips);
+      }
+    else
+      for (i = 0; i < *isamp; ++i) for (w = 0; w < min(2, p->ochannels); ++w) {
+        FLOAT x = (1 - p->wet_only) * p->f[0].dry1[i] + p->f[0].wet[w][i];
+        *obuf++ = SOX_FLOAT_32BIT_TO_SAMPLE(x, effp->clips);
+      }
+  }
+  return SOX_SUCCESS;
+}
+
+static int stop(sox_effect_t * effp)
 {
-    return &sox_reverb_effect;
+  priv_t * p = (priv_t *) effp->priv;
+  size_t i;
+  for (i = 0; i < p->ichannels; ++i)
+    reverb_delete(&p->f[i].reverb);
+  return SOX_SUCCESS;
+}
+
+sox_effect_handler_t const *sox_reverb_effect_fn(void)
+{
+  static sox_effect_handler_t handler = {
+    "reverb", 
+    "[-w|--wet-only]"
+    " [reverberance (50%)"
+    " [HF-damping (50%)"
+    " [room-scale (100%)"
+    " [stereo-depth (100%)"
+    " [pre-delay (0ms)"
+    " [wet-gain (0dB)"
+    "]]]]]]", SOX_EFF_MCHAN, getopts, start, flow, NULL, stop, NULL
+  };
+  return &handler;
 }
--- a/src/xmalloc.h
+++ b/src/xmalloc.h
@@ -29,4 +29,6 @@
 void *xrealloc(void *ptr, size_t newsize);
 char *xstrdup(const char *s);
 
+#define Xcalloc(var, n) var = xcalloc(n, sizeof(*var))
+
 #endif