shithub: sox

Download patch

ref: b123511e766ad231f601a2909f3e1cbec82a6256
parent: 0e90c7f765e3c04f07196c08ed57bc9fa90f6fb6
author: robs <robs>
date: Sat Jun 9 08:23:39 EDT 2007

Effects chain can now handle multiple effects changing rate or chans, though current effects are not suited to this

--- a/src/effects.c
+++ b/src/effects.c
@@ -23,57 +23,45 @@
 #include <strings.h>
 
 #undef sox_fail
-#undef sox_warn
 #undef sox_report
 #define sox_fail sox_message_filename=effp->handler.name,sox_fail
-#define sox_warn sox_message_filename=effp->handler.name,sox_warn
 #define sox_report sox_message_filename=effp->handler.name,sox_report
 
-/* dummy effect routine for do-nothing functions */
-static int effect_nothing(sox_effect_t * effp UNUSED)
+
+
+/* Default effect handler functions for do-nothing situations: */
+
+static int default_function(sox_effect_t * effp UNUSED)
 {
   return SOX_SUCCESS;
 }
 
-static int effect_nothing_flow(sox_effect_t * effp UNUSED, const sox_ssample_t *ibuf UNUSED, sox_ssample_t *obuf UNUSED, sox_size_t *isamp, sox_size_t *osamp)
+/* Pass through samples verbatim */
+static int default_flow(sox_effect_t * effp UNUSED, const sox_ssample_t *ibuf UNUSED, sox_ssample_t *obuf UNUSED, sox_size_t *isamp, sox_size_t *osamp)
 {
-  /* Pass through samples verbatim */
   *isamp = *osamp = min(*isamp, *osamp);
   memcpy(obuf, ibuf, *isamp * sizeof(*obuf));
   return SOX_SUCCESS;
 }
 
-static int effect_nothing_drain(sox_effect_t * effp UNUSED, sox_ssample_t *obuf UNUSED, sox_size_t *osamp)
+/* Inform no more samples to drain */
+static int default_drain(sox_effect_t * effp UNUSED, sox_ssample_t *obuf UNUSED, sox_size_t *osamp)
 {
-  /* Inform no more samples to drain */
   *osamp = 0;
   return SOX_EOF;
 }
 
-static int effect_nothing_getopts(sox_effect_t * effp, int argc, char **argv UNUSED)
+/* Check that no parameters have been given */
+static int default_getopts(sox_effect_t * effp, int argc, char **argv UNUSED)
 {
   if (argc) {
-    sox_fail(effp->handler.usage);
+    sox_fail("takes no parameters");
     return SOX_EOF;
   }
   return SOX_SUCCESS;
 }
 
-
-/* Effect chain routines */
-
-sox_effect_handler_t const * sox_find_effect(char const * name)
-{
-  int e;
-
-  for (e = 0; sox_effect_fns[e]; ++e) {
-    const sox_effect_handler_t *effp = sox_effect_fns[e] ();
-    if (effp && effp->name && strcasecmp(effp->name, name) == 0)
-      return effp;                 /* Found it. */
-  }
-  return NULL;
-}
-
+/* Partially initialise the effect structure; signal info will come later */
 void sox_create_effect(sox_effect_t * effp, sox_effect_handler_t const * eh)
 {
   assert(eh);
@@ -80,70 +68,22 @@
   memset(effp, 0, sizeof(*effp));
   effp->global_info = &effects_global_info;
   effp->handler = *eh;
-  if (!effp->handler.getopts) effp->handler.getopts = effect_nothing_getopts;
-  if (!effp->handler.start  ) effp->handler.start   = effect_nothing;
-  if (!effp->handler.flow   ) effp->handler.flow    = effect_nothing_flow;
-  if (!effp->handler.drain  ) effp->handler.drain   = effect_nothing_drain;
-  if (!effp->handler.stop   ) effp->handler.stop    = effect_nothing;
-  if (!effp->handler.kill   ) effp->handler.kill    = effect_nothing;
+  if (!effp->handler.getopts) effp->handler.getopts = default_getopts;
+  if (!effp->handler.start  ) effp->handler.start   = default_function;
+  if (!effp->handler.flow   ) effp->handler.flow    = default_flow;
+  if (!effp->handler.drain  ) effp->handler.drain   = default_drain;
+  if (!effp->handler.stop   ) effp->handler.stop    = default_function;
+  if (!effp->handler.kill   ) effp->handler.kill    = default_function;
 }
 
