shithub: sox

Download patch

ref: c28e759b03dd2a3d74adecf9658775835c006aa0
parent: 93eed5cd3459c8c32cccf9aef8113bdca255221d
author: robs <robs>
date: Mon Jun 30 17:04:54 EDT 2008

Recent synth speed-up lost spectral purity in some cases.
This fixed version should be as fast as possible whilst remaining pure.
Also added a new pure exponential sweep, and a squared sweep.

--- a/ChangeLog
+++ b/ChangeLog
@@ -83,7 +83,8 @@
   o Improved `rate' resampling effect; resample, polyphase, & rabbit
     now deprecated.  (robs)
   o New `spectrogram' effect; creates a PNG (if built with PNG lib).  (robs)
-  o `synth' can now sweep linearly (as well as logarithmically).  (robs)
+  o `synth' can now sweep linearly and squarely (as well as
+    exponentially).  (robs)
   o Fix synth max. level setting for some output encodings.  (robs)
   o Fix crash on 64-bit arch. with tempo & key effects.  (Sami Liedes)
   o `gain' alias for the vol effect.  (robs)
--- a/soxeffect.7
+++ b/soxeffect.7
@@ -1160,6 +1160,8 @@
 .IP \fB\-o\ \fItext\fR
 Name of the spectrogram output PNG file, default `spectrogram.png'.
 .RE
+.TP
+\ 
 For the ability to perform off-line processing of spectral data, see the
 .B stat
 effect.
@@ -1301,7 +1303,7 @@
 will overwrite channel 1 with channel 2; creating a stereo
 file with both channels containing the same audio.
 .TP
