shithub: sox

Download patch

ref: d5aabbc466238d0231e17568ad52f123eb6f2616
parent: ebeb3d0c6599523b9c292909a4ccbf31780c50bf
author: robs <robs>
date: Sun Feb 10 07:15:12 EST 2008

normalise effect

--- a/AUTHORS
+++ b/AUTHORS
@@ -19,7 +19,7 @@
                 formats.
 		Effects: key, tempo, pad, bass, treble, new reverb, new
 		flanger, soft-knee companding, speed via resampling, filters
-		makeover inc. gnuplot & octave plotting, splice, remix;
+		makeover inc. gnuplot & octave plotting, splice, remix, norm;
 		new effects chain with buffering and any # channels.
                 Others: soxi, open input files via URL, file merging, play
                 multiple files with mixed format types, play with replay-gain,
--- a/ChangeLog
+++ b/ChangeLog
@@ -15,6 +15,7 @@
 
   o Added effect to splice together audio sections.  (robs)
   o Added remix effect (complements the mixer effect).  (robs)
+  o Added norm (normalise) effect.  (robs)
 
 Other new features:
 
--- a/README
+++ b/README
@@ -85,6 +85,7 @@
   o Apply an equalizer effect
   o Add dithering/masking noise to a signal
   o Multi-band compander
+  o Normalise the audio level
   o Pad with silence
   o Pan sound between channels
   o Apply a phaser effect
--- a/soxeffect.7
+++ b/soxeffect.7
@@ -560,6 +560,38 @@
 	sox noisy.au -n trim 0 1 noiseprof | play noisy.au noisered
 .EE
 .TP