-/*
- * Copy input and output signal info into effect structures.
- * Must pass in a bitmask containing info on whether SOX_EFF_CHAN
- * or SOX_EFF_RATE has been used previously on this effect stream.
- * If not running multiple effects then just pass in a value of 0.
- *
- * Return value is the same mask plus addition of SOX_EFF_CHAN or
- * SOX_EFF_RATE if it was used in this effect.  That make this
- * return value can be passed back into this function in future
- * calls.
- */
 
-int sox_update_effect(sox_effect_t * effp, const sox_signalinfo_t * in,
-                      const sox_signalinfo_t * out, int effect_mask)
-{
-  effp->ininfo = *in;
-  effp->outinfo = *out;
 
-  if (in->channels != out->channels) {
-    /* Only effects with SOX_EFF_CHAN flag can actually handle
-     * outputing a different number of channels then the input.
-     */
-    if (!(effp->handler.flags & SOX_EFF_CHAN)) {
-      /* If this effect is being run before a SOX_EFF_CHAN effect
-       * then its output is the same as the input file; otherwise,
-       * its input contains the same number of channels as the
-       * output file. */
-      if (effect_mask & SOX_EFF_CHAN)
-        effp->ininfo.channels = out->channels;
-      else
-        effp->outinfo.channels = in->channels;
-    }
-  }
+/* Effects chain: */
 
-  if (in->rate != out->rate) {
-    /* Only SOX_EFF_RATE effects can handle an input that
-     * has a different sample rate from the output. */
-    if (!(effp->handler.flags & SOX_EFF_RATE)) {
-      if (effect_mask & SOX_EFF_RATE)
-        effp->ininfo.rate = out->rate;
-      else
-        effp->outinfo.rate = in->rate;
-    }
-  }
-
-  if (effp->handler.flags & SOX_EFF_CHAN)
-    effect_mask |= SOX_EFF_CHAN;
-  if (effp->handler.flags & SOX_EFF_RATE)
-    effect_mask |= SOX_EFF_RATE;
-
-  return effect_mask;
-}
-
 sox_effect_t * sox_effects[SOX_MAX_EFFECTS];
 unsigned sox_neffects;
 
