shithub: sox

Download patch

ref: 3a5f9247329b4001bf88128d7c047da7ab916ffa
parent: 3f980b27d33ed0f73990cd0c4539ab70776e5dfd
author: robs <robs>
date: Wed Jul 16 04:17:39 EDT 2008

[FR 2012856] mix-power

--- a/ChangeLog
+++ b/ChangeLog
@@ -96,6 +96,8 @@
   o Now possible to control play-back resampling quality (and hence
     cpu-load) via the PLAY_RATE_ARG environment variable.  (robs)
   o Command line support for multiple file comments.  (robs)
+  o New --combine=mix-power option to mix combine using 1/sqrt(n) scaling
+    instead of 1/n [FR 2012856].  (robs)
   o New `soxi' utility to extract/display file header fields.  (robs)
   o Pkg-config support. (Pascal Giard)
   o Simple VU meter.  (robs)
--- a/sox.1
+++ b/sox.1
@@ -360,8 +360,9 @@
 If clipping occurs at any point during processing, then
 SoX will display a warning message to that effect.
 .SS Input File Combining 
-SoX's input combiner can combine multiple files using any of the
-following methods: `concatenate', `sequence', `mix', or `merge'.
+SoX's input combiner can be configured (see OPTIONS below) to
+combine multiple files using any of the
+following methods: `concatenate', `sequence', `mix', `mix-power', or `merge'.
 The default method is `sequence' for
 .BR play ,
 and `concatenate' for
@@ -387,7 +388,7 @@
 device, but is not generally useful when the output file is a normal
 file.
 .SP
-If the `mix' combining method is selected (with \fB-m\fR) then two or
+If the `mix' (or `mix-power') combining method is selected, then two or
 more input files must be given and will be mixed together to form the
 output file.  The number of channels in each input file need not be the
 same, however, SoX will issue a warning if they are not and some
@@ -395,7 +396,7 @@
 file.  A mixed audio file cannot be un-mixed (without reference to the
 orignal input files).
 .SP
-If the `merge' combining method is selected (with \fB-M\fR), then two or
+If the `merge' combining method is selected, then two or
 more input files must be given and will be merged together to form the
 output file.  The number of channels in each input file need not be the
 same.  A merged audio file comprises all of the channels from all of the
