ref: 8481bc76a8815b5fd65aca604805254cd7ad3696
parent: 887eacddad2e66f395d979d5b8f03288bbc23b85
author: cbagwell <cbagwell>
date: Sun Sep 21 20:32:03 EDT 2008
Support a list of effects; one for each output file created. Minor refactoring of effects flow.
--- a/sox.1
+++ b/sox.1
@@ -467,9 +467,9 @@
certain cases. If the output method 'multiple' is specified
then SoX will create a new file each time the specified effects
terminate the output early. Each time a new file is created, the effects
-chain is restarted. In case multiple effects are in the chain, the
-chain is only restart from the effect that terminated so that no
-input samples are lost.
+chain is restarted. It is important to place the effect that
+stops processing at the front of the chain because all samples
+in the pipeline before it will be discarded.
There are only a few effects that can stop processing input early such
as the trim and silence effects so the application for multiple files
--- a/src/effects.c
+++ b/src/effects.c
@@ -414,6 +414,16 @@
}
+void sox_delete_effect_last(sox_effects_chain_t *chain)
+{
+ if (chain->length > 0)
+ {
+ chain->length--;
+ sox_delete_effect(chain->effects[chain->length]);
+ chain->effects[chain->length] = NULL;
+ }
+} /* sox_delete_effect_last */
+
/* Remove all effects from the chain.
* Note: This currently closes down the effect which might
* note be obvious from name.
--- a/src/sox.c
+++ b/src/sox.c
@@ -120,22 +120,25 @@
/* Effects */
/* We parse effects into a temporary effects table and then place into
- * 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.
+ * the real effects chain. This allows scanning all effects to give
+ * hints to what input effect options should be as well as determining
+ * when mixer or resample effects need to be auto-inserted as well.
*
* User effects table must be 4 entries smaller then the real
* effects table. This is because at most we will need to add
- * a resampling effect, a channel mixing effect, the input, and the output.
+ * the input effect, an optional resample effect, an optional mixer
+ * effect, and the output effect.
*/
#define MAX_USER_EFF (SOX_MAX_EFFECTS - 4)
-static struct { char *name; int argc; char *argv[256]; } user_effargs[MAX_USER_EFF];
static sox_effect_t *user_efftab[MAX_USER_EFF];
-static unsigned nuser_effects;
static sox_effects_chain_t *effects_chain = NULL;
+
+#define MAX_USER_EFF_LOOPS 256
+static struct { char *name; int argc; char *argv[FILENAME_MAX]; } user_effargs[MAX_USER_EFF_LOOPS][MAX_USER_EFF];
+static unsigned nuser_effects[MAX_USER_EFF_LOOPS];
+static int current_eff_loop = 0;
+static int eff_loop_count = 0;
static char *effects_filename = NULL;
-static void parse_effects(int argc, char **argv);
/* Flowing */
@@ -429,20 +432,22 @@
size_t ilen[MAX_INPUT_FILES];
size_t olen = 0;
- if (is_serial(combine_method)) while (sox_true) {
- if (!user_skip)
- olen = sox_read_wide(files[current_input]->ft, obuf, *osamp);
- if (olen == 0) { /* If EOF, go to the next input file. */
- if (++current_input < input_count) {
- if (combine_method == sox_sequence && !can_segue(current_input))
- break;
- progress_to_next_input_file(files[current_input]);
- continue;
+ if (is_serial(combine_method)) {
+ while (sox_true) {
+ if (!user_skip)
+ olen = sox_read_wide(files[current_input]->ft, obuf, *osamp);
+ if (olen == 0) { /* If EOF, go to the next input file. */
+ if (++current_input < input_count) {
+ if (combine_method == sox_sequence && !can_segue(current_input))
+ break;
+ progress_to_next_input_file(files[current_input]);
+ continue;
+ }
}
- }
- balance_input(obuf, olen, files[current_input]);
- break;
- } else {
+ balance_input(obuf, olen, files[current_input]);
+ break;
+ } /* while */
+ } /* is_serial */ else { /* else is_parallel() */
sox_sample_t * p = obuf;
for (i = 0; i < input_count; ++i) {
ilen[i] = sox_read_wide(files[i]->ft, z->ibuf[i], *osamp);
@@ -449,7 +454,7 @@
balance_input(z->ibuf[i], ilen[i], files[i]);
olen = max(olen, ilen[i]);
}
- for (ws = 0; ws < olen; ++ws) /* wide samples */
+ for (ws = 0; ws < olen; ++ws) { /* wide samples */
if (combine_method == sox_mix || combine_method == sox_mix_power) {
for (s = 0; s < effp->in_signal.channels; ++s, ++p) { /* sum samples */
*p = 0;
@@ -458,10 +463,10 @@
/* Cast to double prevents integer overflow */
double sample = *p + (double)z->ibuf[i][ws * files[i]->ft->signal.channels + s];
*p = SOX_ROUND_CLIP_COUNT(sample, mixing_clips);
- }
+ }
}
- } else if (combine_method == sox_multiply) { /* multiply samples */
- for (s = 0; s < effp->in_signal.channels; ++s, ++p) {
+ } /* sox_mix */ else if (combine_method == sox_multiply) {
+ for (s = 0; s < effp->in_signal.channels; ++s, ++p) { /* multiple samples */
i = 0;
*p = ws < ilen[i] && s < files[i]->ft->signal.channels?
z->ibuf[i][ws * files[i]->ft->signal.channels + s] : 0;
@@ -470,12 +475,14 @@
*p = SOX_ROUND_CLIP_COUNT(sample, mixing_clips);
}
}
- } else { /* sox_merge: like a multi-track recorder */
+ } /* sox_multiply */ else { /* sox_merge: like a multi-track recorder */
for (i = 0; i < input_count; ++i)
for (s = 0; s < files[i]->ft->signal.channels; ++s)
*p++ = (ws < ilen[i]) * z->ibuf[i][ws * files[i]->ft->signal.channels + s];
- }
- }
+ } /* sox_merge */
+ } /* wide samples */
+ current_input += input_count;
+ } /* is_parallel */
read_wide_samples += olen;
olen *= effp->in_signal.channels;
*osamp = olen;
@@ -566,27 +573,57 @@
exit(2); /* The effects chain should have displayed an error message */
}
-static void delete_user_effects(void)
+static void delete_user_effargs(void)
{
- unsigned i;
- int j;
+ unsigned j;
+ int i, k;
- for (i = 0; i < nuser_effects; i++)
+ for (i = 0; i < eff_loop_count; i++)
{
- if (user_effargs[i].name)
- free(user_effargs[i].name);
- user_effargs[i].name = NULL;
- for (j = 0; j < user_effargs[i].argc; j++)
+ for (j = 0; j < nuser_effects[i]; j++)
{
- if (user_effargs[i].argv[j])
- free(user_effargs[i].argv[j]);
- user_effargs[i].argv[j] = NULL;
+ if (user_effargs[i][j].name)
+ free(user_effargs[i][j].name);
+ user_effargs[i][j].name = NULL;
+ for (k = 0; k < user_effargs[i][j].argc; k++)
+ {
+ if (user_effargs[i][j].argv[k])
+ free(user_effargs[i][j].argv[k]);
+ user_effargs[i][j].argv[k] = NULL;
+ }
+ user_effargs[i][j].argc = 0;
}
- user_effargs[i].argc = 0;
+ nuser_effects[i] = 0;
}
- nuser_effects = 0;
-} /* delete_user_effects */
+ eff_loop_count = 0;
+} /* delete_user_effargs */
+static void parse_effects(int argc, char **argv)
+{
+ unsigned i;
+
+ nuser_effects[eff_loop_count] = 0;
+ for (i = 0; optind < argc; i++) {
+ unsigned eff_offset;
+ int j;
+
+ eff_offset = nuser_effects[eff_loop_count];
+ if (eff_offset >= MAX_USER_EFF) {
+ sox_fail("too many effects specified (at most %i allowed)", MAX_USER_EFF);
+ exit(1);
+ }
+
+ /* Name should always be correct! */
+ user_effargs[eff_loop_count][eff_offset].name = strdup(argv[optind++]);
+ for (j = 0; j < argc - optind && !sox_find_effect(argv[optind + j]); ++j)
+ user_effargs[eff_loop_count][eff_offset].argv[j] = strdup(argv[optind + j]);
+ user_effargs[eff_loop_count][eff_offset].argc = j;
+
+ optind += j; /* Skip past the effect arguments */
+ nuser_effects[eff_loop_count]++;
+ }
+} /* parse_effects */
+
static int strtoargv(char *s, char *(*argv)[])
{
int argc = 0;
@@ -645,12 +682,13 @@
static void read_user_effects(char *filename)
{
FILE *file = fopen(filename, "rt");
- char s[1025];
+ char s[FILENAME_MAX];
int argc;
- char *argv[256];
+ char *argv[FILENAME_MAX];
int len;
- delete_user_effects();
+ delete_user_effargs();
+ current_eff_loop = 0;
if (file == NULL)
{
@@ -660,23 +698,29 @@
sox_report("Reading effects from file %s", filename);
- if (fgets(s, 1024, file) == NULL)
+ while (fgets(s, FILENAME_MAX, file))
{
- sox_fail("Error reading effects file %s", filename);
- exit(2);
- }
- len = strlen(s);
- if (s[len-1] == '\n')
+ len = strlen(s);
+ if (s[len-1] == '\n')
s[len-1] = 0;
- argc = strtoargv(s, &argv);
+ argc = strtoargv(s, &argv);
- /* parse_effects normally parses options from command line.
- * Reset opt index so it thinks its back at beginning of
- * main()'s argv[].
+ /* parse_effects normally parses options from command line.
+ * Reset opt index so it thinks its back at beginning of
+ * main()'s argv[].
+ */
+ optind = 0;
+ parse_effects(argc, argv);
+ eff_loop_count++;
+ }
+
+ /* More then 1 user effect in loop currently means multiple
+ * output mode or else it will simply keep overwriting the
+ * same file each loop.
*/
- optind = 0;
- parse_effects(argc, argv);
+ if (eff_loop_count > 1)
+ output_method = sox_multiple;
fclose(file);
@@ -698,20 +742,10 @@
unsigned i;
sox_effect_t *effp;
- /* If user specified an effects filename then use that file
- * to load user effects. Free any previously specified options
- * from the command line. This also means that effects will
- * be reloaded each time a new input or output file is opened.
- */
- if (effects_filename)
+ for (i = 0; i < nuser_effects[current_eff_loop]; i++)
{
- read_user_effects(effects_filename);
- }
+ effp = sox_create_effect(sox_find_effect(user_effargs[current_eff_loop][i].name));
- for (i = 0; i < nuser_effects; i++)
- {
- effp = sox_create_effect(sox_find_effect(user_effargs[i].name));
-
if (!effp)
sox_fail("Failed creating effect. Out of Memory?\n");
@@ -720,8 +754,8 @@
effp->handler.name);
/* The failing effect should have displayed an error message */
- if (sox_effect_options(effp, user_effargs[i].argc,
- user_effargs[i].argv) == SOX_EOF)
+ if (sox_effect_options(effp, user_effargs[current_eff_loop][i].argc,
+ user_effargs[current_eff_loop][i].argv) == SOX_EOF)
exit(1);
user_efftab[i] = effp;
@@ -755,7 +789,7 @@
}
/* Add auto effects if appropriate; add user specified effects */
- for (i = 0; i < nuser_effects; i++) {
+ for (i = 0; i < nuser_effects[current_eff_loop]; i++) {
effects_added++;
if (effects_added > chain->length)
{
@@ -942,7 +976,7 @@
fn = filename;
- efn = expand_fn = malloc((size_t)FILENAME_MAX);
+ efn = expand_fn = lsx_malloc((size_t)FILENAME_MAX);
/* Find extension in case user didn't specify a substitution
* marker.
@@ -1093,7 +1127,8 @@
*/
for (i = 0; i < input_count; i++) {
unsigned j;
- for (j =0; j < nuser_effects && !files[i]->ft->signal.channels; ++j)
+ for (j =0; j < nuser_effects[current_eff_loop] &&
+ !files[i]->ft->signal.channels; ++j)
files[i]->ft->signal.channels = user_efftab[j]->in_signal.channels;
/* For historical reasons, default to one channel if not specified. */
if (!files[i]->ft->signal.channels)
@@ -1176,10 +1211,10 @@
/* 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;
+ for (i = 0; i < nuser_effects[current_eff_loop] && !ofile->signal.rate; ++i)
+ ofile->signal.rate = user_efftab[nuser_effects[current_eff_loop] - 1 - i]->out_signal.rate;
+ for (i = 0; i < nuser_effects[current_eff_loop] && !ofile->signal.channels; ++i)
+ ofile->signal.channels = user_efftab[nuser_effects[current_eff_loop] - 1 - i]->out_signal.channels;
if (!ofile->signal.rate)
ofile->signal.rate = combiner_signal.rate;
if (!ofile->signal.channels)
@@ -1192,7 +1227,7 @@
* 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++)
+ for (i = 0; i < nuser_effects[current_eff_loop]; i++)
known_length = known_length && !(user_efftab[i]->handler.flags & SOX_EFF_LENGTH);
if (!known_length)
@@ -1932,26 +1967,6 @@
add_file(&opts, device_name(opts.filetype));
}
-static void parse_effects(int argc, char **argv)
-{
- for (nuser_effects = 0; optind < argc; ++nuser_effects) {
- int i;
-
- if (nuser_effects >= MAX_USER_EFF) {
- sox_fail("too many effects specified (at most %i allowed)", MAX_USER_EFF);
- exit(1);
- }
-
- /* Name should always be correct! */
- user_effargs[nuser_effects].name = strdup(argv[optind++]);
- for (i = 0; i < argc - optind && !sox_find_effect(argv[optind + i]); ++i)
- user_effargs[nuser_effects].argv[i] = strdup(argv[optind + i]);
- user_effargs[nuser_effects].argc = i;
-
- optind += i; /* Skip past the effect arguments */
- }
-}
-
typedef enum {
full, rate, channels, samples, duration, bits, encoding, annotation} soxi_t;
@@ -2145,9 +2160,11 @@
/* Loop through the rest of the arguments looking for effects */
parse_effects(argc, argv);
+ eff_loop_count++;
/* Not the best way for users to do this; now deprecated in favour of soxi. */
- if (!show_progress && !nuser_effects && ofile->filetype && !strcmp(ofile->filetype, "null")) {
+ if (!show_progress && !nuser_effects[current_eff_loop] &&
+ ofile->filetype && !strcmp(ofile->filetype, "null")) {
for (i = 0; i < input_count; i++)
report_file_info(files[i]);
exit(0);
@@ -2158,7 +2175,7 @@
else {
time_t t;
- time(&t);
+ time(&t);
srand((unsigned)t);
}
@@ -2167,42 +2184,42 @@
ofile_signal_options = ofile->signal;
ofile_encoding_options = ofile->encoding;
- if (combine_method == sox_sequence || output_method == sox_multiple) do {
- /* If ofile->ft is set then we've called this at least once.
- * Need to do some cleanup before continueing on.
- */
- if (ofile->ft)
+ /* If user specified an effects filename then use that file
+ * to load user effects. Free any previously specified options
+ * from the command line.
+ */
+ if (effects_filename)
+ {
+ read_user_effects(effects_filename);
+ }
+
+ while (process() != SOX_EOF && !user_abort && current_input < input_count)
+ {
+ if (input_eof)
+ sox_delete_effects(effects_chain);
+ else
{
- /* If in sox_multiple mode and something besides input/combiner
- * effect stopped the writing then only restart those effects
- * so that no input samples are lost.
- * If input reached EOF then restart all effects.
+ /* if input hasn't reached EOF, delete all effects except for
+ * input effect.
*/
- if (output_method == sox_multiple && !input_eof)
+ /* TODO: Warn user when an effect is deleted that still
+ * had unprocessed samples.
+ */
+ while (effects_chain->length > 1)
{
- size_t e;
-
- /* Scan backwards until data is found and do not free input effect */
- for (e = effects_chain->length-1; e > 0; --e)
- {
- if (effects_chain->effects[e]->obeg !=
- effects_chain->effects[e]->oend)
- break;
- sox_delete_effect(effects_chain->effects[e]);
- effects_chain->effects[e] = NULL;
- effects_chain->length--;
- }
+ sox_delete_effect_last(effects_chain);
}
- else
- sox_delete_effects(effects_chain);
- sox_close(ofile->ft);
- ofile->ft = NULL;
}
- } while (process() != SOX_EOF && !user_abort && current_input < input_count);
- else process();
+ /* Advance to next effect loop; with rollover */
+ current_eff_loop++;
+ if (current_eff_loop >= eff_loop_count) current_eff_loop = 0;
+ sox_close(ofile->ft);
+ ofile->ft = NULL;
+ }
+
sox_delete_effects_chain(effects_chain);
- delete_user_effects();
+ delete_user_effargs();
for (i = 0; i < file_count; ++i)
if (files[i]->ft->clips != 0)
--- a/src/sox.h
+++ b/src/sox.h
@@ -502,6 +502,7 @@
size_t sox_effects_clips(sox_effects_chain_t *);
size_t sox_stop_effect(sox_effect_t *effp);
void sox_delete_effect(sox_effect_t *effp);
+void sox_delete_effect_last(sox_effects_chain_t *chain);
void sox_delete_effects(sox_effects_chain_t *chain);
/* The following routines are unique to the trim effect.