ref: 6fb7397c334e2362621f4e2c3eed7152538fa34b
parent: a3428e64ebeed3c0ed2f298906099cb79e796dd2
author: robs <robs>
date: Wed Jan 24 18:27:05 EST 2007
mix & merge no longer require same # of chans in each input
--- a/ChangeLog
+++ b/ChangeLog
@@ -53,6 +53,8 @@
o Higher quality audio speed adjustment; also fixes [1155364]. (robs)
o Replacement flanger effect; also fixes [1393245]. (robs)
o Added silence padding effect. (robs)
+ o Removed the restriction whereby multiple input files had to have
+ the same data encoding & size. (robs)
o Added ability to merge e.g. 2 mono files to 1 stereo file
[FR# 1297076]. (robs)
--- a/sox.1
+++ b/sox.1
@@ -38,9 +38,8 @@
optionally apply effects to them; it includes a basic audio synthesiser,
and, on many systems, can play and record audio files.
.SP
-SoX can also combine multiple input files (with the same sample rate and
-number of channels) to form one output file using one of three methods:
-`concatenate' (the default), `mix', or `merge'.
+SoX can also combine multiple input files to form one output file using
+one of three methods: `concatenate', `mix', or `merge'.
.SP
If invoked as `play', the output file is the default sound device. If
invoked as `rec', the input device is the default sound device.
@@ -217,8 +216,34 @@
.SP
If clipping occurs at any point during processing, then
SoX will display a warning message to that effect.
-.SS Input File Balancing
-When multiple input files are given, SoX applies any specified effects
+.SS Input File Combining
+In order for SoX to combine multiple input files, they must all have the
+same sample-rate.
+The default combining method is concatenate; here the input files must
+also have the same number of channels. For
+.BR play ,
+to concatenate multiple audio files means to play them in sequence.
+.SP
+If the `mix' combining method is selected (with \fB-m\fR) 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
+channels in the output file will not contain audio from every input
+file. A mixed audio file cannot be un-mixed.
+.SP
+If the `merge' combining method is selected (with \fB-M\fR), 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
+input files; un-merging is possible using multiple
+invocations of SoX with the
+.B mixer
+effect.
+For example, two mono files could be merged to form one stereo file; the
+first and second mono files would become the left and right channels of
+the stereo file.
+.SP
+When combining input files, SoX applies any specified effects
(including, for example, the
.B vol
volume adjustment effect) after the audio has been combined; however, it
@@ -225,7 +250,7 @@
is often useful to be able to set the volume of (i.e. `balance') the
inputs individually, before combining takes place.
.SP
-For all SoX combining methods (`concatenate', `mix', or `merge'), input
+For all combining methods, input
file volume adjustments can be made manually using the
.B \-v
option (below) which can be given for one or more input files; if it is
@@ -235,7 +260,7 @@
.SP
The \fB\-V\fR option (below) can be used to show the input file volume
adjustments that have been selected (either manually or automatically).
-.SS Input File Mixing
+.SP
There are some special considerations that need to made when mixing
input files:
.SP
@@ -385,27 +410,13 @@
.TP
\fB\-m\fR, \fB\-\-mix\fR
Set the input file combining method to `mix'.
-Two or more input files must be given,
-and will be mixed together (instead of concatenated)
-to form the output file.
-A mixed audio file cannot be un-mixed.
.SP
-See also \fBInput File Mixing\fR above.
+See also \fBInput File Combining\fR above.
.TP
\fB\-M\fR, \fB\-\-merge\fR
Set the input file combining method to `merge'.
-Two or more input files must be given,
-and will be merged together (instead of concatenated)
-to form the output file.
-A merged audio file comprises all of the channels from all of the input
-files; a merged file could be un-merged by using multiple invocations of
-SoX with the
-.B mixer
-effect.
.SP
-For example, two mono files could be merged to form one
-stereo file; the first and second mono files would become
-the left and right channels of the stereo file.
+See also \fBInput File Combining\fR above.
.TP
\fB\-\-octave\fR
Run in a mode that can be used, in conjunction with the GNU
--- a/src/sox.c
+++ b/src/sox.c
@@ -70,8 +70,8 @@
static int success = 0;
static st_option_t show_progress = ST_OPTION_DEFAULT;
-static unsigned long input_samples = 0;
-static unsigned long read_samples = 0;
+static unsigned long input_wide_samples = 0;
+static unsigned long read_wide_samples = 0;
static unsigned long output_samples = 0;
static st_sample_t ibufl[ST_BUFSIZ / 2]; /* Left/right interleave buffers */
@@ -88,15 +88,16 @@
double replay_gain;
char *comment;
st_size_t volume_clips;
-} *file_info_t;
+ ft_t desc; /* stlib file descriptor */
+} *file_t;
/* local forward declarations */
-static st_bool doopts(file_info_t fo, int, char **);
+static st_bool doopts(file_t, int, char **);
static void usage(char const *) NORET;
static void usage_effect(char *) NORET;
static void process(void);
static void update_status(void);
-static void volumechange(st_sample_t * buf, st_ssize_t len, file_info_t fo);
+static void balance_input(st_sample_t * buf, st_ssize_t len, file_t);
static void parse_effects(int argc, char **argv);
static void build_effects_table(void);
static int start_all_effects(void);
@@ -109,12 +110,11 @@
#define MAX_INPUT_FILES 32
#define MAX_FILES MAX_INPUT_FILES + 2 /* 1 output file plus record input */
-/* Arrays tracking input and output files */
-static file_info_t file_opts[MAX_FILES];
-static ft_t file_desc[MAX_FILES];
-#define ofile file_desc[file_count - 1]
+static file_t files[MAX_FILES]; /* Array tracking input and output files */
+#define ofile files[file_count - 1]
static size_t file_count = 0;
static size_t input_count = 0;
+static st_signalinfo_t combiner;
/* We parse effects into a temporary effects table and then place into
* the real effects table. This makes it easier to reorder some effects
@@ -179,65 +179,69 @@
static void cleanup(void)
{
size_t i;
- ft_t ft = ofile;
/* Close the input and output files before exiting. */
- for (i = 0; i < input_count; i++)
- if (file_desc[i]) {
- st_close(file_desc[i]);
- free(file_desc[i]);
+ for (i = 0; i < input_count; i++) {
+ if (files[i]->desc) {
+ st_close(files[i]->desc);
+ free(files[i]->desc);
}
+ free(files[i]);
+ }
- if (ft) {
- if (!(ft->h->flags & ST_FILE_NOSTDIO)) {
- struct stat st;
- fstat(fileno(ft->fp), &st);
+ if (file_count) {
+ if (ofile->desc) {
+ if (!(ofile->desc->h->flags & ST_FILE_NOSTDIO)) {
+ struct stat st;
+ fstat(fileno(ofile->desc->fp), &st);
- /* If we didn't succeed and we created an output file, remove it. */
- if (!success && (st.st_mode & S_IFMT) == S_IFREG)
- unlink(ft->filename);
- }
+ /* If we didn't succeed and we created an output file, remove it. */
+ if (!success && (st.st_mode & S_IFMT) == S_IFREG)
+ unlink(ofile->desc->filename);
+ }
- /* Assumption: we can unlink a file before st_closing it. */
- st_close(ft);
- free(ft);
+ /* Assumption: we can unlink a file before st_closing it. */
+ st_close(ofile->desc);
+ free(ofile->desc);
+ }
+ free(ofile);
}
}
-static file_info_t make_file_info(void)
+static file_t new_file(void)
{
- file_info_t fi = xcalloc(sizeof(*fi), 1);
+ file_t f = xcalloc(sizeof(*f), 1);
- fi->signal.size = -1;
- fi->signal.encoding = ST_ENCODING_UNKNOWN;
- fi->signal.channels = 0;
- fi->signal.reverse_bytes = ST_OPTION_DEFAULT;
- fi->signal.reverse_nibbles = ST_OPTION_DEFAULT;
- fi->signal.reverse_bits = ST_OPTION_DEFAULT;
- fi->signal.compression = HUGE_VAL;
- fi->volume = HUGE_VAL;
- fi->replay_gain = HUGE_VAL;
- fi->volume_clips = 0;
+ f->signal.size = -1;
+ f->signal.encoding = ST_ENCODING_UNKNOWN;
+ f->signal.channels = 0;
+ f->signal.reverse_bytes = ST_OPTION_DEFAULT;
+ f->signal.reverse_nibbles = ST_OPTION_DEFAULT;
+ f->signal.reverse_bits = ST_OPTION_DEFAULT;
+ f->signal.compression = HUGE_VAL;
+ f->volume = HUGE_VAL;
+ f->replay_gain = HUGE_VAL;
+ f->volume_clips = 0;
- return fi;
+ return f;
}
-static void set_device(file_info_t fi)
+static void set_device(file_t f)
{
#if defined(HAVE_ALSA)
- fi->filetype = "alsa";
- fi->filename = xstrdup("default");
+ f->filetype = "alsa";
+ f->filename = xstrdup("default");
#elif defined(HAVE_OSS)
- fi->filetype = "ossdsp";
- fi->filename = xstrdup("/dev/dsp");
+ f->filetype = "ossdsp";
+ f->filename = xstrdup("/dev/dsp");
#elif defined (HAVE_SUN_AUDIO)
char *device = getenv("AUDIODEV");
- fi->filetype = "sunau";
- fi->filename = xstrdup(device ? device : "/dev/audio");
+ f->filetype = "sunau";
+ f->filename = xstrdup(device ? device : "/dev/audio");
#endif
}
-static void set_replay_gain(char const * comment, file_info_t fi)
+static void set_replay_gain(char const * comment, file_t f)
{
rg_t rg = replay_gain_mode;
int try = 2;
@@ -248,7 +252,7 @@
rg == RG_TRACK? "REPLAYGAIN_TRACK_GAIN=" : "REPLAYGAIN_ALBUM_GAIN=";
do {
if (strncasecmp(p, target, strlen(target)) == 0) {
- fi->replay_gain = atof(p + strlen(target));
+ f->replay_gain = atof(p + strlen(target));
return;
}
while (*p && *p!= '\n') ++p;
@@ -258,31 +262,14 @@
}
}
-int main(int argc, char **argv)
+static void parse_options_and_filenames(int argc, char **argv)
{
- size_t i;
- file_info_t fi = NULL;
+ file_t f = NULL;
struct file_info fi_none;
- myname = argv[0];
- atexit(cleanup);
- st_output_message_handler = sox_output_message;
-
- i = strlen(myname);
- if (i >= sizeof("play") - 1 &&
- strcmp(myname + i - (sizeof("play") - 1), "play") == 0) {
- play = st_true;
- replay_gain_mode = RG_TRACK;
- } else if (i >= sizeof("rec") - 1 &&
- strcmp(myname + i - (sizeof("rec") - 1), "rec") == 0) {
- rec = st_true;
- }
-
- /* Loop over arguments and filenames, stop when an effect name is
- * found. */
while (optind < argc && !is_effect_name(argv[optind])) {
- fi = make_file_info();
- fi_none = *fi;
+ f = new_file();
+ fi_none = *f;
if (file_count >= MAX_FILES) {
st_fail("Too many filenames; maximum is %d input files and 1 output file", MAX_INPUT_FILES);
@@ -289,39 +276,37 @@
exit(1);
}
- if (doopts(fi, argc, argv)) { /* is null file? */
- if (fi->filetype != NULL && strcmp(fi->filetype, "null") != 0)
- st_warn("Ignoring '-t %s'.", fi->filetype);
- fi->filetype = "null";
- fi->filename = xstrdup("-n");
+ if (doopts(f, argc, argv)) { /* is null file? */
+ if (f->filetype != NULL && strcmp(f->filetype, "null") != 0)
+ st_warn("Ignoring '-t %s'.", f->filetype);
+ f->filetype = "null";
+ f->filename = xstrdup("-n");
} else {
if (optind >= argc || is_effect_name(argv[optind]))
break;
- fi->filename = xstrdup(argv[optind++]);
+ f->filename = xstrdup(argv[optind++]);
}
- file_opts[file_count++] = fi;
- fi = NULL;
+ files[file_count++] = f;
+ f = NULL;
}
if (play) {
- file_info_t fo = fi? fi : make_file_info();
-
if (file_count >= MAX_FILES) {
st_fail("Too many filenames; maximum is %d input files and 1 output file", MAX_INPUT_FILES);
exit(1);
}
- set_device(fo);
- file_opts[file_count++] = fo;
+ f = f? f : new_file();
+ set_device(f);
+ files[file_count++] = f;
}
- else if (fi) {
- if (memcmp(fi, &fi_none, sizeof(*fi)) != 0) /* fopts but no file */
+ else if (f) {
+ if (memcmp(f, &fi_none, sizeof(*f)) != 0) /* fopts but no file */
usage("missing filename"); /* No return */
- free(fi); /* No file opts and no filename, so that's okay */
+ free(f); /* No file opts and no filename, so that's okay */
}
if (rec) {
- file_info_t fo = make_file_info();
st_size_t i;
if (file_count >= MAX_FILES) {
@@ -330,13 +315,34 @@
}
for (i = file_count; i > 0; -- i)
- file_opts[i] = file_opts[i - 1];
-
+ files[i] = files[i - 1];
file_count++;
- set_device(fo);
- file_opts[0] = fo;
+
+ f = new_file();
+ set_device(f);
+ files[0] = f;
}
+}
+int main(int argc, char **argv)
+{
+ size_t i;
+
+ myname = argv[0];
+ atexit(cleanup);
+ st_output_message_handler = sox_output_message;
+
+ i = strlen(myname);
+ if (i >= sizeof("play") - 1 &&
+ strcmp(myname + i - (sizeof("play") - 1), "play") == 0) {
+ play = st_true;
+ replay_gain_mode = RG_TRACK;
+ } else if (i >= sizeof("rec") - 1 &&
+ strcmp(myname + i - (sizeof("rec") - 1), "rec") == 0) {
+ rec = st_true;
+ }
+ parse_options_and_filenames(argc, argv);
+
/* Make sure we got at least the required # of input filenames */
input_count = file_count ? file_count - 1 : 0;
if (input_count < (combine_method == SOX_CONCAT ? 1 : 2))
@@ -344,46 +350,46 @@
/* Check for misplaced input/output-specific options */
for (i = 0; i < input_count; ++i) {
- if (file_opts[i]->signal.compression != HUGE_VAL)
+ if (files[i]->signal.compression != HUGE_VAL)
usage("A compression factor can only be given for an output file");
- if (file_opts[i]->comment != NULL)
+ if (files[i]->comment != NULL)
usage("A comment can only be given for an output file");
}
- if (file_opts[i]->volume != HUGE_VAL)
+ if (ofile->volume != HUGE_VAL)
usage("-v can only be given for an input file;\n"
"\tuse 'vol' to set the output file volume");
for (i = 0; i < input_count; i++) {
int j = input_count - 1 - i; /* Open in reverse order 'cos of rec (below) */
- file_info_t fi = file_opts[j];
+ file_t f = files[j];
/* When mixing audio, default to input side volume adjustments that will
* make sure no clipping will occur. Users probably won't be happy with
* this, and will override it, possibly causing clipping to occur. */
if (combine_method == SOX_MIX && !uservolume)
- fi->volume = 1.0 / input_count;
+ f->volume = 1.0 / input_count;
if (rec && !j) { /* Set the recording sample rate & # of channels: */
if (input_count > 1) { /* Get them from the next input file: */
- fi->signal.rate = file_desc[1]->signal.rate;
- fi->signal.channels = file_desc[1]->signal.channels;
+ f->signal.rate = files[1]->desc->signal.rate;
+ f->signal.channels = files[1]->desc->signal.channels;
}
else { /* Get them from the output file (which is not open yet): */
- fi->signal.rate = file_opts[1]->signal.rate;
- fi->signal.channels = file_opts[1]->signal.channels;
+ f->signal.rate = files[1]->signal.rate;
+ f->signal.channels = files[1]->signal.channels;
}
}
- file_desc[j] = st_open_read(fi->filename, &fi->signal, fi->filetype);
- if (!file_desc[j])
+ files[j]->desc = st_open_read(f->filename, &f->signal, f->filetype);
+ if (!files[j]->desc)
/* st_open_read() will call st_warn for most errors.
* Rely on that printing something. */
exit(2);
if (show_progress == ST_OPTION_DEFAULT &&
- (file_desc[j]->h->flags & ST_FILE_DEVICE) != 0 &&
- (file_desc[j]->h->flags & ST_FILE_PHONY) == 0)
+ (files[j]->desc->h->flags & ST_FILE_DEVICE) != 0 &&
+ (files[j]->desc->h->flags & ST_FILE_PHONY) == 0)
show_progress = ST_OPTION_YES;
- if (file_desc[j]->comment)
- set_replay_gain(file_desc[j]->comment, fi);
+ if (files[j]->desc->comment)
+ set_replay_gain(files[j]->desc->comment, f);
}
/* Loop through the rest of the arguments looking for effects */
@@ -404,9 +410,9 @@
st_warn("-m clipped %u samples; decrease volume?", mixing_clips);
for (i = 0; i < file_count; i++)
- if (file_opts[i]->volume_clips > 0)
- st_warn("%s: -v clipped %u samples; decrease volume?", file_opts[i]->filename,
- file_opts[i]->volume_clips);
+ if (files[i]->volume_clips > 0)
+ st_warn("%s: -v clipped %u samples; decrease volume?", files[i]->filename,
+ files[i]->volume_clips);
if (show_progress) {
if (user_abort)
@@ -481,7 +487,7 @@
{NULL, 0, NULL, 0}
};
-static st_bool doopts(file_info_t fi, int argc, char **argv)
+static st_bool doopts(file_t f, int argc, char **argv)
{
while (st_true) {
int option_index;
@@ -495,20 +501,20 @@
case 0: /* Long options with no short equivalent. */
switch (option_index) {
case 0:
- fi->comment = read_comment_file(optarg);
+ f->comment = read_comment_file(optarg);
break;
case 1:
- fi->comment = xstrdup(optarg);
+ f->comment = xstrdup(optarg);
break;
case 2:
if (!strcmp(optarg, "little"))
- fi->signal.reverse_bytes = ST_IS_BIGENDIAN;
+ f->signal.reverse_bytes = ST_IS_BIGENDIAN;
else if (!strcmp(optarg, "big"))
- fi->signal.reverse_bytes = ST_IS_LITTLEENDIAN;
+ f->signal.reverse_bytes = ST_IS_LITTLEENDIAN;
else if (!strcmp(optarg, "swap"))
- fi->signal.reverse_bytes = st_true;
+ f->signal.reverse_bytes = st_true;
else {
st_fail("Endian type '%s' is not little|big|swap", optarg);
exit(1);
@@ -568,9 +574,9 @@
break;
case 't':
- fi->filetype = optarg;
- if (fi->filetype[0] == '.')
- fi->filetype++;
+ f->filetype = optarg;
+ if (f->filetype[0] == '.')
+ f->filetype++;
break;
case 'r':
@@ -578,16 +584,16 @@
st_fail("Rate value '%s' is not a positive integer", optarg);
exit(1);
}
- fi->signal.rate = i;
+ f->signal.rate = i;
break;
case 'v':
- if (sscanf(optarg, "%lf %c", &fi->volume, &dummy) != 1) {
+ if (sscanf(optarg, "%lf %c", &f->volume, &dummy) != 1) {
st_fail("Volume value '%s' is not a number", optarg);
exit(1);
}
uservolume = st_true;
- if (fi->volume < 0.0)
+ if (f->volume < 0.0)
st_report("Volume adjustment is negative; "
"this will result in a phase change");
break;
@@ -597,46 +603,46 @@
st_fail("Channels value '%s' is not a positive integer", optarg);
exit(1);
}
- fi->signal.channels = i;
+ f->signal.channels = i;
break;
case 'C':
- if (sscanf(optarg, "%lf %c", &fi->signal.compression, &dummy) != 1) {
+ if (sscanf(optarg, "%lf %c", &f->signal.compression, &dummy) != 1) {
st_fail("Compression value '%s' is not a number", optarg);
exit(1);
}
break;
- case '1': case 'b': fi->signal.size = ST_SIZE_BYTE; break;
- case '2': case 'w': fi->signal.size = ST_SIZE_16BIT; break;
- case '3': fi->signal.size = ST_SIZE_24BIT; break;
- case '4': case 'l': fi->signal.size = ST_SIZE_32BIT; break;
- case '8': case 'd': fi->signal.size = ST_SIZE_64BIT; break;
+ case '1': case 'b': f->signal.size = ST_SIZE_BYTE; break;
+ case '2': case 'w': f->signal.size = ST_SIZE_16BIT; break;
+ case '3': f->signal.size = ST_SIZE_24BIT; break;
+ case '4': case 'l': f->signal.size = ST_SIZE_32BIT; break;
+ case '8': case 'd': f->signal.size = ST_SIZE_64BIT; break;
- case 's': fi->signal.encoding = ST_ENCODING_SIGN2; break;
- case 'u': fi->signal.encoding = ST_ENCODING_UNSIGNED; break;
- case 'f': fi->signal.encoding = ST_ENCODING_FLOAT; break;
- case 'a': fi->signal.encoding = ST_ENCODING_ADPCM; break;
- case 'D': fi->signal.encoding = ST_ENCODING_MS_ADPCM; break; /* WIP */
- case 'i': fi->signal.encoding = ST_ENCODING_IMA_ADPCM; break;
- case 'o': fi->signal.encoding = ST_ENCODING_OKI_ADPCM; break; /* WIP */
- case 'g': fi->signal.encoding = ST_ENCODING_GSM; break;
+ case 's': f->signal.encoding = ST_ENCODING_SIGN2; break;
+ case 'u': f->signal.encoding = ST_ENCODING_UNSIGNED; break;
+ case 'f': f->signal.encoding = ST_ENCODING_FLOAT; break;
+ case 'a': f->signal.encoding = ST_ENCODING_ADPCM; break;
+ case 'D': f->signal.encoding = ST_ENCODING_MS_ADPCM; break; /* WIP */
+ case 'i': f->signal.encoding = ST_ENCODING_IMA_ADPCM; break;
+ case 'o': f->signal.encoding = ST_ENCODING_OKI_ADPCM; break; /* WIP */
+ case 'g': f->signal.encoding = ST_ENCODING_GSM; break;
- case 'U': fi->signal.encoding = ST_ENCODING_ULAW;
- if (fi->signal.size == -1)
- fi->signal.size = ST_SIZE_BYTE;
+ case 'U': f->signal.encoding = ST_ENCODING_ULAW;
+ if (f->signal.size == -1)
+ f->signal.size = ST_SIZE_BYTE;
break;
- case 'A': fi->signal.encoding = ST_ENCODING_ALAW;
- if (fi->signal.size == -1)
- fi->signal.size = ST_SIZE_BYTE;
+ case 'A': f->signal.encoding = ST_ENCODING_ALAW;
+ if (f->signal.size == -1)
+ f->signal.size = ST_SIZE_BYTE;
break;
- case 'L': fi->signal.reverse_bytes = ST_IS_BIGENDIAN; break;
- case 'B': fi->signal.reverse_bytes = ST_IS_LITTLEENDIAN; break;
- case 'x': fi->signal.reverse_bytes = ST_OPTION_YES; break;
- case 'X': fi->signal.reverse_bits = ST_OPTION_YES; break;
- case 'N': fi->signal.reverse_nibbles = ST_OPTION_YES; break;
+ case 'L': f->signal.reverse_bytes = ST_IS_BIGENDIAN; break;
+ case 'B': f->signal.reverse_bytes = ST_IS_LITTLEENDIAN; break;
+ case 'x': f->signal.reverse_bytes = ST_OPTION_YES; break;
+ case 'X': f->signal.reverse_bits = ST_OPTION_YES; break;
+ case 'N': f->signal.reverse_nibbles = ST_OPTION_YES; break;
case 'S': show_progress = ST_OPTION_YES; break;
case 'q': show_progress = ST_OPTION_NO; break;
@@ -655,16 +661,14 @@
}
}
-static void display_file_info(int file_no, double speed, st_bool full)
+static void display_file_info(file_t f, st_bool full)
{
static char const * const no_yes[] = {"no", "yes"};
- ft_t f = file_desc[file_no];
- file_info_t fo = file_opts[file_no];
fprintf(stderr, "\n%s: '%s'",
- f->mode == 'r'? "Input File " : "Output File ", f->filename);
- if (strcmp(f->filename, "-") == 0 || (f->h->flags & ST_FILE_DEVICE))
- fprintf(stderr, " (%s)", f->h->names[0]);
+ f->desc->mode == 'r'? "Input File " : "Output File ", f->desc->filename);
+ if (strcmp(f->desc->filename, "-") == 0 || (f->desc->h->flags & ST_FILE_DEVICE))
+ fprintf(stderr, " (%s)", f->desc->h->names[0]);
fprintf(stderr, "\n"
"Sample Size : %s (%s)\n"
@@ -671,10 +675,10 @@
"Sample Encoding: %s\n"
"Channels : %u\n"
"Sample Rate : %u\n",
- st_size_bits_str[f->signal.size], st_sizes_str[f->signal.size],
- st_encodings_str[f->signal.encoding],
- f->signal.channels,
- (int)(f->signal.rate / speed + 0.5));
+ st_size_bits_str[f->desc->signal.size], st_sizes_str[f->desc->signal.size],
+ st_encodings_str[f->desc->signal.encoding],
+ f->desc->signal.channels,
+ f->desc->signal.rate);
if (full)
fprintf(stderr,
@@ -681,40 +685,40 @@
"Endian Type : %s\n"
"Reverse Nibbles: %s\n"
"Reverse Bits : %s\n",
- f->signal.size == 1? "N/A" :
- f->signal.reverse_bytes != ST_IS_BIGENDIAN? "big" : "little",
- no_yes[f->signal.reverse_nibbles],
- no_yes[f->signal.reverse_bits]);
+ f->desc->signal.size == 1? "N/A" :
+ f->desc->signal.reverse_bytes != ST_IS_BIGENDIAN? "big" : "little",
+ no_yes[f->desc->signal.reverse_nibbles],
+ no_yes[f->desc->signal.reverse_bits]);
- if (fo->replay_gain != HUGE_VAL)
- fprintf(stderr, "Replay gain : %+g dB\n" , fo->replay_gain);
- if (fo->volume != HUGE_VAL)
- fprintf(stderr, "Level adjust : %g (linear gain)\n" , fo->volume);
+ if (f->replay_gain != HUGE_VAL)
+ fprintf(stderr, "Replay gain : %+g dB\n" , f->replay_gain);
+ if (f->volume != HUGE_VAL)
+ fprintf(stderr, "Level adjust : %g (linear gain)\n" , f->volume);
- if (!(f->h->flags & ST_FILE_DEVICE) && f->comment) {
- if (strchr(f->comment, '\n'))
- fprintf(stderr, "Comments : \n%s\n", f->comment);
+ if (!(f->desc->h->flags & ST_FILE_DEVICE) && f->desc->comment) {
+ if (strchr(f->desc->comment, '\n'))
+ fprintf(stderr, "Comments : \n%s\n", f->desc->comment);
else
- fprintf(stderr, "Comment : '%s'\n", f->comment);
+ fprintf(stderr, "Comment : '%s'\n", f->desc->comment);
}
fprintf(stderr, "\n");
}
-static void report_file_info(int f)
+static void report_file_info(file_t f)
{
if (st_output_verbosity_level > 2)
- display_file_info(f, 1, st_true);
+ display_file_info(f, st_true);
}
-static void progress_to_file(int f)
+static void progress_to_file(file_t f)
{
if (show_progress && (st_output_verbosity_level < 3 ||
(combine_method == SOX_CONCAT && input_count > 1)))
- display_file_info(f, globalinfo.speed, st_false);
- if (file_opts[f]->volume == HUGE_VAL)
- file_opts[f]->volume = 1;
- if (file_opts[f]->replay_gain != HUGE_VAL)
- file_opts[f]->volume *= pow(10, file_opts[f]->replay_gain / 20);
+ display_file_info(f, st_false);
+ if (f->volume == HUGE_VAL)
+ f->volume = 1;
+ if (f->replay_gain != HUGE_VAL)
+ f->volume *= pow(10, f->replay_gain / 20);
}
static void sigint(int s)
@@ -738,67 +742,86 @@
static void process(void) {
int e, flowstatus = 0;
size_t current_input = 0;
- st_size_t s, f;
+ st_size_t ws, s, i;
st_ssize_t ilen[MAX_INPUT_FILES];
st_sample_t *ibuf[MAX_INPUT_FILES];
+ {
+ st_size_t total_channels = 0;
+ st_size_t min_channels = ST_SIZE_MAX;
+ st_size_t max_channels = 0;
+ st_size_t min_rate = ST_SIZE_MAX;
+ st_size_t max_rate = 0;
- for (f = 0; f < input_count; f++) { /* Report all inputs first, then check */
- report_file_info(f);
- if (combine_method == SOX_MERGE)
- file_desc[f]->signal.channels *= input_count;
- }
- for (f = 1; f < input_count; f++)
- if (file_desc[0]->signal.rate != file_desc[f]->signal.rate ||
- file_desc[0]->signal.channels != file_desc[f]->signal.channels) {
- st_fail("Input files must have the same rate and # of channels");
- exit(1);
+ for (i = 0; i < input_count; i++) { /* Report all inputs, then check */
+ report_file_info(files[i]);
+ total_channels += files[i]->desc->signal.channels;
+ min_channels = min(min_channels, files[i]->desc->signal.channels);
+ max_channels = max(max_channels, files[i]->desc->signal.channels);
+ min_rate = min(min_rate, files[i]->desc->signal.rate);
+ max_rate = max(max_rate, files[i]->desc->signal.rate);
}
+ if (min_rate != max_rate)
+ st_fail("Input files do not have the same sample-rate");
+ if (min_channels != max_channels) {
+ if (combine_method == SOX_CONCAT) {
+ st_fail("Input files do not have the same # channels");
+ exit(1);
+ } else if (combine_method == SOX_MIX)
+ st_warn("Input files do not have the same # channels");
+ }
+ if (min_rate != max_rate)
+ exit(1);
+ combiner = files[0]->desc->signal;
+ combiner.channels =
+ combine_method == SOX_MERGE? total_channels : max_channels;
+ }
+
+ if (ofile->signal.rate == 0)
+ ofile->signal.rate = combiner.rate;
+ if (ofile->signal.size == -1)
+ ofile->signal.size = combiner.size;
+ if (ofile->signal.encoding == ST_ENCODING_UNKNOWN)
+ ofile->signal.encoding = combiner.encoding;
+ if (ofile->signal.channels == 0)
+ ofile->signal.channels = combiner.channels;
+
+ combiner.rate = combiner.rate * globalinfo.speed + .5;
+
{
st_loopinfo_t loops[ST_MAX_NLOOPS];
double factor;
int i;
- file_info_t info = file_opts[file_count - 1];
char const *comment = NULL;
- if (info->signal.rate == 0)
- info->signal.rate = file_desc[0]->signal.rate;
- if (info->signal.size == -1)
- info->signal.size = file_desc[0]->signal.size;
- if (info->signal.encoding == ST_ENCODING_UNKNOWN)
- info->signal.encoding = file_desc[0]->signal.encoding;
- if (info->signal.channels == 0)
- info->signal.channels = file_desc[0]->signal.channels;
+ if (ofile->comment == NULL)
+ comment = files[0]->desc->comment ? files[0]->desc->comment : "Processed by SoX";
+ else if (*ofile->comment != '\0')
+ comment = ofile->comment;
- if (info->comment == NULL)
- comment = file_desc[0]->comment ? file_desc[0]->comment : "Processed by SoX";
- else if (*info->comment != '\0')
- comment = info->comment;
-
/*
- * copy loop info, resizing appropriately
+ * copy loop ofile, 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) info->signal.rate / (double)
- file_desc[0]->signal.rate;
+ factor = (double) ofile->signal.rate / combiner.rate;
for (i = 0; i < ST_MAX_NLOOPS; i++) {
- loops[i].start = file_desc[0]->loops[i].start * factor;
- loops[i].length = file_desc[0]->loops[i].length * factor;
- loops[i].count = file_desc[0]->loops[i].count;
- loops[i].type = file_desc[0]->loops[i].type;
+ loops[i].start = files[0]->desc->loops[i].start * factor;
+ loops[i].length = files[0]->desc->loops[i].length * factor;
+ loops[i].count = files[0]->desc->loops[i].count;
+ loops[i].type = files[0]->desc->loops[i].type;
}
- ofile = st_open_write(overwrite_permitted,
- info->filename,
- &info->signal,
- info->filetype,
+ ofile->desc = st_open_write(overwrite_permitted,
+ ofile->filename,
+ &ofile->signal,
+ ofile->filetype,
comment,
- &file_desc[0]->instr,
+ &files[0]->desc->instr,
loops);
- if (!ofile)
+ if (!ofile->desc)
/* st_open_write() will call st_warn for most errors.
* Rely on that printing something. */
exit(2);
@@ -807,16 +830,12 @@
* progress display to match behavior of ogg123,
* unless the user requested us not to display anything. */
if (show_progress == ST_OPTION_DEFAULT)
- show_progress = (ofile->h->flags & ST_FILE_DEVICE) != 0 &&
- (ofile->h->flags & ST_FILE_PHONY) == 0;
+ show_progress = (ofile->desc->h->flags & ST_FILE_DEVICE) != 0 &&
+ (ofile->desc->h->flags & ST_FILE_PHONY) == 0;
- report_file_info(file_count - 1);
+ report_file_info(ofile);
}
- /* Adjust the input rate for the speed effect */
- for (f = 0; f < input_count; f++)
- file_desc[f]->signal.rate = file_desc[f]->signal.rate * globalinfo.speed + .5;
-
build_effects_table();
if (start_all_effects() != ST_SUCCESS)
@@ -829,24 +848,19 @@
efftabR[e].obuf = (st_sample_t *)xmalloc(ST_BUFSIZ * sizeof(st_sample_t));
}
- if (combine_method != SOX_CONCAT) {
- for (f = 0; f < input_count; f++) {
- st_size_t alloc_size = ST_BUFSIZ * sizeof(st_sample_t);
+ if (combine_method == SOX_CONCAT) {
+ current_input = 0;
+ input_wide_samples = files[current_input]->desc->length / files[current_input]->desc->signal.channels;
+ progress_to_file(files[current_input]);
+ } else {
+ for (i = 0; i < input_count; i++) {
/* Treat overall length the same as longest input file. */
- if (file_desc[f]->length > input_samples)
- input_samples = file_desc[f]->length;
-
- if (combine_method == SOX_MERGE) {
- alloc_size /= input_count;
- file_desc[f]->signal.channels /= input_count;
- }
- ibuf[f] = (st_sample_t *)xmalloc(alloc_size);
- progress_to_file(f);
+ size_t wide_samples = files[i]->desc->length / files[i]->desc->signal.channels;
+ if (wide_samples > input_wide_samples)
+ input_wide_samples = wide_samples;
+ ibuf[i] = (st_sample_t *)xmalloc(ST_BUFSIZ * sizeof(st_sample_t));
+ progress_to_file(files[i]);
}
- } else {
- current_input = 0;
- input_samples = file_desc[current_input]->length;
- progress_to_file(current_input);
}
/*
@@ -853,8 +867,8 @@
* Just like errno, we must set st_errno to known values before
* calling I/O operations.
*/
- for (f = 0; f < file_count; f++)
- file_desc[f]->st_errno = 0;
+ for (i = 0; i < file_count; i++)
+ files[i]->desc->st_errno = 0;
input_eff = 0;
input_eff_eof = 0;
@@ -875,7 +889,7 @@
user_skip = st_false;
fprintf(stderr, "\nSkipped.");
} else {
- ilen[0] = st_read(file_desc[current_input], efftab[0].obuf,
+ ilen[0] = st_read(files[current_input]->desc, efftab[0].obuf,
(st_ssize_t)ST_BUFSIZ);
if (ilen[0] > ST_BUFSIZ) {
st_warn("WARNING: Corrupt value of %d! Assuming 0 bytes read.", ilen);
@@ -884,12 +898,12 @@
if (ilen[0] == ST_EOF) {
efftab[0].olen = 0;
- if (file_desc[current_input]->st_errno)
- fprintf(stderr, file_desc[current_input]->st_errstr);
+ if (files[current_input]->desc->st_errno)
+ fprintf(stderr, files[current_input]->desc->st_errstr);
} else
efftab[0].olen = ilen[0];
- read_samples += efftab[0].olen;
+ read_wide_samples += efftab[0].olen / combiner.channels;
}
/* Some file handlers claim 0 bytes instead of returning
* ST_EOF. In either case, attempt to go to the next
@@ -898,73 +912,47 @@
if (ilen[0] == ST_EOF || efftab[0].olen == 0) {
if (current_input < input_count - 1) {
current_input++;
- input_samples = file_desc[current_input]->length;
- read_samples = 0;
- progress_to_file(current_input);
+ input_wide_samples = files[current_input]->desc->length / files[current_input]->desc->signal.channels;
+ read_wide_samples = 0;
+ progress_to_file(files[current_input]);
continue;
}
}
- volumechange(efftab[0].obuf, efftab[0].olen, file_opts[current_input]);
- } else if (combine_method == SOX_MIX) {
- for (f = 0; f < input_count; f++) {
- ilen[f] = st_read(file_desc[f], ibuf[f], (st_ssize_t)ST_BUFSIZ);
-
- if (ilen[f] == ST_EOF) {
- ilen[f] = 0;
- if (file_desc[f]->st_errno)
- fprintf(stderr, file_desc[f]->st_errstr);
+ balance_input(efftab[0].obuf, efftab[0].olen, files[current_input]);
+ } else {
+ st_sample_t * p = efftab[0].obuf;
+ efftab[0].olen = 0;
+ for (i = 0; i < input_count; ++i) {
+ ilen[i] = st_read(files[i]->desc, ibuf[i], ST_BUFSIZ / combiner.channels * files[i]->desc->signal.channels);
+ if (ilen[i] == ST_EOF) {
+ ilen[i] = 0;
+ if (files[i]->desc->st_errno)
+ fprintf(stderr, files[i]->desc->st_errstr);
}
-
- /* Only count read samples for first file in mix */
- if (f == 0)
- read_samples += efftab[0].olen;
-
- volumechange(ibuf[f], ilen[f], file_opts[f]);
+ balance_input(ibuf[i], ilen[i], files[i]);
+ ilen[i] /= files[i]->desc->signal.channels;
+ if ((st_size_t)ilen[i] > efftab[0].olen)
+ efftab[0].olen = ilen[i];
}
-
- /* FIXME: Should report if the size of the reads are not
- * the same.
- */
- efftab[0].olen = 0;
- for (f = 0; f < input_count; f++)
- if ((st_size_t)ilen[f] > efftab[0].olen)
- efftab[0].olen = ilen[f];
-
- for (s = 0; s < efftab[0].olen; s++) {
- /* Mix audio by summing samples together.
- * Input side volume changes are performed above. */
- for (f = 0; f < input_count; f++) {
- if (f == 0)
- efftab[0].obuf[s] =
- (s<(st_size_t)ilen[f]) ? ibuf[f][s] : 0;
- else if (s < (st_size_t)ilen[f]) {
- /* Cast to double prevents integer overflow */
- double sample = efftab[0].obuf[s] + (double)ibuf[f][s];
- efftab[0].obuf[s] = ST_ROUND_CLIP_COUNT(sample, mixing_clips);
+ for (ws = 0; ws < efftab[0].olen; ++ws) /* wide samples */
+ if (combine_method == SOX_MIX) { /* sum samples together */
+ for (s = 0; s < combiner.channels; ++s) {
+ *p = 0;
+ for (i = 0; i < input_count; ++i)
+ if (ws < (st_size_t)ilen[i] && s < files[i]->desc->signal.channels) {
+ /* Cast to double prevents integer overflow */
+ double sample = *p + (double)ibuf[i][ws * files[i]->desc->signal.channels + s];
+ *p = ST_ROUND_CLIP_COUNT(sample, mixing_clips);
+ }
+ ++p;
}
- }
+ } else { /* SOX_MERGE: like a multi-track recorder */
+ for (i = 0; i < input_count; ++i)
+ for (s = 0; s < files[i]->desc->signal.channels; ++s)
+ *p++ = (ws < (st_size_t)ilen[i]) * ibuf[i][ws * files[i]->desc->signal.channels + s];
}
- } else { /* combine_method == SOX_MERGE */
- efftab[0].olen = 0;
- for (f = 0; f < input_count; ++f) {
- ilen[f] = st_read(file_desc[f], ibuf[f], ST_BUFSIZ / input_count);
- if (ilen[f] == ST_EOF) {
- ilen[f] = 0;
- if (file_desc[f]->st_errno)
- fprintf(stderr, file_desc[f]->st_errstr);
- }
- if ((st_size_t)ilen[f] > efftab[0].olen)
- efftab[0].olen = ilen[f];
- volumechange(ibuf[f], ilen[f], file_opts[f]);
- }
-
- for (s = 0; s < efftab[0].olen; s++)
- for (f = 0; f < input_count; f++)
- efftab[0].obuf[s * input_count + f] =
- (s < (st_size_t)ilen[f]) * ibuf[f][s];
-
- read_samples += efftab[0].olen;
- efftab[0].olen *= input_count;
+ read_wide_samples += efftab[0].olen;
+ efftab[0].olen *= combiner.channels;
}
efftab[0].odone = 0;
@@ -983,12 +971,12 @@
break;
/* If there's an error, don't try to write more. */
- if (ofile->st_errno)
+ if (ofile->desc->st_errno)
break;
} while (flowstatus == 0);
/* Drain the effects; don't write if output is indicating errors. */
- if (ofile->st_errno == 0)
+ if (ofile->desc->st_errno == 0)
drain_effect_out();
if (show_progress)
@@ -996,8 +984,8 @@
if (combine_method != SOX_CONCAT)
/* Free input buffers now that they are not used */
- for (f = 0; f < input_count; f++)
- free(ibuf[f]);
+ for (i = 0; i < input_count; i++)
+ free(ibuf[i]);
/* Free output buffers now that they won't be used */
for (e = 0; e < neffects; e++) {
@@ -1008,16 +996,16 @@
/* N.B. more data may be written during stop_effects */
stop_effects();
- for (f = 0; f < input_count; f++)
- if (file_desc[f]->clips != 0)
- st_warn("%s: input clipped %u samples", file_desc[f]->filename,
- file_desc[f]->clips);
+ for (i = 0; i < input_count; i++)
+ if (files[i]->desc->clips != 0)
+ st_warn("%s: input clipped %u samples", files[i]->desc->filename,
+ files[i]->desc->clips);
- if (file_desc[f]->clips != 0)
+ if (files[i]->desc->clips != 0)
st_warn("%s: output clipped %u samples; decrease volume?",
- (file_desc[f]->h->flags & ST_FILE_DEVICE)?
- file_desc[f]->h->names[0] : file_desc[f]->filename,
- file_desc[f]->clips);
+ (files[i]->desc->h->flags & ST_FILE_DEVICE)?
+ files[i]->desc->h->names[0] : files[i]->desc->filename,
+ files[i]->desc->clips);
}
static void parse_effects(int argc, char **argv)
@@ -1055,7 +1043,7 @@
/* Copy format info to effect table */
*effects_mask =
- st_updateeffect(e, &file_desc[0]->signal, &ofile->signal, *effects_mask);
+ st_updateeffect(e, &combiner, &ofile->desc->signal, *effects_mask);
/* If this effect can't handle multiple channels then account for this. */
if (e->ininfo.channels > 1 && !(e->h->flags & ST_EFF_MCHAN))
@@ -1091,8 +1079,8 @@
{
int i;
int effects_mask = 0;
- st_bool need_rate = file_desc[0]->signal.rate != ofile->signal.rate;
- st_bool need_chan = file_desc[0]->signal.channels != ofile->signal.channels;
+ st_bool need_rate = combiner.rate != ofile->desc->signal.rate;
+ st_bool need_chan = combiner.channels != ofile->desc->signal.channels;
{ /* Check if we have to add effects to change rate/chans or if the
user has specified effects to do this, in which case, check if
@@ -1124,13 +1112,13 @@
neffects = 1;
/* If reducing channels, it's faster to do so before all other effects: */
- if (need_chan && file_desc[0]->signal.channels > ofile->signal.channels) {
+ if (need_chan && combiner.channels > ofile->desc->signal.channels) {
add_default_effect("mixer", &effects_mask);
need_chan = st_false;
}
/* If reducing rate, it's faster to do so before all other effects
* (except reducing channels): */
- if (need_rate && file_desc[0]->signal.rate > ofile->signal.rate) {
+ if (need_rate && combiner.rate > ofile->desc->signal.rate) {
add_default_effect("resample", &effects_mask);
need_rate = st_false;
}
@@ -1219,22 +1207,22 @@
if (user_abort)
return ST_EOF;
- len = st_write(ofile,
+ len = st_write(ofile->desc,
&efftab[neffects - 1].obuf[total],
efftab[neffects - 1].olen - total);
- if (len != efftab[neffects - 1].olen - total || ofile->eof) {
- st_warn("Error writing: %s", ofile->st_errstr);
+ if (len != efftab[neffects - 1].olen - total || ofile->desc->eof) {
+ st_warn("Error writing: %s", ofile->desc->st_errstr);
return ST_EOF;
}
total += len;
} while (total < efftab[neffects-1].olen);
- output_samples += (total / ofile->signal.channels);
+ output_samples += (total / ofile->desc->signal.channels);
efftab[neffects-1].odone = efftab[neffects-1].olen = 0;
} else {
/* Make it look like everything was consumed */
output_samples += (efftab[neffects-1].olen /
- ofile->signal.channels);
+ ofile->desc->signal.channels);
efftab[neffects-1].odone = efftab[neffects-1].olen = 0;
}
@@ -1258,7 +1246,7 @@
* will cause stereo channels to be inversed.
*/
if ((efftab[e].olen - efftab[e].odone) >=
- ofile->signal.channels)
+ ofile->desc->signal.channels)
havedata = 1;
else
st_warn("Received buffer with incomplete amount of samples.");
@@ -1522,8 +1510,7 @@
/* Currently, for all sox modes, all input files must have
* the same sample rate. So we can always just use the rate
* of the first input file to compute time. */
- read_time = (double)read_samples / (double)file_desc[0]->signal.rate /
- (double)file_desc[0]->signal.channels;
+ read_time = (double)read_wide_samples / combiner.rate;
read_min = read_time / 60;
read_sec = (double)read_time - 60.0f * (double)read_min;
@@ -1544,14 +1531,13 @@
}
}
- if (input_samples) {
- in_time = (double)input_samples / (double)file_desc[0]->signal.rate /
- (double)file_desc[0]->signal.channels;
+ if (input_wide_samples) {
+ in_time = (double)input_wide_samples / combiner.rate;
left_time = in_time - read_time;
if (left_time < 0)
left_time = 0;
- completed = ((double)read_samples / (double)input_samples) * 100;
+ completed = (double)read_wide_samples / input_wide_samples * 100;
if (completed < 0)
completed = 0;
} else {
@@ -1569,12 +1555,12 @@
fprintf(stderr, "\rTime: %02i:%05.2f [%02i:%05.2f] of %02i:%05.2f (% 5.1f%%) Output Buffer:% 7.2f%c", read_min, read_sec, left_min, left_sec, in_min, in_sec, completed, out_size, unit);
}
-static void volumechange(st_sample_t * buf, st_ssize_t len, file_info_t fi)
+static void balance_input(st_sample_t * buf, st_ssize_t len, file_t f)
{
- if (fi->volume != 1)
+ if (f->volume != 1)
while (len--) {
- double d = fi->volume * *buf;
- *buf++ = ST_ROUND_CLIP_COUNT(d, fi->volume_clips);
+ double d = f->volume * *buf;
+ *buf++ = ST_ROUND_CLIP_COUNT(d, f->volume_clips);
}
}