shithub: sox

Download patch

ref: f1ebb3d4d6f46a896ac91a2aef166ac1f84b86ee
parent: 926fc36598461ed3dc432a709047a741b4436a3a
author: robs <robs>
date: Sat Feb 9 05:31:38 EST 2008

Added remix effect

--- a/AUTHORS
+++ b/AUTHORS
@@ -8,6 +8,8 @@
 
 Mantainers:
 	Chris Bagwell		cbagwell@users.sourceforge.net
+		OSS and Sun players, bugfixes, ADPCM support,
+		patch collection and maintance.
 	Reuben Thomas		rrt@sc3d.org
 		Build system makeover, libsndfile and ffmpeg sound
 		format support, libao playback, Secret Rabbit Code
@@ -17,8 +19,8 @@
                 formats.
 		Effects: key, tempo, pad, bass, treble, new reverb, new
 		flanger, soft-knee companding, speed via resampling, filters
-		makeover inc.  gnuplot & octave plotting; new effects chain
-		with buffering and any # channels.
+		makeover inc. gnuplot & octave plotting, splice, remix;
+		new effects chain with buffering and any # channels.
                 Others: open input files via URL, file merging, play
                 multiple files with mixed format types, play with replay-gain,
                 building with cmake, much manual improvement and expansion,
@@ -94,8 +96,5 @@
 	Stuart Daines <sjd.u-net.com>
 		Patches for r/w support of gsm-encoded wav files,
 		cleanup of wav.c.
-	Chris Bagwell		cbagwell@sprynet.com
-		OSS and Sun players, bugfixes, ADPCM support,
-		patch collection and maintance.
 	Matthias Nutt
 		Multiple effects from command line.
--- a/ChangeLog
+++ b/ChangeLog
@@ -12,6 +12,7 @@
   Effects:
 
   o Added effect to splice together audio sections.  (robs)
+  o Added remix effect (complements the mixer effect).  (robs)
 
   Other new features:
 
@@ -92,7 +93,7 @@
   o Show (with --plot) compand transfer function.  (robs)
   o Allow e.g. "vol 6dB" (as well as "vol 6 dB").  (robs)
   o Changed deemph filter from 1st order to 2nd order for
-    better accuracy.  (robs)
+    slightly better accuracy.  (robs)
   o Add option to silence effect to leave periods of silence
     in and only strip out extra silence.   (Mark Schreiber)
   o synth can now generate any number of channels.  (robs)
--- a/soxeffect.7
+++ b/soxeffect.7
@@ -512,6 +512,9 @@
 .TE
 .DT
 .SP
+See also
+.B remix
+for a similar effect.
 .TP
 \fBnoiseprof\fR [\fIprofile-file\fR]
 Calculate a profile of the audio for use in noise reduction.  See the
@@ -651,6 +654,146 @@
 .B resample
 for other sample-rate changing effects, and see
 \fBresample\fR for more discussion of resampling.