+\fBnorm\fR [\fIlevel\fR]
+Normalise audio to 0dB FSD or to a given level relative to 0dB.
+Requires temporary file space to store the audio to be normalised.
+.SP
+To create a normalised copy of an audio file,
+.EX
+	sox infile outfile norm
+.EE
+can be used, though note that if `infile' has a simple encoding (e.g.
+PCM), then
+.EX
+	sox infile outfile vol \`sox infile -n stat -v 2>&1\`
+.EE
+(on systems that support this construct) might be quicker to execute
+(though perhaps not to type!) as it doesn't require a temporary file.
+.SP
+For a more complex example, suppose that `effect1' performs some unknown
+or unpredictable attenuation and that `effect2' requires up to 10dB of
+headroom, then
+.EX
+	sox infile outfile effect1 norm -10 effect2 norm
+.EE
+gives both effect2 and the output file the highest possible signal
+levels.
+.SP
+In most cases,
+.B norm \-3
+should be the maximum level used at the output file (to leave headroom
+for playback-resampling, etc.).  See also the discussions of clipping
+and Replay Gain in
+.BR sox (1).
+.TP
 \fBoops\fR
 Out Of Phase Stereo effect.
 Mixes stereo to twin-mono where each mono channel contains the
@@ -799,7 +831,7 @@
 .TP
 \fBrepeat \fIcount\fR
 Repeat the entire audio \fIcount\fR times.
-Requires disk space to store the data to be repeated.
+Requires temporary file space to store the audio to be repeated.
 Note that repeating once yields two copies: the original audio and the
 repeated audio.
 .TP
@@ -917,7 +949,7 @@
 .TP
 \fBreverse\fR
 Reverse the audio completely.
-Requires disk space to store the data to be reversed.
+Requires temporary file space to store the audio to be reversed.
 .TP
 \fBsilence \fR[\fB\-l\fR] \fIabove-periods\fR [\fIduration
 threshold\fR[\fBd\fR\^|\^\fB%\fR] [\fIbelow-periods duration
--- 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       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
+  biquad          echos           noiseprof       remix           stat
+  biquads         effects         noisered        repeat          stretch
+  chorus          fade            normalise       resample        swap
+  compand         FFT             pad             reverb          synth
+  compandt        filter          pan             reverse         tempo
+  dcshift         flanger         phaser          silence         tremolo
+  dither          key             pitch           skeleff         trim
+  earwax          mcompand        polyphas        speed           vibro
+  echo            mixer           rate            splice          vol
 )
 set(formats_srcs
   8svx            cvsd            hcom            s1-fmt          u2-fmt
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -199,7 +199,7 @@
 	  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
+	  vibro.c vol.c normalise.c
 libsfx_la_CFLAGS = @SAMPLERATE_CFLAGS@
 libsfx_la_LIBADD = @SAMPLERATE_LIBS@ libsox.la
 
--- a/src/compandt.c
+++ b/src/compandt.c
@@ -19,7 +19,7 @@
 #include "compandt.h"
 #include <string.h>
 
-#define LOG_TO_LOG10(x) ((x) * 20 / log(10.))
+#define LOG_TO_LOG10(x) ((x) * 20 / M_LN10)
 
 sox_bool sox_compandt_show(sox_compandt_t * t, sox_plot_t plot)
 {
@@ -78,12 +78,12 @@
 static void prepare_transfer_fn(sox_compandt_t * t)
 {
   int i;
-  double radius = t->curve_dB * log(10.) / 20;
+  double radius = t->curve_dB * M_LN10 / 20;
 
   for (i = 0; !i || t->segments[i-2].x; i += 2) {
     t->segments[i].y += t->outgain_dB;
-    t->segments[i].x *= log(10.) / 20; /* Convert to natural logs */
-    t->segments[i].y *= log(10.) / 20;
+    t->segments[i].x *= M_LN10 / 20; /* Convert to natural logs */
+    t->segments[i].y *= M_LN10 / 20;
   }
 
 #define line1 t->segments[i - 4]
--- a/src/effects.h
+++ b/src/effects.h
@@ -31,6 +31,7 @@
   EFFECT(mixer)
   EFFECT(noiseprof)
   EFFECT(noisered)
+  EFFECT(norm)
   EFFECT(oops)
   EFFECT(pad)
   EFFECT(pan)
--- /dev/null
+++ b/src/normalise.c
@@ -1,0 +1,112 @@
+/*
+ * 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.
+ */
+
+/* Effect: Normalise   (c) 2008 robs@users.sourceforge.net */
+
+#include "sox_i.h"
+#include <math.h>
+#include <string.h>
+
+typedef struct norm {
+  double        norm0;    /* Multiplier to take to 0dB FSD */
+  double        level;    /* Multiplier to take to 'level' */
+  sox_sample_t  min, max;
+  FILE          * tmp_file;
+} * priv_t;
+
+assert_static(sizeof(struct norm) <= SOX_MAX_EFFECT_PRIVSIZE,
+              /* else */ norm_PRIVSIZE_too_big);
+
+static int create(sox_effect_t * effp, int argc, char * * argv)
+{
+  priv_t p = (priv_t)effp->priv;
+  char dummy;
+
+  if (argc > 1 || (argc == 1 &&
+        (sscanf(*argv, "%lf%c", &p->level, &dummy) != 1 || p->level > 0)))
+    return sox_usage(effp);
+  p->level = dB_to_linear(p->level);
+  return SOX_SUCCESS;
+}
+
+static int start(sox_effect_t * effp)
+{
+  priv_t p = (priv_t)effp->priv;
+
+  p->norm0 = p->max = p->min = 0;
+  p->tmp_file = tmpfile();
+  if (p->tmp_file == NULL) {
+    sox_fail("can't create temporary file: %s", strerror(errno));
+    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)
+{
+  priv_t p = (priv_t)effp->priv;
+  sox_size_t len;
+
+  if (fwrite(ibuf, sizeof(*ibuf), *isamp, p->tmp_file) != *isamp) {
+    sox_fail("error writing temporary file: %s", strerror(errno));
+    return SOX_EOF;
+  }
+  for (len = *osamp; len; --len, ++ibuf) {
+    p->max = max(p->max, *ibuf);
+    p->min = min(p->min, *ibuf);
+  }
+  (void)obuf, *osamp = 0; /* samples not output until drain */
+  return SOX_SUCCESS;
+}
+
+static int drain(sox_effect_t * effp, sox_sample_t * obuf, sox_size_t * osamp)
+{
+  priv_t p = (priv_t)effp->priv;
+  sox_size_t len;
+  int result = SOX_SUCCESS;
+
+  if (!p->norm0) {
+    int shift_for_max = (4 - min(effp->outinfo.size, 4)) << 3;
+    double max = (SOX_SAMPLE_MAX >> shift_for_max) << shift_for_max;
+    p->norm0 = p->level * min(max / p->max, (double)SOX_SAMPLE_MIN / p->min);
+    rewind(p->tmp_file);
+  }
+  len = fread(obuf, sizeof(*obuf), *osamp, p->tmp_file);
+  if (len != *osamp && !feof(p->tmp_file)) {
+    sox_fail("error reading temporary file: %s", strerror(errno));
+    result = SOX_EOF;
+  }
+  for (*osamp = len; len; --len, ++obuf)
+    *obuf = floor(*obuf * p->norm0 + .5);
+  return result;
+}
+
+static int stop(sox_effect_t * effp)
+{
+  priv_t p = (priv_t)effp->priv;
+
+  fclose(p->tmp_file); /* auto-deleted by tmpfile */
+  return SOX_SUCCESS;
+}
+
+sox_effect_handler_t const * sox_norm_effect_fn(void)
+{
+  static sox_effect_handler_t handler = {
+    "norm", "[level]", 0, create, start, flow, drain, stop, NULL
+  };
+  return &handler;
+}
--- a/src/remix.c
+++ b/src/remix.c
@@ -81,7 +81,7 @@
         multiplier = sep1 == 'v' ? 1 : 0;
         PARSE(sep2, "%lf", multiplier, -HUGE_VAL, separators + 4);
         if (sep1 != 'v')
-          multiplier = (sep1 == 'p'? 1 : -1) * exp(multiplier / 20 * log(10.));
+          multiplier = (sep1 == 'p'? 1 : -1) * dB_to_linear(multiplier);
         mul_spec = sox_true;
       }
       if (chan2 < chan1) {int t = chan1; chan1 = chan2; chan2 = t;}
--- a/src/reverb.c
+++ b/src/reverb.c
@@ -131,7 +131,7 @@
   memset(p, 0, sizeof(*p));
   p->feedback = 1 - exp((reverberance - b) / (a * b));
   p->hf_damping = hf_damping / 100 * .3 + .2;
-  p->gain = exp(wet_gain_dB / 20 * log(10.)) * .015;
+  p->gain = dB_to_linear(wet_gain_dB) * .015;
   fifo_create(&p->input_fifo, sizeof(float));
   memset(fifo_write(&p->input_fifo, delay, 0), 0, delay * sizeof(float));
   for (i = 0; i <= ceil(depth); ++i) {
--- a/src/sox_i.h
+++ b/src/sox_i.h
@@ -253,6 +253,11 @@
 #ifndef M_PI_2
 #define M_PI_2  1.57079632679489661923  /* pi/2 */
 #endif
+#ifndef M_LN10
+#define M_LN10  2.30258509299404568402  /* natural log of 10 */
+#endif
+#define dB_to_linear(x) exp((x) * M_LN10 * 0.05)
+#define linear_to_dB(x) (log10(x) * 20)
 
 typedef struct sox_globals /* Global parameters (for effects & formats) */
 {
--- a/src/synth.c
+++ b/src/synth.c
@@ -511,8 +511,7 @@
          * |                            |
          * 0             p1             1
          */
-#define LOG_10_20        0.1151292546497022842009e0
-        synth_out = exp(-chan->p2 * LOG_10_20 * 100);  /* 0 ..  1 */
+        synth_out = dB_to_linear(chan->p2 * -100);  /* 0 ..  1 */
         if (phase < chan->p1)
           synth_out = synth_out * exp(phase * log(1 / synth_out) / chan->p1);
         else
--- a/src/vol.c
+++ b/src/vol.c
@@ -17,8 +17,6 @@
 #include "sox_i.h"
 #include <math.h>
 
-#define LOG_10_20 ((double)(0.1151292546497022842009e0))
-
 typedef struct {
   double    gain; /* amplitude gain. */
   sox_bool  uselimiter;
@@ -67,7 +65,7 @@
     if (!p)
       return sox_usage(effp);
     switch (p->value) {
-      case vol_dB: vol->gain = exp(vol->gain*LOG_10_20); break;
+      case vol_dB: vol->gain = dB_to_linear(vol->gain); break;
       case vol_power: /* power to amplitude, keep phase change */
         vol->gain = vol->gain > 0 ? sqrt(vol->gain) : -sqrt(-vol->gain);
         break;