shithub: sox

Download patch

ref: 24d144e4f7a11ff2b2359eadcd3e6ae96bfb9733
parent: 3a5f9247329b4001bf88128d7c047da7ab916ffa
author: robs <robs>
date: Sat Jul 19 16:33:42 EDT 2008

Allow k suffix with rate effect.
Allow effect to specify output rate.
Documentation:
        Fix rate rej. specs.
        --input-buffer
Add some comments to main process() function; break it up a bit.
Fix muddled rate and # channels when playing mixed rate/channels files.

--- a/ChangeLog
+++ b/ChangeLog
@@ -98,6 +98,7 @@
   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 --input-buffer option to specify (only) input buffer size.  (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
@@ -552,8 +552,19 @@
 option is strongly recommended; a `shell' alias, script, or batch file
 may be an appropriate way of permanently enabling it.
 .TP
-\fB\-\-buffer\fR \fBBYTES\fR
-Set the size in bytes of the buffers used for reading and writing sound data (default 8192).
+\fB\-\-buffer\fR \fBBYTES\fR, \fB\-\-input\-buffer\fR \fBBYTES\fR
+Set the size in bytes of the buffers used for processing audio (default 8192).
+.B \-\-buffer
+applies to input, effects, and output processing;
+.B \-\-input\-buffer
+applies only to input processing (for which it overrides
+.B \-\-buffer
+if both are given).
+.SP
+Be aware that large values for
+.B \-\-buffer
+will cause SoX to be become slow to respond to requests to terminate or to skip
+the current input file.
 .TP
 \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;
--- a/soxeffect.7
+++ b/soxeffect.7
@@ -731,7 +731,7 @@
 (\fB\-t\fR).  The decay should be less than 0\*d5 to avoid
 feedback.  Gain-out is the volume of the output.
 .TP
-\fBrate\fR [\fB\-q\fR\^|\^\fB\-l\fR\^|\^\fB\-m\fR\^|\^\fB\-h\fR\^|\^\fB\-v\fR]
+\fBrate\fR [\fB\-q\fR\^|\^\fB\-l\fR\^|\^\fB\-m\fR\^|\^\fB\-h\fR\^|\^\fB\-v\fR] [\fIRATE\fR[\fBk\fR]]
 Change the audio sampling rate (i.e. resample the audio)
 using a quality level as follows:
 .TS
@@ -750,11 +750,11 @@
 playback on old hardware
 T}
 \-m	medium	99	100	audio playback
-\-h	high	99	120	T{
+\-h	high	99	125	T{
 .na
 16-bit mastering (use with dither)
 T}
-\-v	very high	99	150	24-bit mastering\ 
+\-v	very high	99	175	24-bit mastering\ 
 .TE
 .DT
 .SP
@@ -765,6 +765,22 @@
 .B Rej dB
 is the level of noise rejection.
 The default quality level is `high' (\fB\-h\fR).
+.SP
+This effect is invoked automatically if SoX's \fB\-r\fR option specifies a
+rate that is different to that of the input file(s).  Alternatively, this
+effect may be invoked with the output rate parameter
+.I RATE
+and SoX's
+.B \-r
+option need not be given.  For example, the following two commands are
+equivalent:
+.EX
+	sox input.au -r 48k output.au bass -3
+	sox input.au output.au bass -3 rate 48k
+.EE
+though the second command is more flexible as it allows a
+.B rate
+quality option to be given, and it allows the effects to be ordered arbitrarily.
 .SP
 See also
 .BR resample ,
--- a/src/rate.c
+++ b/src/rate.c
@@ -489,7 +489,7 @@
 static int create(sox_effect_t * effp, int argc, char **argv)
 {
   priv_t * p = (priv_t *) effp->priv;
-  char dummy, * found_at, * bws = "-qlmhvu", * nums = "123";
+  char * dummy_p, * found_at, * bws = "-qlmhvu", * nums = "123";
 
   p->quality = p->coef_interp = -1;
   p->shared_ptr = &p->shared;
@@ -503,9 +503,10 @@
     argc--; argv++;
   }
   if (argc) {
-    if (sscanf(*argv, "%lf %c", &p->out_rate, &dummy) != 1 || p->out_rate <= 0)
+    if ((p->out_rate = lsx_parse_frequency(*argv, &dummy_p)) <= 0 || *dummy_p)
       return lsx_usage(effp);
     argc--; argv++;
+    effp->out_signal.rate = p->out_rate;
   }
   return argc? lsx_usage(effp) : SOX_SUCCESS;
 }
