1 /* libSoX Echo effect             August 24, 1998
2  *
3  * Copyright (C) 1998 Juergen Mueller And Sundry Contributors
4  * This source code is freely redistributable and may be used for
5  * any purpose.  This copyright notice must be maintained.
6  * Juergen Mueller And Sundry Contributors are not responsible for
7  * the consequences of using this software.
8  *
9  *
10  * Flow diagram scheme for n delays ( 1 <= n <= MAX_ECHOS ):
11  *
12  *        * gain-in                                              ___
13  * ibuff -----------+------------------------------------------>|   |
14  *                  |       _________                           |   |
15  *                  |      |         |                * decay 1 |   |
16  *                  +----->| delay 1 |------------------------->|   |
17  *                  |      |_________|                          |   |
18  *                  |            _________                      | + |
19  *                  |           |         |           * decay 2 |   |
20  *                  +---------->| delay 2 |-------------------->|   |
21  *                  |           |_________|                     |   |
22  *                  :                 _________                 |   |
23  *                  |                |         |      * decay n |   |
24  *                  +--------------->| delay n |--------------->|___|
25  *                                   |_________|                  |
26  *                                                                | * gain-out
27  *                                                                |
28  *                                                                +----->obuff
29  * Usage:
30  *   echo gain-in gain-out delay-1 decay-1 [delay-2 decay-2 ... delay-n decay-n]
31  *
32  * Where:
33  *   gain-in, decay-1 ... decay-n :  0.0 ... 1.0      volume
34  *   gain-out :  0.0 ...      volume
35  *   delay-1 ... delay-n :  > 0.0 msec
36  *
37  * Note:
38  *   when decay is close to 1.0, the samples can begin clipping and the output
39  *   can saturate!
40  *
41  * Hint:
42  *   1 / out-gain > gain-in ( 1 + decay-1 + ... + decay-n )
43  */
44 
45 #include "sox_i.h"
46 
47 #include <stdlib.h> /* Harmless, and prototypes atof() etc. --dgc */
48 
49 #define DELAY_BUFSIZ ( 50 * 50U * 1024 )
50 #define MAX_ECHOS 7     /* 24 bit x ( 1 + MAX_ECHOS ) = */
51                         /* 24 bit x 8 = 32 bit !!!      */
52 
53 /* Private data for SKEL file */
54 typedef struct {
55         int     counter;
56         int     num_delays;
57         double  *delay_buf;
58         float   in_gain, out_gain;
59         float   delay[MAX_ECHOS], decay[MAX_ECHOS];
60         ptrdiff_t samples[MAX_ECHOS], maxsamples;
61         size_t fade_out;
62 } priv_t;
63 
64 /* Private data for SKEL file */
65 
66 
67 /*
68  * Process options
69  */
sox_echo_getopts(sox_effect_t * effp,int argc,char ** argv)70 static int sox_echo_getopts(sox_effect_t * effp, int argc, char **argv)
71 {
72         priv_t * echo = (priv_t *) effp->priv;
73         int i;
74 
75   --argc, ++argv;
76         echo->num_delays = 0;
77 
78         if ((argc < 4) || (argc % 2))
79           return lsx_usage(effp);
80 
81         i = 0;
82         sscanf(argv[i++], "%f", &echo->in_gain);
83         sscanf(argv[i++], "%f", &echo->out_gain);
84         while (i < argc) {
85                 if ( echo->num_delays >= MAX_ECHOS )
86                         lsx_fail("echo: to many delays, use less than %i delays",
87                                 MAX_ECHOS);
88                 /* Linux bug and it's cleaner. */
89                 sscanf(argv[i++], "%f", &echo->delay[echo->num_delays]);
90                 sscanf(argv[i++], "%f", &echo->decay[echo->num_delays]);
91                 echo->num_delays++;
92         }
93         return (SOX_SUCCESS);
94 }
95 
96 /*
97  * Prepare for processing.
98  */
sox_echo_start(sox_effect_t * effp)99 static int sox_echo_start(sox_effect_t * effp)
100 {
101         priv_t * echo = (priv_t *) effp->priv;
102         int i;
103         float sum_in_volume;
104         long j;
105 
106         echo->maxsamples = 0;
107         if ( echo->in_gain < 0.0 )
108         {
109                 lsx_fail("echo: gain-in must be positive!");
110                 return (SOX_EOF);
111         }
112         if ( echo->in_gain > 1.0 )
113         {
114                 lsx_fail("echo: gain-in must be less than 1.0!");
115                 return (SOX_EOF);
116         }
117         if ( echo->out_gain < 0.0 )
118         {
119                 lsx_fail("echo: gain-in must be positive!");
120                 return (SOX_EOF);
121         }
122         for ( i = 0; i < echo->num_delays; i++ ) {
123                 echo->samples[i] = echo->delay[i] * effp->in_signal.rate / 1000.0;
124                 if ( echo->samples[i] < 1 )
125                 {
126                     lsx_fail("echo: delay must be positive!");
127                     return (SOX_EOF);
128                 }
129                 if ( echo->samples[i] > (ptrdiff_t)DELAY_BUFSIZ )
130                 {
131                         lsx_fail("echo: delay must be less than %g seconds!",
132                                 DELAY_BUFSIZ / effp->in_signal.rate );
133                         return (SOX_EOF);
134                 }
135                 if ( echo->decay[i] < 0.0 )
136                 {
137                     lsx_fail("echo: decay must be positive!" );
138                     return (SOX_EOF);
139                 }
140                 if ( echo->decay[i] > 1.0 )
141                 {
142                     lsx_fail("echo: decay must be less than 1.0!" );
143                     return (SOX_EOF);
144                 }
145                 if ( echo->samples[i] > echo->maxsamples )
146                         echo->maxsamples = echo->samples[i];
147         }
148         echo->delay_buf = lsx_malloc(sizeof (double) * echo->maxsamples);
149         for ( j = 0; j < echo->maxsamples; ++j )
150                 echo->delay_buf[j] = 0.0;
151         /* Be nice and check the hint with warning, if... */
152         sum_in_volume = 1.0;
153         for ( i = 0; i < echo->num_delays; i++ )
154                 sum_in_volume += echo->decay[i];
155         if ( sum_in_volume * echo->in_gain > 1.0 / echo->out_gain )
156                 lsx_warn("echo: warning >>> gain-out can cause saturation of output <<<");
157         echo->counter = 0;
158         echo->fade_out = echo->maxsamples;
159 
160   effp->out_signal.length = SOX_UNKNOWN_LEN; /* TODO: calculate actual length */
161 
162         return (SOX_SUCCESS);
163 }
164 
165 /*
166  * Processed signed long samples from ibuf to obuf.
167  * Return number of samples processed.
168  */
sox_echo_flow(sox_effect_t * effp,const sox_sample_t * ibuf,sox_sample_t * obuf,size_t * isamp,size_t * osamp)169 static int sox_echo_flow(sox_effect_t * effp, const sox_sample_t *ibuf, sox_sample_t *obuf,
170                  size_t *isamp, size_t *osamp)
171 {
172         priv_t * echo = (priv_t *) effp->priv;
173         int j;
174         double d_in, d_out;
175         sox_sample_t out;
176         size_t len = min(*isamp, *osamp);
177         *isamp = *osamp = len;
178 
179         while (len--) {
180                 /* Store delays as 24-bit signed longs */
181                 d_in = (double) *ibuf++ / 256;
182                 /* Compute output first */
183                 d_out = d_in * echo->in_gain;
184                 for ( j = 0; j < echo->num_delays; j++ ) {
185                         d_out += echo->delay_buf[
186 (echo->counter + echo->maxsamples - echo->samples[j]) % echo->maxsamples]
187                         * echo->decay[j];
188                 }
189                 /* Adjust the output volume and size to 24 bit */
190                 d_out = d_out * echo->out_gain;
191                 out = SOX_24BIT_CLIP_COUNT((sox_sample_t) d_out, effp->clips);
192                 *obuf++ = out * 256;
193                 /* Store input in delay buffer */
194                 echo->delay_buf[echo->counter] = d_in;
195                 /* Adjust the counter */
196                 echo->counter = ( echo->counter + 1 ) % echo->maxsamples;
197         }
198         /* processed all samples */
199         return (SOX_SUCCESS);
200 }
201 
202 /*
203  * Drain out reverb lines.
204  */
sox_echo_drain(sox_effect_t * effp,sox_sample_t * obuf,size_t * osamp)205 static int sox_echo_drain(sox_effect_t * effp, sox_sample_t *obuf, size_t *osamp)
206 {
207         priv_t * echo = (priv_t *) effp->priv;
208         double d_in, d_out;
209         sox_sample_t out;
210         int j;
211         size_t done;
212 
213         done = 0;
214         /* drain out delay samples */
215         while ( ( done < *osamp ) && ( done < echo->fade_out ) ) {
216                 d_in = 0;
217                 d_out = 0;
218                 for ( j = 0; j < echo->num_delays; j++ ) {
219                         d_out += echo->delay_buf[
220 (echo->counter + echo->maxsamples - echo->samples[j]) % echo->maxsamples]
221                         * echo->decay[j];
222                 }
223                 /* Adjust the output volume and size to 24 bit */
224                 d_out = d_out * echo->out_gain;
225                 out = SOX_24BIT_CLIP_COUNT((sox_sample_t) d_out, effp->clips);
226                 *obuf++ = out * 256;
227                 /* Store input in delay buffer */
228                 echo->delay_buf[echo->counter] = d_in;
229                 /* Adjust the counters */
230                 echo->counter = ( echo->counter + 1 ) % echo->maxsamples;
231                 done++;
232                 echo->fade_out--;
233         };
234         /* samples played, it remains */
235         *osamp = done;
236         if (echo->fade_out == 0)
237             return SOX_EOF;
238         else
239             return SOX_SUCCESS;
240 }
241 
sox_echo_stop(sox_effect_t * effp)242 static int sox_echo_stop(sox_effect_t * effp)
243 {
244         priv_t * echo = (priv_t *) effp->priv;
245 
246         free(echo->delay_buf);
247         echo->delay_buf = NULL;
248         return (SOX_SUCCESS);
249 }
250 
251 static sox_effect_handler_t sox_echo_effect = {
252   "echo",
253   "gain-in gain-out delay decay [ delay decay ... ]",
254   SOX_EFF_LENGTH | SOX_EFF_GAIN,
255   sox_echo_getopts,
256   sox_echo_start,
257   sox_echo_flow,
258   sox_echo_drain,
259   sox_echo_stop,
260   NULL, sizeof(priv_t)
261 };
262 
lsx_echo_effect_fn(void)263 const sox_effect_handler_t *lsx_echo_effect_fn(void)
264 {
265     return &sox_echo_effect;
266 }
267