+/* Effect can call in start() or flow() to set minimum input size to flow() */
 int sox_effect_set_imin(sox_effect_t * effp, sox_size_t imin)
 {
   if (imin > sox_bufsiz / effp->flows) {
@@ -155,94 +95,57 @@
   return SOX_SUCCESS;
 }
 
-int sox_add_effect(sox_effect_t * effp, sox_signalinfo_t * in, sox_signalinfo_t * out, int * effects_mask)
-{
-  unsigned f, flows;
+/* Add an effect to the chain. *in is the input signal for this effect. *out is
+ * a suggestion as to what the output signal should be, but depending on its
+ * given options and *in, the effect can choose to do differently.  Whatever
+ * output rate and channels the effect does produce are written back to *in,
+ * ready for the next effect in the chain.
+ */
+int sox_add_effect(sox_effect_t * effp, sox_signalinfo_t * in, sox_signalinfo_t
+    const * out) { int ret, (*start)(sox_effect_t * effp) =
+  effp->handler.start; unsigned f;
 
-  if (sox_neffects == SOX_MAX_EFFECTS)
+  if (effp->handler.flags & SOX_EFF_NULL) {
+    sox_report("has no effect (is a proxy effect)");
+    return SOX_EFF_NULL;
+  }
+  effp->outinfo = effp->ininfo = *in;
+  if (effp->handler.flags & SOX_EFF_CHAN)
+    effp->outinfo.channels = out->channels;
+  if (effp->handler.flags & SOX_EFF_RATE)
+    effp->outinfo.rate = out->rate;
+  effp->flows =
+    (effp->handler.flags & SOX_EFF_MCHAN)? 1 : effp->ininfo.channels;
+  effp->clips = 0;
+  effp->imin = 0;
+  ret = start(effp);
+  if (ret == SOX_EFF_NULL) {
+    sox_report("has no effect in this configuration");
+    return SOX_EFF_NULL;
+  }
+  if (ret != SOX_SUCCESS)
     return SOX_EOF;
+  *in = effp->outinfo;
 
-  *effects_mask = sox_update_effect(effp, in, out, *effects_mask);
-
-  flows = (effp->handler.flags & SOX_EFF_MCHAN)? 1 : effp->ininfo.channels;
-
-  sox_effects[sox_neffects] = xcalloc(flows, sizeof(sox_effects[sox_neffects][0]));
+  if (sox_neffects == SOX_MAX_EFFECTS) {
+    sox_fail("Too many effects!");
+    return SOX_EOF;
+  }
+  sox_effects[sox_neffects] =
+    xcalloc(effp->flows, sizeof(sox_effects[sox_neffects][0]));
   sox_effects[sox_neffects][0] = *effp;
-  sox_effects[sox_neffects][0].flows = flows;
 
-  for (f = 1; f < flows; ++f)
-    sox_effects[sox_neffects][f] = sox_effects[sox_neffects][0];
+  for (f = 1; f < effp->flows; ++f) {
+    sox_effects[sox_neffects][f] = *effp;
+    sox_effects[sox_neffects][f].flow = f;
+    if (start(&sox_effects[sox_neffects][f]) != SOX_SUCCESS)
+      return SOX_EOF;
+  }
 
   ++sox_neffects;
   return SOX_SUCCESS;
 }
 
-static void stop_effect(unsigned e)
-{
-  sox_effect_t * effp = &sox_effects[e][0];
-  unsigned f;
-
-  sox_size_t clips = 0;
-
-  for (f = 0; f < effp->flows; ++f) {
-    effp->handler.stop(&sox_effects[e][f]);
-    clips += sox_effects[e][f].clips;
-  }
-  if (clips != 0)
-    sox_warn("clipped %u samples; decrease volume?", clips);
-}
-
-void sox_stop_effects(void)
-{
-  unsigned e;
-  for (e = 0; e < sox_neffects; ++e)
-    stop_effect(e);
-}
-
-int sox_start_effects(void)
-{
-  unsigned e, f, i;
-  int ret = SOX_SUCCESS;
-
-  for (e = 0; e < sox_neffects; ++e) {
-    sox_effect_t * effp = &sox_effects[e][0];
-    sox_bool is_always_null = (effp->handler.flags & SOX_EFF_NULL) != 0;
-    int (*start)(sox_effect_t * effp) = effp->handler.start;
-
-    if (is_always_null)
-      sox_report("has no effect (is a proxy effect)");
-    else {
-      effp->clips = 0;
-      effp->imin = 0;
-      ret = start(effp);
-      if (ret == SOX_EFF_NULL)
-        sox_report("has no effect in this configuration");
-      else if (ret != SOX_SUCCESS)
-        return SOX_EOF;
-    }
-    if (is_always_null || ret == SOX_EFF_NULL) { /* remove from the chain */
-      free(sox_effects[e]);
-      --sox_neffects;
-      for (i = e--; i < sox_neffects; ++i)
-        sox_effects[i] = sox_effects[i + 1];
-    }
-    else for (f = 1; f < sox_effects[e][0].flows; ++f) {
-      sox_effects[e][f].clips = 0;
-      if (start(&sox_effects[e][f]) != SOX_SUCCESS)
-        return SOX_EOF;
-    }
-  }
-  for (e = 0; e < sox_neffects; ++e) {
-    sox_effect_t * effp = &sox_effects[e][0];
-    #undef sox_report
-    #define sox_report     sox_message_filename="effects chain",sox_report
-    sox_report("%-10s %uHz %u channels %s",
-        effp->handler.name, effp->ininfo.rate, effp->ininfo.channels,
-        (effp->handler.flags & SOX_EFF_MCHAN)? "(multi)" : "");
-  }
-  return SOX_SUCCESS;
-}
-
 static sox_ssample_t **ibufc, **obufc; /* Channel interleave buffers */
 
 static int flow_effect(unsigned n)
@@ -344,6 +247,7 @@
   return effstatus == SOX_SUCCESS? SOX_SUCCESS : SOX_EOF;
 }
 
+/* Flow data through the effects chain until an effect or callback gives EOF */
 int sox_flow_effects(int (* callback)(sox_bool all_done))
 {
   int flow_status = SOX_SUCCESS;
@@ -403,6 +307,7 @@
   return flow_status;
 }
 
+/* Remove all effects from the chain */
 void sox_delete_effects(void)
 {
   while (sox_neffects)
@@ -410,8 +315,9 @@
 }
 
 
-/* Effects handlers. */
 
+/* Effects library: */
+
 sox_effect_fn_t sox_effect_fns[] = {
 #define EFFECT(f) sox_##f##_effect_fn,
 #include "effects.h"
@@ -418,3 +324,16 @@
 #undef EFFECT
   NULL
 };
+
+/* Find a named effect in the effects library */
+sox_effect_handler_t const * sox_find_effect(char const * name)
+{
+  int e;
+
+  for (e = 0; sox_effect_fns[e]; ++e) {
+    const sox_effect_handler_t *eh = sox_effect_fns[e] ();
+    if (eh && eh->name && strcasecmp(eh->name, name) == 0)
+      return eh;                 /* Found it. */
+  }
+  return NULL;
+}
--- a/src/sox.c
+++ b/src/sox.c
@@ -1216,79 +1216,68 @@
   return &handler;
 }
 