@@ -562,13 +563,14 @@
 sox_effect_handler_t const * sox_rate_effect_fn(void)
 {
   static sox_effect_handler_t handler = {
-    "rate", "[-q|-l|-m|-h|-v]\n"
-    "     Quality        BW %    Rej dB    Typical Use\n"
-    " -q  quick & dirty  n/a   ~30 @ Fs/4  playback on ancient hardware\n"
-    " -l  low            80       100      playback on old hardware\n"
-    " -m  medium         99       100      audio playback\n"
-    " -h  high           99       120      16-bit mastering (use with dither)\n"
-    " -v  very high      99       150      24-bit mastering"
+    "rate", "[-q|-l|-m|-h|-v] [RATE[k]]"
+    "\n\tQuality        BW %    Rej dB    Typical Use"
+    "\n -q\tquick & dirty  n/a   ~30 @ Fs/4  playback on ancient hardware"
+    "\n -l\tlow            80       100      playback on old hardware"
+    "\n -m\tmedium         99       100      audio playback"
+    "\n -h\thigh           99       125      16-bit mastering (use with dither)"
+    "\n -v\tvery high      99       175      24-bit mastering"
+    /* "\n -u\tultra high     99.7     175      " */
     , SOX_EFF_RATE, create, start, flow, drain, stop, NULL, sizeof(priv_t)
   };
   return &handler;
--- a/src/sox.c
+++ b/src/sox.c
@@ -115,8 +115,8 @@
 /* Effects */
 
 /* We parse effects into a temporary effects table and then place into
- * the real effects table.  This makes it easier to reorder some effects
- * as needed.  For instance, we can run a resampling effect before
+ * the real effects table.  This makes it easier to auto-add some effects
+ * as appropriate.  For instance, we can run a resampling effect before
  * converting a mono file to stereo.  This allows the resampling to work
  * on half the data.
  *
@@ -125,7 +125,7 @@
  * a resampling effect, a channel mixing effect, the input, and the output.
  */
 #define MAX_USER_EFF (SOX_MAX_EFFECTS - 4)
-static sox_effect_t * user_efftab[MAX_USER_EFF];
+static sox_effect_t * user_efftab[MAX_USER_EFF], efftab_options[MAX_USER_EFF];
 static unsigned nuser_effects;
 static sox_effects_chain_t ofile_effects_chain;
 
@@ -693,25 +693,21 @@
 
 static void optimize_trim(void)
 {
-  /* Speed hack.  If the "trim" effect is the first effect then
-   * peek inside its "effect descriptor" and see what the
-   * start location is.  This has to be done after its start()
-   * is called to have the correct location.
-   * Also, only do this when only working with one input file.
-   * This is because the logic to do it for multiple files is
-   * complex and problably never used.
-   * This hack is a huge time savings when trimming
-   * gigs of audio data into managable chunks
-   */
+  /* Speed hack.  If the "trim" effect is the first effect then peek inside its
+   * "effect descriptor" and see what the start location is.  This has to be
+   * done after its start() is called to have the correct location.  Also, only
+   * do this when only working with one input file.  This is because the logic
+   * to do it for multiple files is complex and problably never used.  This
+   * hack is a huge time savings when trimming gigs of audio data into
+   * managable chunks.  */
   if (input_count == 1 && ofile_effects_chain.length > 1 && strcmp(ofile_effects_chain.effects[1][0].handler.name, "trim") == 0) {
     if (files[0]->ft->handler.seek && files[0]->ft->seekable){
       sox_size_t offset = sox_trim_get_start(&ofile_effects_chain.effects[1][0]);
       if (offset && sox_seek(files[0]->ft, offset, SOX_SEEK_SET) == SOX_SUCCESS) {
         read_wide_samples = offset / files[0]->ft->signal.channels;
-        /* Assuming a failed seek stayed where it was.  If the
-         * seek worked then reset the start location of
-         * trim so that it thinks user didn't request a skip.
-         */
+        /* Assuming a failed seek stayed where it was.  If the seek worked then
+         * reset the start location of trim so that it thinks user didn't
+         * request a skip.  */
         sox_trim_clear_start(&ofile_effects_chain.effects[1][0]);
         sox_debug("optimize_trim successful");
       }
@@ -754,12 +750,9 @@
       sox_append_comment(&oob.comments, *p++);
   }
 
-  /*
-   * copy loop info, resizing appropriately
-   * it's in samples, so # channels don't matter
-   * FIXME: This doesn't work for multi-file processing or
-   * effects that change file length.
-   */
+  /* Copy loop info, resizing appropriately it's in samples, so # channels
+   * don't matter FIXME: This doesn't work for multi-file processing or effects
+   * that change file length.  */
   factor = (double) ofile->signal.rate / combiner_signal.rate;
   for (i = 0; i < SOX_MAX_NLOOPS; i++) {
     oob.loops[i].start = oob.loops[i].start * factor;
@@ -775,9 +768,9 @@
      * Rely on that printing something. */
     exit(2);
 
-  /* When writing to an audio device, auto turn on the
-   * progress display to match behavior of ogg123,
-   * unless the user requested us not to display anything. */
+  /* If whether to enable the progress display (similar to that of ogg123) has
+   * not been specified by the user, auto turn on when outputting to an audio
+   * device: */
   if (show_progress == SOX_OPTION_DEFAULT)
     show_progress = (ofile->ft->handler.flags & SOX_FILE_DEVICE) != 0 &&
                     (ofile->ft->handler.flags & SOX_FILE_PHONY) == 0;
@@ -794,19 +787,21 @@
   else user_abort = sox_true;
 }
 
-/*
- * Process:   Input(s) -> Balancing -> Combiner -> Effects -> Output
- */
-
-static int process(void) {
-  int flowstatus = 0;
-  sox_size_t i;
+static void calculate_combiner_and_output_signal_parameters(void)
+{
   sox_bool known_length = combine_method != sox_sequence;
-  sox_size_t olen = 0;
+  sox_size_t i, olen = 0;
 
+  /* Set the combiner output signal attributes to those of the 1st/next input
+   * file.  If we are in sox_sequence mode then we don't need to check the
+   * attributes of the other inputs, otherwise, it is mandatory that all input
+   * files have the same sample rate, and for sox_concatenate, it is mandatory
+   * that they have the same number of channels, otherwise, the number of
+   * channels at the output of the combiner is calculated according to the
+   * combiner mode. */
   combiner_signal = files[current_input]->ft->signal;
-  combiner_encoding = files[current_input]->ft->encoding;
   if (combine_method == sox_sequence) {
+    /* Report all input files; do this only the 1st time process() is called: */
     if (!current_input) for (i = 0; i < input_count; i++)
       report_file_info(files[i]);
   } else {
@@ -816,13 +811,15 @@
     sox_size_t min_rate = SOX_SIZE_MAX;
     sox_size_t max_rate = 0;
 
-    for (i = 0; i < input_count; i++) { /* Report all inputs, then check */
+    /* Report all input files and gather info on differing rates & numbers of
+     * channels, and on the resulting output audio length: */
+    for (i = 0; i < input_count; i++) {
       report_file_info(files[i]);
       total_channels += files[i]->ft->signal.channels;
       min_channels = min(min_channels, files[i]->ft->signal.channels);
       max_channels = max(max_channels, files[i]->ft->signal.channels);
-      min_rate = min(min_rate, files[i]->ft->signal.rate);
-      max_rate = max(max_rate, files[i]->ft->signal.rate);
+      min_rate     = min(min_rate    , files[i]->ft->signal.rate);
+      max_rate     = max(max_rate    , files[i]->ft->signal.rate);
       known_length = known_length && files[i]->ft->signal.length != 0;
       if (combine_method == sox_concatenate)
         olen += files[i]->ft->signal.length / files[i]->ft->signal.channels;
@@ -829,42 +826,74 @@
       else
         olen = max(olen, files[i]->ft->signal.length / files[i]->ft->signal.channels);
     }
+
+    /* Check for invalid/unusual rate or channel combinations: */
     if (min_rate != max_rate)
       sox_fail("Input files must have the same sample-rate");
+      /* Don't exit quite yet; give the user any other message 1st */
     if (min_channels != max_channels) {
       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_mix_power ||
-          combine_method == sox_multiply)
+      } else if (combine_method != sox_merge)
         sox_warn("Input files don't have the same # channels");
     }
     if (min_rate != max_rate)
       exit(1);
 
+    /* Store the calculated # of combined channels: */
     combiner_signal.channels =
       combine_method == sox_merge? total_channels : max_channels;
   }
 
+  /* Determine the output file signal attributes; set from user options
+   * if given: */
   ofile->signal = ofile_signal_options;
-  if (ofile->signal.rate == 0)
+
+  /* If no user option for output rate or # of channels, set from the last
+   * effect that sets these, or from the input combiner if there is none such */
+  for (i = 0; i < nuser_effects && !ofile->signal.rate; ++i)
+    ofile->signal.rate = user_efftab[nuser_effects - 1 - i]->out_signal.rate;
+  for (i = 0; i < nuser_effects && !ofile->signal.channels; ++i)
+    ofile->signal.channels = user_efftab[nuser_effects - 1 - i]->out_signal.channels;
+  if (!ofile->signal.rate)
     ofile->signal.rate = combiner_signal.rate;
-  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]->out_signal.channels;
-    if (ofile->signal.channels == 0)
-      ofile->signal.channels = combiner_signal.channels;
-  }
+  if (!ofile->signal.channels)
+    ofile->signal.channels = combiner_signal.channels;
+
+  /* FIXME: comment this: */
   ofile->signal.precision = combiner_signal.precision;
 
+  /* Now take account of any net speed change specified by user effects by
+   * adjusting the nominal sample rate at the output of the combiner: */
   combiner_signal.rate *= sox_effects_globals.speed;
 
+  /* If any given user effect modifies the audio length, then we assume that
+   * we don't know what the output length will be.  FIXME: in most cases,
+   * an effect that modifies length will be able to determine by how much from
+   * its getopts parameters, so olen should be calculable. */
+  for (i = 0; i < nuser_effects; i++)
+    known_length = known_length && !(user_efftab[i]->handler.flags & SOX_EFF_LENGTH);
+
+  if (!known_length)
+    olen = 0;
+  ofile->signal.length = (sox_size_t)(olen * ofile->signal.channels * ofile->signal.rate / combiner_signal.rate + .5);
+}
+
+static void set_combiner_and_output_encoding_parameters(void)
+{
+  /* The input encoding parameters passed to the effects chain are those of
+   * the first input file (for each segued block if sox_sequence):*/
+  combiner_encoding = files[current_input]->ft->encoding;
+
+  /* Determine the output file encoding attributes; set from user options
+   * if given: */
   ofile->encoding = ofile_encoding_options;
  
-  /* Get unspecified output file encoding attributes from the input file
-   * and set the output file to the resultant encoding if this is
-   * supported by the output file type.  */
+  /* Get unspecified output file encoding attributes from the input file and
+   * set the output file to the resultant encoding if this is supported by the
+   * output file type; if not, the output file handler should select an
+   * encoding suitable for the output signal and its precision. */
   {
     sox_encodinginfo_t t = ofile->encoding;
     if (!t.encoding)
@@ -874,17 +903,23 @@
     if (sox_format_supports_encoding(ofile->filename, ofile->filetype, &t))
       ofile->encoding = t;
   }
+}
 
-  for (i = 0; i < nuser_effects; i++)
-    known_length = known_length && !(user_efftab[i]->handler.flags & SOX_EFF_LENGTH);
+static int process(void)
+{         /* Input(s) -> Balancing -> Combiner -> Effects -> Output */
+  int i;
 
-  if (!known_length)
-    olen = 0;
-  ofile->signal.length = (sox_size_t)(olen * ofile->signal.channels * ofile->signal.rate / combiner_signal.rate + .5);
+  for (i = 0; i < nuser_effects; ++i)
+    *user_efftab[i] = efftab_options[i];
+
+  calculate_combiner_and_output_signal_parameters();
+  set_combiner_and_output_encoding_parameters();
   open_output_file();
 
-  ofile_effects_chain.length = 0; /* FIXME: needs proper cleanup of effects
-                                     chain memory. `kill' auto effects? */
+  /* FIXME: For sox_sequence, this needs proper cleanup of effects-chain memory
+   * (including `kill' of auto effects) */
+  ofile_effects_chain.length = 0;
+
   ofile_effects_chain.global_info = sox_effects_globals;
   ofile_effects_chain.in_enc = &combiner_encoding;
   ofile_effects_chain.out_enc = &ofile->ft->encoding;
@@ -892,13 +927,9 @@
 
   optimize_trim();
 
-  signal(SIGINT, sigint);
-  /* FIXME: For SIGTERM at least we really should guarantee to stop quickly */
-  signal(SIGTERM, sigint); /* Stop gracefully even in extremis */
-
-  flowstatus = sox_flow_effects(&ofile_effects_chain, update_status);
-
-  return flowstatus;
+  signal(SIGTERM, sigint); /* Stop gracefully, as soon as we possibly can. */
+  signal(SIGINT , sigint); /* Either skip current input or behave as SIGTERM. */
+  return sox_flow_effects(&ofile_effects_chain, update_status);
 }
 
 static void display_SoX_version(FILE * file)
@@ -1768,8 +1799,13 @@
     srand((unsigned)t);
   }
 
+  /* Save things that sox_sequence needs to be reinitialised for each segued
+   * block of input files: */
   ofile_signal_options = ofile->signal;
   ofile_encoding_options = ofile->encoding;
+  for (i = 0; i < nuser_effects; ++i)
+    efftab_options[i] = *user_efftab[i];
+
   if (combine_method == sox_sequence) do {
     if (ofile->ft)
       sox_close(ofile->ft);