1 /* libSoX effect: trim - cut portions out of the audio
2  *
3  * First version written 01/2012 by Ulrich Klauer.
4  * Replaces an older trim effect originally written by Curt Zirzow in 2000.
5  *
6  * Copyright 2012 Chris Bagwell and SoX Contributors
7  *
8  * This library is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or (at
11  * your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this library; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #include "sox_i.h"
24 
25 typedef struct {
26   /* parameters */
27   unsigned int num_pos;
28   struct {
29     uint64_t sample; /* NB: wide samples */
30     char *argstr;
31   } *pos;
32   /* state */
33   unsigned int current_pos;
34   uint64_t samples_read; /* NB: wide samples */
35   sox_bool copying;
36 } priv_t;
37 
parse(sox_effect_t * effp,int argc,char ** argv)38 static int parse(sox_effect_t *effp, int argc, char **argv)
39 {
40   priv_t *p = (priv_t*) effp->priv;
41   unsigned int i;
42   --argc, ++argv;
43   p->num_pos = argc;
44   lsx_Calloc(p->pos, p->num_pos);
45   for (i = 0; i < p->num_pos; i++) {
46     const char *arg = argv[i];
47     p->pos[i].argstr = lsx_strdup(arg);
48     /* dummy parse to check for syntax errors */
49     arg = lsx_parseposition(0., arg, NULL, (uint64_t)0, (uint64_t)0, '+');
50     if (!arg || *arg) {
51       lsx_fail("Error parsing position %u", i+1);
52       return lsx_usage(effp);
53     }
54   }
55   return SOX_SUCCESS;
56 }
57 
start(sox_effect_t * effp)58 static int start(sox_effect_t *effp)
59 {
60   priv_t *p = (priv_t*) effp->priv;
61   uint64_t in_length = effp->in_signal.length != SOX_UNKNOWN_LEN ?
62     effp->in_signal.length / effp->in_signal.channels : SOX_UNKNOWN_LEN;
63   uint64_t last_seen = 0;
64   sox_bool open_end;
65   unsigned int i;
66 
67   p->copying = sox_false;
68 
69   /* calculate absolute positions */
70   for (i = 0; i < p->num_pos; i++) {
71     if (!lsx_parseposition(effp->in_signal.rate, p->pos[i].argstr, &p->pos[i].sample, last_seen, in_length, '+')) {
72       lsx_fail("Position %u is relative to end of audio, but audio length is unknown", i+1);
73       return SOX_EOF;
74     }
75     last_seen = p->pos[i].sample;
76     lsx_debug_more("position %u at %" PRIu64, i+1, last_seen);
77   }
78 
79   /* sanity checks */
80   last_seen = 0;
81   for (i = 0; i < p->num_pos; i++) {
82     if (p->pos[i].sample < last_seen) {
83       lsx_fail("Position %u is behind the following position.", i);
84       return SOX_EOF;
85     }
86     last_seen = p->pos[i].sample;
87   }
88   if (p->num_pos && in_length != SOX_UNKNOWN_LEN)
89     if (p->pos[0].sample > in_length ||
90         p->pos[p->num_pos-1].sample > in_length)
91       lsx_warn("%s position is after expected end of audio.",
92           p->pos[0].sample > in_length ? "Start" : "End");
93 
94   /* avoid unnecessary work */
95   if (in_length == SOX_UNKNOWN_LEN)
96     while (p->num_pos && p->pos[p->num_pos-1].sample == SOX_UNKNOWN_LEN) {
97       lsx_debug_more("removing `-0' position");
98       p->num_pos--;
99       free(p->pos[p->num_pos].argstr);
100     }
101   if (p->num_pos == 1 && !p->pos[0].sample)
102     return SOX_EFF_NULL;
103 
104   /* calculate output length */
105   open_end = p->num_pos % 2;
106   if (open_end && in_length == SOX_UNKNOWN_LEN)
107     effp->out_signal.length = SOX_UNKNOWN_LEN;
108   else {
109     effp->out_signal.length = 0;
110     for (i = 0; i+1 < p->num_pos ; i += 2)
111       effp->out_signal.length +=
112         min(p->pos[i+1].sample, in_length) - min(p->pos[i].sample, in_length);
113     if (open_end)
114       effp->out_signal.length +=
115         in_length - min(p->pos[p->num_pos-1].sample, in_length);
116     effp->out_signal.length *= effp->in_signal.channels;
117   }
118 
119   return SOX_SUCCESS;
120 }
121 
flow(sox_effect_t * effp,const sox_sample_t * ibuf,sox_sample_t * obuf,size_t * isamp,size_t * osamp)122 static int flow(sox_effect_t *effp, const sox_sample_t *ibuf,
123     sox_sample_t *obuf, size_t *isamp, size_t *osamp)
124 {
125   priv_t *p = (priv_t*) effp->priv;
126   size_t len = min(*isamp, *osamp);
127   size_t channels = effp->in_signal.channels;
128   len /= channels;
129   *isamp = *osamp = 0;
130 
131   while (len) {
132     size_t chunk;
133 
134     if (p->current_pos < p->num_pos &&
135         p->samples_read == p->pos[p->current_pos].sample) {
136       p->copying = !p->copying;
137       p->current_pos++;
138     }
139 
140     if (p->current_pos >= p->num_pos && !p->copying)
141       return SOX_EOF;
142 
143     chunk = p->current_pos < p->num_pos ?
144       min(len, p->pos[p->current_pos].sample - p->samples_read) : len;
145     if (p->copying) {
146       memcpy(obuf, ibuf, chunk * channels * sizeof(*obuf));
147       obuf += chunk * channels, *osamp += chunk * channels;
148     }
149     ibuf += chunk * channels; *isamp += chunk * channels;
150     p->samples_read += chunk, len -= chunk;
151   }
152 
153   return SOX_SUCCESS;
154 }
155 
drain(sox_effect_t * effp,sox_sample_t * obuf UNUSED,size_t * osamp)156 static int drain(sox_effect_t *effp, sox_sample_t *obuf UNUSED, size_t *osamp)
157 {
158   priv_t *p = (priv_t*) effp->priv;
159   *osamp = 0; /* only checking for errors */
160 
161   if (p->current_pos + 1 == p->num_pos &&
162       p->pos[p->current_pos].sample == p->samples_read &&
163       p->copying) /* would stop here anyway */
164     p->current_pos++;
165   if (p->current_pos < p->num_pos)
166     lsx_warn("Last %u position(s) not reached%s.",
167       p->num_pos - p->current_pos,
168       (effp->in_signal.length == SOX_UNKNOWN_LEN ||
169        effp->in_signal.length/effp->in_signal.channels == p->samples_read) ?
170       "" /* unknown length, or did already warn during start() */ :
171       " (audio shorter than expected)"
172       );
173   return SOX_EOF;
174 }
175 
lsx_kill(sox_effect_t * effp)176 static int lsx_kill(sox_effect_t *effp)
177 {
178   unsigned int i;
179   priv_t *p = (priv_t*) effp->priv;
180   for (i = 0; i < p->num_pos; i++)
181     free(p->pos[i].argstr);
182   free(p->pos);
183   return SOX_SUCCESS;
184 }
185 
lsx_trim_effect_fn(void)186 sox_effect_handler_t const *lsx_trim_effect_fn(void)
187 {
188   static sox_effect_handler_t handler = {
189     "trim", "{position}",
190     SOX_EFF_MCHAN | SOX_EFF_LENGTH | SOX_EFF_MODIFY,
191     parse, start, flow, drain, NULL, lsx_kill,
192     sizeof(priv_t)
193   };
194   return &handler;
195 }
196 
197 /* The following functions allow a libSoX client to do a speed
198  * optimization, by asking for the number of samples to be skipped
199  * at the beginning of the audio with sox_trim_get_start(), skipping
200  * that many samples in an efficient way such as seeking within the
201  * input file, then telling us it has been done by calling
202  * sox_trim_clear_start() (the name is historical).
203  * Note that sox_trim_get_start() returns the number of non-wide
204  * samples. */
205 
sox_trim_get_start(sox_effect_t * effp)206 sox_uint64_t sox_trim_get_start(sox_effect_t *effp)
207 {
208     priv_t *p = (priv_t*) effp->priv;
209     return p->num_pos ? p->pos[0].sample * effp->in_signal.channels : 0;
210 }
211 
sox_trim_clear_start(sox_effect_t * effp)212 void sox_trim_clear_start(sox_effect_t *effp)
213 {
214     priv_t *p = (priv_t*) effp->priv;
215     p->samples_read = p->num_pos ? p->pos[0].sample : 0;
216 }
217