+.TP
+\fBremix\fR [\fB\-a\fR\^|\^\fB\-m\fR] <\fIout-spec\fR>
+\fIout-spec\fR	= \fIin-spec\fR{\fB,\fIin-spec\fR} | \fB0\fR
+.br
+\fIin-spec\fR	= [\fIin-chan\fR]\^[\fB\-\fR[\fIin-chan2\fR]]\^[\fIvol-spec\fR]
+.br
+\fIvol-spec\fR	= \fBp\fR\^|\^\fBi\fR\^|\^\fBv\^\fR[\fIvolume\fR]
+.br
+.SP
+Select and mix input audio channels into output audio channels.  Each output
+channel is specified, in turn, by a given \fIout-spec\fR: a list of
+contributing input channels and volume specifications.
+.SP
+Note that this effect operates on the audio
+.I channels
+within the SoX effects processing chain; it should not be confused with the 
+.B \-m
+global option (where multiple
+.I files
+are mix-combined before entering the effects chain).
+.SP
+An
+.I out-spec
+contains comma-separated input channel-numbers and hyphen-delimited
+channel-number ranges; alternatively,
+.B 0
+may be given to create a silent output channel.  For example,
+.EX
+	sox input.au output.au remix 6 7 8 0
+.EE
+creates an output file with four channels, where channels 1, 2, and 3 are
+copies of channels 6, 7, and 8 in the input file, and channel 4 is silent.
+Whereas
+.EX
+	sox input.au output.au remix 1-3,7 3
+.EE
+creates a stereo output file where the left channel is a mix-down of input
+channels 1, 2, 3, and 7, and the right channel is a copy of input channel 3.
+.SP
+Where a range of channels is specified, the channel numbers to the left and
+right of the hyphen are optional and default to 1 and to the number of input
+channels respectively. Thus
+.EX
+	sox input.au output.au remix -
+.EE
+performs a mix-down of all input channels to mono.
+.SP
+By default, where an output channel is mixed from multiple (n) input
+channels, each input channel will be scaled by a factor of \(S1/\s-2n\s+2.
+Custom mixing volumes can be set by following a given input channel or range
+of input channels with a \fIvol-spec\fR (volume specification).
+This is one of the letters \fBp\fR, \fBi\fR, or \fBv\fR,
+followed by a volume number, the meaning of which depends on the given
+letter and is defined as follows:
+.TS
+center;
+lI lI lI
+c l l.
+Letter	Volume number	Notes
+p	power adjust in dB	0 = no change
+i	power adjust in dB	T{
+.na
+As `p', but invert the audio
+T}
+v	voltage multiplier	T{
+.na
+1 = no change, 0\*d5 \(~= 6dB attenuation, 2 \(~= 6dB gain, \-1 = invert
+T}
+.TE
+
+If an
+.I out-spec
+includes at least one
+.I vol-spec
+then, by default, \(S1/\s-2n\s+2 scaling is not applied to any other channels in the
+same out-spec (though may be in other out-specs).
+The \-a (automatic)
+option however, can be given to retain the automatic scaling in this
+case.  For example,
+.EX
+	sox input.au output.au remix 1,2 3,4v0.8
+.EE
+results in channel level multipliers of 0\*d5,0\*d5 1,0\*d8, whereas
+.EX
+	sox input.au output.au remix -a 1,2 3,4v0.8
+.EE
+results in channel level multipliers of 0\*d5,0\*d5 0\*d5,0\*d8.
+.SP
+The \-m (manual) option disables all automatic volume adjustments, so
+.EX
+	sox input.au output.au remix -m 1,2 3,4v0.8
+.EE
+results in channel level multipliers of 1,1 1,0\*d8.
+.SP
+The volume number is optional and omitting it corresponds to no volume
+change; however, the only case in which this is useful is in conjunction
+with
+.BR i .
+For example, if
+.I input.au
+is stereo, then
+.EX
+	sox input.au output.au remix 1,2i
+.EE
+is a mono equivalent of the
+.B oops
+effect.
+.TS
+center;
+c8 c8 c.
+*	*	*
+.TE
+.SP
+One typical use of the
+.B remix
+effect is to split an audio file into a set of files, each containing
+one of the constituent channels (in order to perform subsequent
+processing on individual audio channels).  Where more than a few
+channels are involved, a script such as the following is useful:
+.EX
+#!/bin/sh                        # This is a Bourne shell script
+chans=$(sox -V "$1" -n 2>&1|grep \^Chan|head -1|sed "s/.*: //")
+while [ $chans -ge 1 ]; do
+  chans0=$(printf %02i $chans)   # 2 digits hence up to 99 chans
+  out=$(echo "$1"|sed "s/\\(.*\\)\\.\\(.*\\)/\\1-$chans0.\\2/")
+  sox "$1" "$out" remix $chans
+  chans=$(expr $chans - 1)
+done
+.EE
+If a file
+.I input.au
+containing six audio channels were given, the script would produce six
+output files:
+.IR input-01.au ,
+\fIinput-02.au\fR, ...,
+.IR input-06.au .
+.SP
+See also
+.B mixer
+for a similar effect.
 .TP
 \fBrepeat \fIcount\fR
 Repeat the entire audio \fIcount\fR times.
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -18,15 +18,15 @@
 
 # Format with: !xargs echo|tr ' ' '\n'|sort|column|expand|sed 's/^/  /'
 set(effects_srcs
-  biquad          echos           noiseprof       resample        swap
-  biquads         effects         noisered        reverb          synth
-  chorus          fade            pad             reverse         tempo
-  compand         FFT             pan             silence         tremolo
-  compandt        filter          phaser          skeleff         trim
-  dcshift         flanger         pitch           speed           vibro
-  dither          key             polyphas        splice          vol
-  earwax          mcompand        rate            stat
-  echo            mixer           repeat          stretch
+  biquad          echos           noiseprof       repeat          stretch
+  biquads         effects         noisered        resample        swap
+  chorus          fade            pad             reverb          synth
+  compand         FFT             pan             reverse         tempo
+  compandt        filter          phaser          silence         tremolo
+  dcshift         flanger         pitch           skeleff         trim
+  dither          key             polyphas        speed           vibro
+  earwax          mcompand        rate            splice          vol
+  echo            mixer           remix           stat
 )
 set(formats_srcs
   8svx            cvsd            hcom            s1-fmt          u2-fmt
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -196,7 +196,7 @@
 	  compandt.c compandt.h dcshift.c dither.c earwax.c echo.c echos.c \
 	  effects.c effects.h fade.c FFT.c FFT.h fifo.h filter.c flanger.c key.c \
 	  ladspa.c mcompand.c mixer.c noiseprof.c noisered.c noisered.h pad.c \
-	  pan.c phaser.c pitch.c polyphas.c rabbit.c rate.c repeat.c	\
+	  pan.c phaser.c pitch.c polyphas.c rabbit.c rate.c remix.c repeat.c \
 	  resample.c reverb.c reverse.c silence.c skeleff.c speed.c	\
 	  splice.c stat.c stretch.c swap.c synth.c tempo.c tremolo.c trim.c \
 	  vibro.c vol.c
--- a/src/effects.h
+++ b/src/effects.h
@@ -43,6 +43,7 @@
 #endif
   EFFECT(rate)
   EFFECT(repeat)
+  EFFECT(remix)
   EFFECT(resample)
   EFFECT(reverb)
   EFFECT(reverse)
--- /dev/null
+++ b/src/remix.c
@@ -1,0 +1,163 @@
+/*
+ * Effect: remix
+ * 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 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,
+ * Fifth Floor, 51 Franklin Street, Boston, MA 02111-1301, USA.
+ */
+
+#include "sox_i.h"
+#include <math.h>
+#include <string.h>
+
+typedef struct remix
+{
+  enum {semi, automatic, manual} mode;
+  unsigned num_out_channels, min_in_channels;
+  struct {
+    char * str;          /* Command-line argument to parse for this out_spec */
+    unsigned num_in_channels;
+    struct in_spec {
+      unsigned channel_num;
+      double   multiplier;
+    } * in_specs;
+  } * out_specs;
+} * remix_t;
+
+assert_static(sizeof(struct remix) <= SOX_MAX_EFFECT_PRIVSIZE,
+              /* else */ remix_PRIVSIZE_too_big);
+
+#define PARSE(SEP, SCAN, VAR, MIN, SEPARATORS) do {\
+  end = strpbrk(text, SEPARATORS); \
+  if (end == text) \
+    SEP = *text++; \
+  else { \
+    SEP = (SEPARATORS)[strlen(SEPARATORS) - 1]; \
+    n = sscanf(text, SCAN"%c", &VAR, &SEP); \
+    if (VAR < MIN || (n == 2 && !strchr(SEPARATORS, SEP))) \
+      return sox_usage(effp); \
+    text = end? end + 1 : text + strlen(text); \
+  } \
+} while (0)
+
+static int parse(sox_effect_t * effp, char * * argv, unsigned channels)
+{
+  remix_t p = (remix_t) effp->priv;
+  unsigned i, j;
+
+  p->min_in_channels = 0;
+  for (i = 0; i < p->num_out_channels; ++i) {
+    sox_bool mul_spec = sox_false;
+    char * text, * end;
+    if (argv) /* 1st parse only */
+      p->out_specs[i].str = xstrdup(argv[i]);
+    for (j = 0, text = p->out_specs[i].str; *text;) {
+      static char const separators[] = "-vpi,";
+      char sep1, sep2;
+      int chan1 = 1, chan2 = channels, n;
+      double multiplier = HUGE_VAL;
+
+      PARSE(sep1, "%i", chan1, 0, separators);
+      if (!chan1) {
+       if (j || *text)
+         return sox_usage(effp);
+       continue;
+      }
+      if (sep1 == '-')
+        PARSE(sep1, "%i", chan2, 0, separators + 1);
+      else chan2 = chan1;
+      if (sep1 != ',') {
+        multiplier = sep1 == 'v' ? 1 : 0;
+        PARSE(sep2, "%lf", multiplier, -HUGE_VAL, separators + 4);
+        if (sep1 != 'v')
+          multiplier = (sep1 == 'p'? 1 : -1) * exp(multiplier / 40 * log(10.));
+        mul_spec = sox_true;
+      }
+      if (chan2 < chan1) {int t = chan1; chan1 = chan2; chan2 = t;}
+      p->out_specs[i].in_specs = xrealloc(p->out_specs[i].in_specs,
+          (j + chan2 - chan1 + 1) * sizeof(*p->out_specs[i].in_specs));
+      while (chan1 <= chan2) {
+        p->out_specs[i].in_specs[j].channel_num = chan1++ - 1;
+        p->out_specs[i].in_specs[j++].multiplier = multiplier;
+      }
+      p->min_in_channels = max(p->min_in_channels, (unsigned)chan2);
+    }
+    p->out_specs[i].num_in_channels = j;
+    for (j = 0; j < p->out_specs[i].num_in_channels; ++j)
+      if (p->out_specs[i].in_specs[j].multiplier == HUGE_VAL)
+        p->out_specs[i].in_specs[j].multiplier = (p->mode == automatic || (p->mode == semi && !mul_spec)) ?  1. / p->out_specs[i].num_in_channels : 1;
+  }
+  effp->outinfo.channels = p->num_out_channels;
+  return SOX_SUCCESS;
+}
+
+static int create(sox_effect_t * effp, int argc, char * * argv)
+{
+  remix_t p = (remix_t) effp->priv;
+  if (argc && !strcmp(*argv, "-m")) p->mode = manual   , ++argv, --argc;
+  if (argc && !strcmp(*argv, "-a")) p->mode = automatic, ++argv, --argc;
+  p->out_specs = xcalloc(p->num_out_channels = argc, sizeof(*p->out_specs));
+  return parse(effp, argv, 1); /* No channels yet; parse with dummy */
+}
+
+static int start(sox_effect_t * effp)
+{
+  remix_t p = (remix_t) effp->priv;
+  parse(effp, NULL, effp->ininfo.channels);
+  if (effp->ininfo.channels < p->min_in_channels) {
+    sox_fail("too few input channels");
+    return SOX_EOF;
+  }
+  return SOX_SUCCESS;
+}
+
+static int flow(sox_effect_t * effp, const sox_sample_t * ibuf,
+    sox_sample_t * obuf, sox_size_t * isamp, sox_size_t * osamp)
+{
+  remix_t p = (remix_t) effp->priv;
+  unsigned i, j, len;
+  len =  min(*isamp / effp->ininfo.channels, *osamp / effp->outinfo.channels);
+  *isamp = len * effp->ininfo.channels;
+  *osamp = len * effp->outinfo.channels;
+
+  for (; len--; ibuf += effp->ininfo.channels) for (j = 0; j < effp->outinfo.channels; j++) {
+    double out = 0;
+    for (i = 0; i < p->out_specs[j].num_in_channels; i++)
+      out += ibuf[p->out_specs[j].in_specs[i].channel_num] * p->out_specs[j].in_specs[i].multiplier;
+    *obuf++ = SOX_ROUND_CLIP_COUNT(out, effp->clips);
+  }
+  return SOX_SUCCESS;
+}
+
+static int kill(sox_effect_t * effp)
+{
+  remix_t p = (remix_t) effp->priv;
+  unsigned i;
+  for (i = 0; i < p->num_out_channels; ++i) {
+    free(p->out_specs[i].str);
+    free(p->out_specs[i].in_specs);
+  }
+  free(p->out_specs);
+  return SOX_SUCCESS;
+}
+
+sox_effect_handler_t const * sox_remix_effect_fn(void)
+{
+  static sox_effect_handler_t handler = {
+    "remix", "<0|in-chan[v|d|i volume]{,in-chan[v|d|i volume]}>",
+    SOX_EFF_MCHAN | SOX_EFF_CHAN,
+    create, start, flow, NULL, NULL, kill
+  };
+  return &handler;
+}
--- a/src/sox.c
+++ b/src/sox.c
@@ -1149,8 +1149,8 @@
 static void sigint(int s)
 {
   static struct timeval then;
-  if (input_count > 1 && show_progress && s == SIGINT && combine_method <= sox_concatenate &&
-             since(&then, 1.0, sox_true))
+  if (input_count > 1 && show_progress && s == SIGINT &&
+      combine_method <= sox_concatenate && since(&then, 1.0, sox_true))
     user_skip = sox_true;
   else user_abort = sox_true;
 }
@@ -1536,8 +1536,13 @@
     ofile->signal.size = combiner.size;
   if (ofile->signal.encoding == SOX_ENCODING_UNKNOWN)
     ofile->signal.encoding = combiner.encoding;
-  if (ofile->signal.channels == 0)
-    ofile->signal.channels = combiner.channels;
+  if (ofile->signal.channels == 0) {
+    unsigned j;
+    for (j = 0; j < nuser_effects && !ofile->signal.channels; ++j)
+      ofile->signal.channels = user_efftab[nuser_effects - 1 - j].outinfo.channels;
+    if (ofile->signal.channels == 0)
+      ofile->signal.channels = combiner.channels;
+  }
 
   combiner.rate *= sox_effects_globals.speed;