ref: 5e8bb94ffccc7aae8af0bff65a8b00444f3665cf
parent: 5ddcaeaae436a8e9f326a30872fcd9aab07fadd9
author: Ulrich Klauer <ulrich@chirlu.de>
date: Sun Oct 14 17:34:14 EDT 2012
Extend time specification syntax Allow time specifications to be chained, adding/subtracting the specified amounts; e.g., trim =2:00-100s =2:00+100s will cut 200 samples centered around 2:00. This is especially useful when combining time-based and sample-based specifications, as well as for scripts that are given audio positions as parameters and need to add an offset to those. It is also possible for such a chained time specification to comprise more than two parts, as in `2:00-12+13s-1:23'. No immediate use case presents itself for that, but the code cost to implement this feature is negligible.
--- a/sox.1
+++ b/sox.1
@@ -1496,6 +1496,11 @@
Specifies the number of samples directly, as in `8000s'. For large sample
counts, \fIe notation\fR is supported: `1.7e6s' is the same as `1700000s'.
.PP
+Time specifications can also be chained with \fB+\fR or \fB\-\fR into a new
+time specification where the right part is added to or subtracted from the
+left, respectively: `3:00\-200s' means two hundred samples before the three
+minute mark.
+.SP
To see if SoX has support for an optional effect, enter
.B sox \-h
and look for its name under the list: `EFFECTS'.
--- a/src/effects_i.c
+++ b/src/effects_i.c
@@ -137,9 +137,12 @@
/*
* lsx_parsesamples
*
- * Parse a string for # of samples. If string ends with a 's' then
- * the string is interpreted as a user-calculated # of samples.
- * If string contains ':' or '.' but no 'e' or if it ends with a 't'
+ * Parse a string for # of samples. The input consists of one or more
+ * parts, with '+' or '-' between them indicating if the sample count
+ * should be added to or subtracted from the previous value.
+ * If a part ends with a 's' then it is interpreted as a
+ * user-calculated # of samples.
+ * If a part contains ':' or '.' but no 'e' or if it ends with a 't'
* then it is treated as an amount of time. This is converted into
* seconds and fraction of seconds, then the sample rate is used to
* calculate # of samples.
@@ -149,69 +152,85 @@
*/
char const * lsx_parsesamples(sox_rate_t rate, const char *str0, uint64_t *samples, int def)
{
- int i;
- sox_bool found_samples = sox_false, found_time = sox_false;
- char const * end;
- char const * pos;
- sox_bool found_colon, found_dot, found_e;
char * str = (char *)str0;
+ char combine = '+';
+ *samples = 0;
- for (;*str == ' '; ++str);
- for (end = str; *end && strchr("0123456789:.ets", *end); ++end);
- if (end == str)
- return NULL; /* error: empty input */
+ do {
+ uint64_t samples_part;
+ sox_bool found_samples = sox_false, found_time = sox_false;
+ char const * end;
+ char const * pos;
+ sox_bool found_colon, found_dot, found_e;
- pos = strchr(str, ':');
- found_colon = pos && pos < end;
+ for (;*str == ' '; ++str);
+ for (end = str; *end && strchr("0123456789:.ets", *end); ++end);
+ if (end == str)
+ return NULL; /* error: empty input */
- pos = strchr(str, '.');
- found_dot = pos && pos < end;
+ pos = strchr(str, ':');
+ found_colon = pos && pos < end;
- pos = strchr(str, 'e');
- found_e = pos && pos < end;
+ pos = strchr(str, '.');
+ found_dot = pos && pos < end;
- if (found_colon || (found_dot && !found_e) || *(end-1) == 't')
- found_time = sox_true;
- else if (*(end-1) == 's')
- found_samples = sox_true;
+ pos = strchr(str, 'e');
+ found_e = pos && pos < end;
- if (found_time || (def == 't' && !found_samples)) {
- if (found_e)
- return NULL; /* error: e notation in time */
+ if (found_colon || (found_dot && !found_e) || *(end-1) == 't')
+ found_time = sox_true;
+ else if (*(end-1) == 's')
+ found_samples = sox_true;
- for (*samples = 0, i = 0; *str != '.' && i < 3; ++i) {
- char * last_str = str;
- long part = strtol(str, &str, 10);
- if (!i && str == last_str)
- return NULL; /* error: empty first component */
- *samples += rate * part;
- if (i < 2) {
- if (*str != ':')
- break;
- ++str;
- *samples *= 60;
+ if (found_time || (def == 't' && !found_samples)) {
+ int i;
+ if (found_e)
+ return NULL; /* error: e notation in time */
+
+ for (samples_part = 0, i = 0; *str != '.' && i < 3; ++i) {
+ char * last_str = str;
+ long part = strtol(str, &str, 10);
+ if (!i && str == last_str)
+ return NULL; /* error: empty first component */
+ samples_part += rate * part;
+ if (i < 2) {
+ if (*str != ':')
+ break;
+ ++str;
+ samples_part *= 60;
+ }
}
- }
- if (*str == '.') {
+ if (*str == '.') {
+ char * last_str = str;
+ double part = strtod(str, &str);
+ if (str == last_str)
+ return NULL; /* error: empty fractional part */
+ samples_part += rate * part + .5;
+ }
+ if (*str == 't')
+ str++;
+ } else {
char * last_str = str;
double part = strtod(str, &str);
if (str == last_str)
- return NULL; /* error: empty fractional part */
- *samples += rate * part + .5;
+ return NULL; /* error: no sample count */
+ samples_part = part + .5;
+ if (*str == 's')
+ str++;
}
- if (*str == 't')
- str++;
- } else {
- char * last_str = str;
- double part = strtod(str, &str);
- if (str == last_str)
- return NULL; /* error: no sample count */
- *samples = part + .5;
- if (*str == 's')
- str++;
- }
- if (str != end)
- return NULL; /* error: trailing characters */
+ if (str != end)
+ return NULL; /* error: trailing characters */
+
+ switch (combine) {
+ case '+': *samples += samples_part; break;
+ case '-': *samples = samples_part <= *samples ?
+ *samples - samples_part : 0;
+ break;
+ }
+ if (strchr("+-", *str))
+ combine = *str++;
+ else combine = '\0';
+ } while (combine);
return str;
}
@@ -280,9 +299,16 @@
TEST("0.555555", 5556, 8)
assert(!lsx_parsesamples(10000, "x", &samples, 't'));
+
+ TEST("1:23+37", 1200000, 7)
+ TEST("12t+12s", 120012, 7)
+ TEST("1e6s-10", 900000, 7)
+ TEST("10-2:00", 0, 7)
+ TEST("123-45+12s+2:00-3e3s@foo", 1977012, 20)
+
return 0;
}
-#endif
+#endif
/* a note is given as an int,
* 0 => 440 Hz = A