-\fBsynth\fR [\fIlen\fR] {[\fItype\fR] [\fIcombine\fR] [\fIfreq\fR[\fBk\fR][\fI\-freq2\fR[\fBk\fR]|\fI~freq2\fR[\fBk\fR]]] [\fIoff\fR] [\fIph\fR] [\fIp1\fR] [\fIp2\fR] [\fIp3\fR]}
+\fBsynth\fR [\fIlen\fR] {[\fItype\fR] [\fIcombine\fR] [[\fB%\fR]\fIfreq\fR[\fBk\fR][\fB:\fR\^|\^\fB+\fR\^|\^\fB/\fR\^|\^\fB\-\fR[\fB%\fR]\fIfreq2\fR[\fBk\fR]]] [\fIoff\fR] [\fIph\fR] [\fIp1\fR] [\fIp2\fR] [\fIp3\fR]}
 This effect can be used to generate fixed or swept frequency audio tones
 with various wave shapes, or to generate wide-band noise of various
 `colours'.
@@ -1382,10 +1384,22 @@
 is given, then
 .I len
 must also have been given and the generated tone will be swept between
-the given frequencies.  If
-.I freq2
-is preceded with `-', the tone will change by a fixed number of octaves
-per second; if `~', a fixed number of hertz per second.
+the given frequencies.  The two given frequencies must be separated by
+one of the characters `:', `+', `/', or `-'.  This character is used to
+specify the sweep function as follows:
+.RS
+.IP \fB:\fR
+Linear: the tone will change by a fixed number of hertz per second.
+.IP \fB+\fR
+Square: a second-order function is used to change the tone.
+.IP \fB/\fR
+Exponential: the tone will change by a fixed number of semitones per second.
+.IP \fB\-\fR
+Exponential: as `/', but initial phase always zero, and stepped (less
+smooth) frequency changes.
+.RE
+.TP
+\ 
 Not used for noise.
 .SP
 \fIoff\fR is the bias (DC-offset) of the signal in percent; default=0.
--- a/src/synth.c
+++ b/src/synth.c
@@ -1,7 +1,7 @@
 /* libSoX synth - Synthesizer Effect.
  *
  * Copyright (c) Jan 2001  Carsten Borchardt
- * Copyright (c) 2001-2007 SoX contributors
+ * Copyright (c) 2001-2008 SoX contributors
  *
  * This source code is freely redistributable and may be used for any purpose.
  * This copyright notice must be maintained.  The authors are not responsible
@@ -145,12 +145,14 @@
 
 
 
+typedef enum {Linear, Square, Exp, Exp_cycle} sweep_t;
+
 typedef struct {
   /* options */
   type_t type;
   combine_t combine;
   double freq, freq2, mult;
-  sox_bool log_sweep;
+  sweep_t sweep;
   double offset, phase;
   double p1, p2, p3; /* Use depends on synth type */
 
@@ -280,13 +282,15 @@
     /* read frequencies if given */
     if (isdigit((int) argv[argn][0]) ||
         argv[argn][0] == '.' || argv[argn][0] == '%') {
+      static const char sweeps[] = ":+/-";
+
       chan->freq2 = chan->freq = lsx_parse_frequency(argv[argn], &end_ptr);
       if (chan->freq < 0) {
         sox_fail("invalid freq");
         return SOX_EOF;
       }
-      if (*end_ptr == '-' || *end_ptr == '~') {        /* freq2 given? */
-        chan->log_sweep = *end_ptr == '-';
+      if (*end_ptr && strchr(sweeps, *end_ptr)) {         /* freq2 given? */
+        chan->sweep = strchr(sweeps, *end_ptr) - sweeps;
         chan->freq2 = lsx_parse_frequency(end_ptr + 1, &end_ptr);
         if (chan->freq2 < 0) {
           sox_fail("invalid freq2");
@@ -301,8 +305,8 @@
         sox_fail("frequency: invalid trailing character");
         return SOX_EOF;
       }
-      if (chan->log_sweep && chan->freq * chan->freq2 == 0) {
-        sox_fail("invalid frequency for logarithmic sweep");
+      if (chan->sweep >= Exp && chan->freq * chan->freq2 == 0) {
+        sox_fail("invalid frequency for exponential sweep");
         return SOX_EOF;
       }
 
@@ -366,9 +370,23 @@
     channel_t chan = &synth->channels[i];
     *chan = synth->getopts_channels[i % synth->getopts_nchannels];
     set_default_parameters(chan, i);
-    chan->mult = chan->log_sweep ?
-        synth->samples_to_do? (log(chan->freq2) - log(chan->freq)) / synth->samples_to_do : 1 :
-        synth->samples_to_do? (chan->freq2 - chan->freq)  / synth->samples_to_do / 2 : 0;
+    switch (chan->sweep) {
+      case Linear: chan->mult = synth->samples_to_do?
+          (chan->freq2 - chan->freq) / synth->samples_to_do / 2 : 0;
+        break;
+      case Square: chan->mult = synth->samples_to_do?
+           sqrt(fabs(chan->freq2 - chan->freq)) / synth->samples_to_do / sqrt(3.) : 0;
+        if (chan->freq > chan->freq2)
+          chan->mult = -chan->mult;
+        break;
+      case Exp: chan->mult = synth->samples_to_do?
+          log(chan->freq2 / chan->freq) / synth->samples_to_do * effp->in_signal.rate : 1;
+        chan->freq /= chan->mult;
+        break;
+      case Exp_cycle: chan->mult = synth->samples_to_do?
+          (log(chan->freq2) - log(chan->freq)) / synth->samples_to_do : 1;
+        break;
+    }
     sox_debug("type=%s, combine=%s, samples_to_do=%u, f1=%g, f2=%g, "
               "offset=%g, phase=%g, p1=%g, p2=%g, p3=%g mult=%g",
         find_enum_value(chan->type, synth_type)->text,
@@ -381,158 +399,162 @@
 
 
 
-static sox_sample_t do_synth(sox_sample_t synth_input, priv_t * synth, unsigned c, double elapsed_time_s)
+#define sign(d) ((d) < 0? -1. : 1.)
+#define elapsed_time_s synth->samples_done / effp->in_signal.rate
+
+static int flow(sox_effect_t * effp, const sox_sample_t * ibuf, sox_sample_t * obuf,
+    sox_size_t * isamp, sox_size_t * osamp)
 {
-  channel_t chan = &synth->channels[c];
-  double synth_out;              /* [-1, 1] */
+  priv_t * synth = (priv_t *) effp->priv;
+  unsigned len = min(*isamp, *osamp) / effp->in_signal.channels;
+  unsigned c, done;
+  int result = SOX_SUCCESS;
 
-  if (chan->type < synth_noise) { /* Need to calculate phase: */
-    double phase;            /* [0, 1) */
+  for (done = 0; done < len && result == SOX_SUCCESS; ++done) {
+    for (c = 0; c < effp->in_signal.channels; c++) {
+      sox_sample_t synth_input = *ibuf++;
+      channel_t chan = &synth->channels[c];
+      double synth_out;              /* [-1, 1] */
 
-    if (!chan->log_sweep) {
-      double f = chan->freq + synth->samples_done * chan->mult;
-      phase = f * elapsed_time_s;
-    }
-    else {
-      double f = chan->freq * exp(synth->samples_done * chan->mult);
-      double cycle_elapsed_time_s = elapsed_time_s - chan->cycle_start_time_s;
-      if (f * cycle_elapsed_time_s >= 1) {  /* move to next cycle */
-        chan->cycle_start_time_s += 1 / f;
-        cycle_elapsed_time_s = elapsed_time_s - chan->cycle_start_time_s;
-      }
-      phase = f * cycle_elapsed_time_s;
-    }
-    phase = fmod(phase + chan->phase, 1.0);
+      if (chan->type < synth_noise) { /* Need to calculate phase: */
+        double phase;            /* [0, 1) */
+        switch (chan->sweep) {
+          case Linear:
+            phase = (chan->freq + synth->samples_done * chan->mult) *
+                elapsed_time_s;
+            break;
+          case Square:
+            phase = (chan->freq + sign(chan->mult) * 
+                sqr(synth->samples_done * chan->mult)) * elapsed_time_s;
+            break;
+          case Exp:
+            phase = chan->freq * exp(chan->mult * elapsed_time_s);
+            break;
+          case Exp_cycle: {
+            double f = chan->freq * exp(synth->samples_done * chan->mult);
+            double cycle_elapsed_time_s = elapsed_time_s - chan->cycle_start_time_s;
+            if (f * cycle_elapsed_time_s >= 1) {  /* move to next cycle */
+              chan->cycle_start_time_s += 1 / f;
+              cycle_elapsed_time_s = elapsed_time_s - chan->cycle_start_time_s;
+            }
+            phase = f * cycle_elapsed_time_s;
+            break;
+          }
+        }
+        phase = fmod(phase + chan->phase, 1.0);
 
-    switch (chan->type) {
-      case synth_sine:
-        synth_out = sin(2 * M_PI * phase);
-        break;
+        switch (chan->type) {
+          case synth_sine:
+            synth_out = sin(2 * M_PI * phase);
+            break;
 
-      case synth_square:
-        /* |_______           | +1
-         * |       |          |
-         * |_______|__________|  0
-         * |       |          |
-         * |       |__________| -1
-         * |                  |
-         * 0       p1          1
-         */
-        synth_out = -1 + 2 * (phase < chan->p1);
-        break;
+          case synth_square:
+            /* |_______           | +1
+             * |       |          |
+             * |_______|__________|  0
+             * |       |          |
+             * |       |__________| -1
+             * |                  |
+             * 0       p1          1
+             */
+            synth_out = -1 + 2 * (phase < chan->p1);
+            break;
 
-      case synth_sawtooth:
-        /* |           __| +1
-         * |        __/  |
-         * |_______/_____|  0
-         * |  __/        |
-         * |_/           | -1
-         * |             |
-         * 0             1
-         */
-        synth_out = -1 + 2 * phase;
-        break;
+          case synth_sawtooth:
+            /* |           __| +1
+             * |        __/  |
+             * |_______/_____|  0
+             * |  __/        |
+             * |_/           | -1
+             * |             |
+             * 0             1
+             */
+            synth_out = -1 + 2 * phase;
+            break;
 
-      case synth_triangle:
-        /* |    .    | +1
-         * |   / \   |
-         * |__/___\__|  0
-         * | /     \ |
-         * |/       \| -1
-         * |         |
-         * 0   p1    1
-         */
+          case synth_triangle:
+            /* |    .    | +1
+             * |   / \   |
+             * |__/___\__|  0
+             * | /     \ |
+             * |/       \| -1
+             * |         |
+             * 0   p1    1
+             */
 
-        if (phase < chan->p1)
-          synth_out = -1 + 2 * phase / chan->p1;          /* In rising part of period */
-        else
-          synth_out = 1 - 2 * (phase - chan->p1) / (1 - chan->p1); /* In falling part */
-        break;
+            if (phase < chan->p1)
+              synth_out = -1 + 2 * phase / chan->p1;          /* In rising part of period */
+            else
+              synth_out = 1 - 2 * (phase - chan->p1) / (1 - chan->p1); /* In falling part */
+            break;
 
-      case synth_trapezium:
-        /* |    ______             |+1
-         * |   /      \            |
-         * |__/________\___________| 0
-         * | /          \          |
-         * |/            \_________|-1
-         * |                       |
-         * 0   p1    p2   p3       1
-         */
-        if (phase < chan->p1)       /* In rising part of period */
-          synth_out = -1 + 2 * phase / chan->p1;
-        else if (phase < chan->p2)  /* In high part of period */
-          synth_out = 1;
-        else if (phase < chan->p3)  /* In falling part */
-          synth_out = 1 - 2 * (phase - chan->p2) / (chan->p3 - chan->p2);
-        else                        /* In low part of period */
-          synth_out = -1;
-        break;
+          case synth_trapezium:
+            /* |    ______             |+1
+             * |   /      \            |
+             * |__/________\___________| 0
+             * | /          \          |
+             * |/            \_________|-1
+             * |                       |
+             * 0   p1    p2   p3       1
+             */
+            if (phase < chan->p1)       /* In rising part of period */
+              synth_out = -1 + 2 * phase / chan->p1;
+            else if (phase < chan->p2)  /* In high part of period */
+              synth_out = 1;
+            else if (phase < chan->p3)  /* In falling part */
+              synth_out = 1 - 2 * (phase - chan->p2) / (chan->p3 - chan->p2);
+            else                        /* In low part of period */
+              synth_out = -1;
+            break;
 
-      case synth_exp:
-        /* |             |              | +1
-         * |            | |             |
-         * |          _|   |_           | 0
-         * |       __-       -__        |
-         * |____---             ---____ | f(p2)
-         * |                            |
-         * 0             p1             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
-          synth_out = synth_out * exp((1 - phase) * log(1 / synth_out) / (1 - chan->p1));
-        synth_out = synth_out * 2 - 1;      /* map 0 .. 1 to -1 .. +1 */
-        break;
+          case synth_exp:
+            /* |             |              | +1
+             * |            | |             |
+             * |          _|   |_           | 0
+             * |       __-       -__        |
+             * |____---             ---____ | f(p2)
+             * |                            |
+             * 0             p1             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
+              synth_out = synth_out * exp((1 - phase) * log(1 / synth_out) / (1 - chan->p1));
+            synth_out = synth_out * 2 - 1;      /* map 0 .. 1 to -1 .. +1 */
+            break;
 
-      default: synth_out = 0;
-    }
-  } else switch (chan->type) {
+          default: synth_out = 0;
+        }
+      } else switch (chan->type) {
 #define RAND (2. * rand() * (1. / RAND_MAX) - 1)
-    case synth_whitenoise:
-      synth_out = RAND;
-      break;
+        case synth_whitenoise:
+          synth_out = RAND;
+          break;
 
-    case synth_pinknoise:
-      synth_out = GeneratePinkNoise(&(chan->pink_noise));
-      break;
+        case synth_pinknoise:
+          synth_out = GeneratePinkNoise(&(chan->pink_noise));
+          break;
 
-    case synth_brownnoise:
-      do synth_out = chan->brown_noise + RAND * (1. / 16);
-      while (fabs(synth_out) > 1);
-      chan->brown_noise = synth_out;
-      break;
+        case synth_brownnoise:
+          do synth_out = chan->brown_noise + RAND * (1. / 16);
+          while (fabs(synth_out) > 1);
+          chan->brown_noise = synth_out;
+          break;
 
-    default: synth_out = 0;
-  }
+        default: synth_out = 0;
+      }
 
-  /* Add offset, but prevent clipping: */
-  synth_out = synth_out * (1 - fabs(chan->offset)) + chan->offset;
+      /* Add offset, but prevent clipping: */
+      synth_out = synth_out * (1 - fabs(chan->offset)) + chan->offset;
 
-  switch (chan->combine) {
-    case synth_create: return  synth_out * synth->max;
-    case synth_mix   : return (synth_out * synth->max + synth_input) * 0.5;
-    case synth_amod  : return (synth_out + 1) * synth_input * 0.5;
-    case synth_fmod  : return  synth_out * synth_input;
-  }
-  return 0;
-}
-
-
-
-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 * synth = (priv_t *) effp->priv;
-  unsigned len = min(*isamp, *osamp) / effp->in_signal.channels;
-  unsigned c, done;
-  double sample_period = 1 / effp->in_signal.rate;
-  int result = SOX_SUCCESS;
-
-  for (done = 0; done < len && result == SOX_SUCCESS; ++done) {
-    double elapsed_time_s = synth->samples_done * sample_period;
-    for (c = 0; c < effp->in_signal.channels; c++)
-      *obuf++ = do_synth(*ibuf++, synth, c, elapsed_time_s);
+      switch (chan->combine) {
+        case synth_create: *obuf++ =  synth_out * synth->max; break;
+        case synth_mix   : *obuf++ = (synth_out * synth->max + synth_input) * 0.5; break;
+        case synth_amod  : *obuf++ = (synth_out + 1) * synth_input * 0.5; break;
+        case synth_fmod  : *obuf++ =  synth_out * synth_input; break;
+      }
+    }
     if (++synth->samples_done == synth->samples_to_do)
       result = SOX_EOF;
   }
@@ -564,7 +586,7 @@
 const sox_effect_handler_t *sox_synth_effect_fn(void)
 {
   static sox_effect_handler_t handler = {
-    "synth", "[len] {type [combine] [freq[k][-freq2[k]|~freq2[k]] [off [ph [p1 [p2 [p3]]]]]]}",
+    "synth", "[len] {type [combine] [[%]freq[k][:|+|/|-[%]freq2[k]] [off [ph [p1 [p2 [p3]]]]]]}",
     SOX_EFF_MCHAN | SOX_EFF_PREC |SOX_EFF_LENGTH,
     getopts, start, flow, 0, stop, kill, sizeof(priv_t)
   };