@@ -434,14 +435,24 @@
 ensure that clipping does not occur, SoX will automatically adjust the
 volume (amplitude) of each input signal by a factor of \(S1/\s-2n\s+2,
 where n is the number of input files.  If this results in audio that is
-too quiet or otherwise unbalanced then the input file volumes should be
-set manually as described above.
+too quiet or otherwise unbalanced then the input file volumes can be
+set manually as described above; using the
+.B norm
+effect on the mix is another alternative.
 .SP
-If mixed audio seems loud enough at some points through the audio but
+If mixed audio seems loud enough at some points through the mixed audio but
 too quiet in others, then dynamic-range compression should be applied to
 correct this\*msee the
 .B compand
 effect.
+.SP
+With the `mix-power' combine method, the
+mixed volume is appropriately equal to that of one of the input signals.
+This is achieved by balancing using a factor of 
+\(S1/\s-2\(srn\s+2 instead of \(S1/\s-2n\s+2.
+Note that this balancing factor does not guarantee that no clipping will occur,
+however, in many cases, the number of clips will be low and the resultant
+distortion imperceptable.
 .SS Stopping SoX
 Usually SoX will complete its processing and exit automatically, however
 if desired, it can be terminated by pressing the
@@ -544,7 +555,7 @@
 \fB\-\-buffer\fR \fBBYTES\fR
 Set the size in bytes of the buffers used for reading and writing sound data (default 8192).
 .TP
-\fB\-m\fR\^|\^\fB\-M\fR\^|\^\fB\-\-combine concatenate\fR\^|\^\fBmerge\fR\^|\^\fBmix\fR\^|\^\fBsequence\fR
+\fB\-m\fR\^|\^\fB\-M\fR\^|\^\fB\-\-combine concatenate\fR\^|\^\fBmerge\fR\^|\^\fBmix\fR\^|\^\fBmix\-power\fR\^|\^\fBsequence\fR
 Select the input file combining method;
 .B \-m
 selects `mix',
--- a/soxeffect.7
+++ b/soxeffect.7
@@ -773,7 +773,7 @@
 .B rabbit
 for other sample-rate changing effects.
 .TP
-\fBremix\fR [\fB\-a\fR\^|\^\fB\-m\fR] <\fIout-spec\fR>
+\fBremix\fR [\fB\-a\fR\^|\^\fB\-m\fR\^|\^\fB\-p\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]
@@ -879,6 +879,10 @@
 is a mono equivalent of the
 .B oops
 effect.
+.SP
+If the \fB\-p\fR option is given, then any automatic \(S1/\s-2n\s+2 scaling
+is replaced by \(S1/\s-2\(srn\s+2 (`power') scaling; this gives a louder mix
+but one that might occasionally clip.
 .TS
 center;
 c8 c8 c.
--- a/src/remix.c
+++ b/src/remix.c
@@ -20,6 +20,7 @@
 
 typedef struct {
   enum {semi, automatic, manual} mode;
+  sox_bool mix_power;
   unsigned num_out_channels, min_in_channels;
   struct {
     char * str;          /* Command-line argument to parse for this out_spec */
@@ -48,6 +49,7 @@
 {
   priv_t * p = (priv_t *)effp->priv;
   unsigned i, j;
+  double mult;
 
   p->min_in_channels = 0;
   for (i = 0; i < p->num_out_channels; ++i) {
@@ -87,9 +89,10 @@
       p->min_in_channels = max(p->min_in_channels, (unsigned)chan2);
     }
     p->out_specs[i].num_in_channels = j;
+    mult = 1. / (p->mix_power? sqrt((double)j) : 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;
+        p->out_specs[i].in_specs[j].multiplier = (p->mode == automatic || (p->mode == semi && !mul_spec)) ? mult : 1;
   }
   effp->out_signal.channels = p->num_out_channels;
   return SOX_SUCCESS;
@@ -100,6 +103,7 @@
   priv_t * p = (priv_t *)effp->priv;
   if (argc && !strcmp(*argv, "-m")) p->mode = manual   , ++argv, --argc;
   if (argc && !strcmp(*argv, "-a")) p->mode = automatic, ++argv, --argc;
+  if (argc && !strcmp(*argv, "-p")) p->mix_power = sox_true, ++argv, --argc;
   p->out_specs = lsx_calloc(p->num_out_channels = argc, sizeof(*p->out_specs));
   return parse(effp, argv, 1); /* No channels yet; parse with dummy */
 }
@@ -148,7 +152,7 @@
 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]}>",
+    "remix", "[-m|-a] [-p] <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, sizeof(priv_t)
   };
   return &handler;
--- a/src/sox.c
+++ b/src/sox.c
@@ -70,7 +70,7 @@
 
 /* gopts */
 
-static enum {sox_sequence, sox_concatenate, sox_mix, sox_merge, sox_multiply}
+static enum {sox_sequence, sox_concatenate, sox_mix, sox_mix_power, sox_merge, sox_multiply}
     combine_method = sox_concatenate;
 #define is_serial(m) ((m) <= sox_concatenate)
 #define is_parallel(m) (!is_serial(m))
@@ -440,8 +440,8 @@
       olen = max(olen, ilen[i]);
     }
     for (ws = 0; ws < olen; ++ws) /* wide samples */
-      if (combine_method == sox_mix) {          /* sum samples together */
-        for (s = 0; s < effp->in_signal.channels; ++s, ++p) {
+      if (combine_method == sox_mix || combine_method == sox_mix_power) {
+        for (s = 0; s < effp->in_signal.channels; ++s, ++p) { /* sum samples */
           *p = 0;
           for (i = 0; i < input_count; ++i)
             if (ws < ilen[i] && s < files[i]->ft->signal.channels) {
@@ -835,7 +835,8 @@
       if (combine_method == sox_concatenate) {
         sox_fail("Input files must have the same # channels");
         exit(1);
-      } else if (combine_method == sox_mix || combine_method == sox_multiply)
+      } else if (combine_method == sox_mix || combine_method == sox_mix_power ||
+          combine_method == sox_multiply)
         sox_warn("Input files don't have the same # channels");
     }
     if (min_rate != max_rate)
@@ -1200,6 +1201,7 @@
   ENUM_ITEM(sox_,sequence)
   ENUM_ITEM(sox_,concatenate)
   ENUM_ITEM(sox_,mix)
+  {"mix-power", sox_mix_power},
   ENUM_ITEM(sox_,merge)
   ENUM_ITEM(sox_,multiply)
   {0, 0}};
@@ -1706,6 +1708,8 @@
      * this, and will override it, possibly causing clipping to occur. */
     if (combine_method == sox_mix && !uservolume)
       f->volume = 1.0 / input_count;
+    else if (combine_method == sox_mix_power && !uservolume)
+      f->volume = 1.0 / sqrt((double)input_count);
 
     if (sox_mode == sox_rec && !j) {       /* Set the recording parameters: */
       if (input_count > 1)                 /* from the (just openned) next */
--- a/src/util.c
+++ b/src/util.c
@@ -41,10 +41,12 @@
   enum_item const * result = NULL; /* Assume not found */
 
   while (enum_items->text) {
+    if (strcasecmp(text, enum_items->text) == 0)
+      return enum_items;    /* Found exact match */
     if (strncasecmp(text, enum_items->text, strlen(text)) == 0) {
       if (result != NULL && result->value != enum_items->value)
         return NULL;        /* Found ambiguity */
-      result = enum_items;  /* Found match */
+      result = enum_items;  /* Found sub-string match */
     }
     ++enum_items;
   }