-static void add_default_effect(char const *name, int *effects_mask)
+static void add_auto_effect(char const * name, sox_signalinfo_t * signal)
 {
-  sox_effect_t e;
+  sox_effect_t eff;
 
-  /* Default name should always be correct! */
-  sox_create_effect(&e, sox_find_effect(name));
-  if (e.handler.getopts(&e, 0, NULL) == SOX_EOF)  /* Set up with default opts */
+  /* Auto effect should always succeed here */
+  sox_create_effect(&eff, sox_find_effect(name));
+  eff.handler.getopts(&eff, 0, NULL);          /* Set up with default opts */
+
+  /* But could fail here */
+  if (sox_add_effect(&eff, signal, &ofile->ft->signal) != SOX_SUCCESS)
     exit(2);
-  sox_add_effect(&e, &combiner, &ofile->ft->signal, effects_mask);
 }
 
 /* If needed effects are not given, auto-add at (performance) optimal point. */
 static void add_effects(void)
 {
-  unsigned i;
-  int effects_mask = 0;
-  sox_bool need_rate = combiner.rate     != ofile->ft->signal.rate;
-  sox_bool need_chan = combiner.channels != ofile->ft->signal.channels;
-  int user_mchan = -1;
+  sox_signalinfo_t signal = combiner;
+  unsigned i, min_chan = 0, min_rate = 0;
   sox_effect_t eff;
 
-  { /* Check if we have to add effects to change rate/chans or if the
-       user has specified effects to do this, in which case, check if
-       too many rate/channel-changing effects have been specified:     */
-    int user_chan_effects = 0, user_rate_effects = 0;
-
-    for (i = 0; i < nuser_effects; i++) {
-      if (user_efftab[i].handler.flags & SOX_EFF_CHAN) {
-        need_chan = sox_false;
-        ++user_chan_effects;
-      }
-      if (user_efftab[i].handler.flags & SOX_EFF_RATE) {
-        need_rate = sox_false;
-        ++user_rate_effects;
-      }
-      if (user_efftab[i].handler.flags & SOX_EFF_MCHAN)
-        user_mchan = i;
-    }
-    if (user_chan_effects > 1) {
-      sox_fail("Cannot specify multiple effects that change number of channels");
-      exit(2);
-    }
-    if (user_rate_effects > 1) {
-      sox_fail("Cannot specify multiple effects that change sample rate");
-      exit(2);
-    }
+  /* Find points after which we might add effects to change rate/chans */
+  for (i = 0; i < nuser_effects; i++) {
+    if (user_efftab[i].handler.flags & (SOX_EFF_CHAN|SOX_EFF_MCHAN))
+      min_chan = i + 1;
+    if (user_efftab[i].handler.flags & SOX_EFF_RATE)
+      min_rate = i + 1;
   }
-
+  /* 1st `effect' in the chain is the input combiner */
   sox_create_effect(&eff, input_combiner_effect_fn());
-  sox_add_effect(&eff, &combiner, &ofile->ft->signal, &effects_mask);
+  sox_add_effect(&eff, &signal, &ofile->ft->signal);
 
-  /* Copy user specified effects into the real effects */
+  /* Add auto effects if appropriate; add user specified effects */
   for (i = 0; i <= nuser_effects; i++) {
     /* If reducing channels, it's faster to do so before all other effects: */
-    if ((int)i > user_mchan && need_chan && combiner.channels > ofile->ft->signal.channels) {
-      add_default_effect("mixer", &effects_mask);
-      need_chan = sox_false;
-    }
+    if (signal.channels > ofile->ft->signal.channels && i >= min_chan)
+      add_auto_effect("mixer", &signal);
+
     /* If reducing rate, it's faster to do so before all other effects
      * (except reducing channels): */
-    if (need_rate)
-      if (i == nuser_effects || combiner.rate > ofile->ft->signal.rate) {
-        add_default_effect("resample", &effects_mask);
-        need_rate = sox_false;
-      }
+    if (signal.rate > ofile->ft->signal.rate && i >= min_rate)
+      add_auto_effect("resample", &signal);
+
     if (i < nuser_effects)
-      sox_add_effect(&user_efftab[i], &combiner, &ofile->ft->signal, &effects_mask);
+      sox_add_effect(&user_efftab[i], &signal, &ofile->ft->signal);
   }
-  if (need_chan)
-    add_default_effect("mixer", &effects_mask);
+  /* Add auto effects if still needed at this point */
+  if (signal.rate != ofile->ft->signal.rate)
+    add_auto_effect("resample", &signal);  /* Must be up-sampling */
+  if (signal.channels != ofile->ft->signal.channels)
+    add_auto_effect("mixer", &signal);     /* Must be increasing channels */
 
+  /* Last `effect' in the chain is the output file */
   sox_create_effect(&eff, output_effect_fn());
-  sox_add_effect(&eff, &combiner, &ofile->ft->signal, &effects_mask);
+  if (sox_add_effect(&eff, &signal, &ofile->ft->signal) != SOX_SUCCESS)
+    exit(2);
+
+  for (i = 0; i < sox_neffects; ++i) {
+    sox_effect_t * effp = &sox_effects[i][0];
+    sox_report("effects chain: %-10s %uHz %u channels %s",
+        effp->handler.name, effp->ininfo.rate, effp->ininfo.channels,
+        (effp->handler.flags & SOX_EFF_MCHAN)? "(multi)" : "");
+  }
 }
 
 static void open_output_file(sox_size_t olen)
@@ -1347,6 +1336,22 @@
   return user_abort? SOX_EOF : SOX_SUCCESS;
 }
 
+static void sox_stop_effects(void)
+{
+  unsigned e, f;
+  for (e = 0; e < sox_neffects; ++e) {
+    sox_effect_t * effp = &sox_effects[e][0];
+    sox_size_t clips = 0;
+
+    for (f = 0; f < effp->flows; ++f) {
+      effp->handler.stop(&sox_effects[e][f]);
+      clips += sox_effects[e][f].clips;
+    }
+    if (clips != 0)
+      sox_warn("clipped %u samples; decrease volume?", clips);
+  }
+}
+
 /*
  * Process:   Input(s) -> Balancing -> Combiner -> Effects -> Output
  */
@@ -1418,8 +1423,6 @@
   open_output_file(olen);
 
   add_effects();
-  if (sox_start_effects() != SOX_SUCCESS)
-    exit(2); /* The failing effect should have displayed an error message */
 
   optimize_trim();
 
--- a/src/sox.h
+++ b/src/sox.h
@@ -399,8 +399,8 @@
 
 #define SOX_MAX_EFFECT_PRIVSIZE SOX_MAX_FILE_PRIVSIZE
 
-#define SOX_EFF_CHAN     1           /* Effect can mix channels up/down */
-#define SOX_EFF_RATE     2           /* Effect can alter data rate */
+#define SOX_EFF_CHAN     1           /* Effect can alter # of channels */
+#define SOX_EFF_RATE     2           /* Effect can alter sample rate */
 #define SOX_EFF_LENGTH   4           /* Effect can alter audio length */
 #define SOX_EFF_MCHAN    8           /* Effect can handle multi-channel */
 #define SOX_EFF_NULL     16          /* Effect does nothing */
@@ -435,12 +435,12 @@
   sox_size_t               odone, olen;  /* consumed, total length */
   sox_size_t               imin;         /* minimum input buffer size */
   sox_size_t               clips;        /* increment if clipping occurs */
-  sox_size_t               flows;
+  sox_size_t               flows;        /* 1 if MCHAN, # chans otherwise */
+  sox_size_t               flow;         /* flow # */
 };
 
 sox_effect_handler_t const *sox_find_effect(char const * name);
 void sox_create_effect(sox_effect_t * effp, sox_effect_handler_t const *e);
-int sox_update_effect(sox_effect_t * effp, const sox_signalinfo_t *in, const sox_signalinfo_t *out, int effect_mask);
 
 /* Effects chain */
 #define SOX_MAX_EFFECTS 20
@@ -447,10 +447,8 @@
 extern sox_effect_t * sox_effects[SOX_MAX_EFFECTS];
 extern unsigned sox_neffects;
 int sox_effect_set_imin(sox_effect_t * effp, sox_size_t imin);
-int sox_add_effect(sox_effect_t * e, sox_signalinfo_t * in, sox_signalinfo_t * out, int * effects_mask);
-int sox_start_effects(void);
+int sox_add_effect(sox_effect_t * effp, sox_signalinfo_t * in, sox_signalinfo_t const * out);
 int sox_flow_effects(int (* callback)(sox_bool all_done));
-void sox_stop_effects(void);
 void sox_delete_effects(void);
 
 char const * sox_parsesamples(sox_rate_t rate, const char *str, sox_size_t *samples, int def);