1 /* libSoX effect: gain/norm/etc.   (c) 2008-9 robs@users.sourceforge.net
2  *
3  * This library is free software; you can redistribute it and/or modify it
4  * under the terms of the GNU Lesser General Public License as published by
5  * the Free Software Foundation; either version 2.1 of the License, or (at
6  * your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this library; if not, write to the Free Software Foundation,
15  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
16  */
17 
18 #define LSX_EFF_ALIAS
19 #include "sox_i.h"
20 #include <ctype.h>
21 #include <string.h>
22 
23 typedef struct {
24   sox_bool      do_equalise, do_balance, do_balance_no_clip, do_limiter;
25   sox_bool      do_restore, make_headroom, do_normalise, do_scan;
26   double        fixed_gain; /* Valid only in channel 0 */
27 
28   double        mult, reclaim, rms, limiter;
29   off_t         num_samples;
30   sox_sample_t  min, max;
31   FILE          * tmp_file;
32 } priv_t;
33 
create(sox_effect_t * effp,int argc,char ** argv)34 static int create(sox_effect_t * effp, int argc, char * * argv)
35 {
36   priv_t * p = (priv_t *)effp->priv;
37   char const * q;
38   for (--argc, ++argv; argc && **argv == '-' && argv[0][1] &&
39       !isdigit((unsigned char)argv[0][1]) && argv[0][1] != '.'; --argc, ++argv)
40     for (q = &argv[0][1]; *q; ++q) switch (*q) {
41       case 'n': p->do_scan = p->do_normalise = sox_true; break;
42       case 'e': p->do_scan = p->do_equalise = sox_true; break;
43       case 'B': p->do_scan = p->do_balance = sox_true; break;
44       case 'b': p->do_scan = p->do_balance_no_clip = sox_true; break;
45       case 'r': p->do_scan = p->do_restore = sox_true; break;
46       case 'h': p->make_headroom = sox_true; break;
47       case 'l': p->do_limiter = sox_true; break;
48       default: lsx_fail("invalid option `-%c'", *q); return lsx_usage(effp);
49     }
50   if ((p->do_equalise + p->do_balance + p->do_balance_no_clip + p->do_restore)/ sox_true > 1) {
51     lsx_fail("only one of -e, -B, -b, -r may be given");
52     return SOX_EOF;
53   }
54   if (p->do_normalise && p->do_restore) {
55     lsx_fail("only one of -n, -r may be given");
56     return SOX_EOF;
57   }
58   if (p->do_limiter && p->make_headroom) {
59     lsx_fail("only one of -l, -h may be given");
60     return SOX_EOF;
61   }
62   do {NUMERIC_PARAMETER(fixed_gain, -HUGE_VAL, HUGE_VAL)} while (0);
63   p->fixed_gain = dB_to_linear(p->fixed_gain);
64   return argc? lsx_usage(effp) : SOX_SUCCESS;
65 }
66 
start(sox_effect_t * effp)67 static int start(sox_effect_t * effp)
68 {
69   priv_t * p = (priv_t *)effp->priv;
70 
71   if (effp->flow == 0) {
72     if (p->do_restore) {
73       if (!effp->in_signal.mult || *effp->in_signal.mult >= 1) {
74         lsx_fail("can't reclaim headroom");
75         return SOX_EOF;
76       }
77       p->reclaim = 1 / *effp->in_signal.mult;
78     }
79     effp->out_signal.mult = p->make_headroom? &p->fixed_gain : NULL;
80     if (!p->do_equalise && !p->do_balance && !p->do_balance_no_clip)
81       effp->flows = 1; /* essentially a conditional SOX_EFF_MCHAN */
82   }
83   p->mult = 0;
84   p->max = 1;
85   p->min = -1;
86   if (p->do_scan) {
87     p->tmp_file = lsx_tmpfile();
88     if (p->tmp_file == NULL) {
89       lsx_fail("can't create temporary file: %s", strerror(errno));
90       return SOX_EOF;
91     }
92   }
93   if (p->do_limiter)
94     p->limiter = (1 - 1 / p->fixed_gain) * (1. / SOX_SAMPLE_MAX);
95   else if (p->fixed_gain == floor(p->fixed_gain) && !p->do_scan)
96     effp->out_signal.precision = effp->in_signal.precision;
97   return SOX_SUCCESS;
98 }
99 
flow(sox_effect_t * effp,const sox_sample_t * ibuf,sox_sample_t * obuf,size_t * isamp,size_t * osamp)100 static int flow(sox_effect_t * effp, const sox_sample_t * ibuf,
101     sox_sample_t * obuf, size_t * isamp, size_t * osamp)
102 {
103   priv_t * p = (priv_t *)effp->priv;
104   size_t len;
105 
106   if (p->do_scan) {
107     if (fwrite(ibuf, sizeof(*ibuf), *isamp, p->tmp_file) != *isamp) {
108       lsx_fail("error writing temporary file: %s", strerror(errno));
109       return SOX_EOF;
110     }
111     if (p->do_balance && !p->do_normalise)
112       for (len = *isamp; len; --len, ++ibuf) {
113         double d = SOX_SAMPLE_TO_FLOAT_64BIT(*ibuf, effp->clips);
114         p->rms += sqr(d);
115         ++p->num_samples;
116       }
117     else if (p->do_balance || p->do_balance_no_clip)
118       for (len = *isamp; len; --len, ++ibuf) {
119         double d = SOX_SAMPLE_TO_FLOAT_64BIT(*ibuf, effp->clips);
120         p->rms += sqr(d);
121         ++p->num_samples;
122         p->max = max(p->max, *ibuf);
123         p->min = min(p->min, *ibuf);
124       }
125     else for (len = *isamp; len; --len, ++ibuf) {
126       p->max = max(p->max, *ibuf);
127       p->min = min(p->min, *ibuf);
128     }
129     *osamp = 0; /* samples not output until drain */
130   }
131   else {
132     double mult = ((priv_t *)(effp - effp->flow)->priv)->fixed_gain;
133     len = *isamp = *osamp = min(*isamp, *osamp);
134     if (!p->do_limiter) for (; len; --len, ++ibuf)
135       *obuf++ = SOX_ROUND_CLIP_COUNT(*ibuf * mult, effp->clips);
136     else for (; len; --len, ++ibuf) {
137       double d = *ibuf * mult;
138       *obuf++ = d < 0 ? 1 / (1 / d - p->limiter) - .5 :
139                 d > 0 ? 1 / (1 / d + p->limiter) + .5 : 0;
140     }
141   }
142   return SOX_SUCCESS;
143 }
144 
start_drain(sox_effect_t * effp)145 static void start_drain(sox_effect_t * effp)
146 {
147   priv_t * p = (priv_t *)effp->priv;
148   double max = SOX_SAMPLE_MAX, max_peak = 0, max_rms = 0;
149   size_t i;
150 
151   if (p->do_balance || p->do_balance_no_clip) {
152     for (i = 0; i < effp->flows; ++i) {
153       priv_t * q = (priv_t *)(effp - effp->flow + i)->priv;
154       max_rms = max(max_rms, sqrt(q->rms / q->num_samples));
155       rewind(q->tmp_file);
156     }
157     for (i = 0; i < effp->flows; ++i) {
158       priv_t * q = (priv_t *)(effp - effp->flow + i)->priv;
159       double this_rms = sqrt(q->rms / q->num_samples);
160       double this_peak = max(q->max / max, q->min / (double)SOX_SAMPLE_MIN);
161       q->mult = this_rms != 0? max_rms / this_rms : 1;
162       max_peak = max(max_peak, q->mult * this_peak);
163       q->mult *= p->fixed_gain;
164     }
165     if (p->do_normalise || (p->do_balance_no_clip && max_peak > 1))
166       for (i = 0; i < effp->flows; ++i) {
167         priv_t * q = (priv_t *)(effp - effp->flow + i)->priv;
168         q->mult /= max_peak;
169       }
170   } else if (p->do_equalise && !p->do_normalise) {
171     for (i = 0; i < effp->flows; ++i) {
172       priv_t * q = (priv_t *)(effp - effp->flow + i)->priv;
173       double this_peak = max(q->max / max, q->min / (double)SOX_SAMPLE_MIN);
174       max_peak = max(max_peak, this_peak);
175       q->mult = p->fixed_gain / this_peak;
176       rewind(q->tmp_file);
177     }
178     for (i = 0; i < effp->flows; ++i) {
179       priv_t * q = (priv_t *)(effp - effp->flow + i)->priv;
180       q->mult *= max_peak;
181     }
182   } else {
183     p->mult = min(max / p->max, (double)SOX_SAMPLE_MIN / p->min);
184     if (p->do_restore) {
185       if (p->reclaim > p->mult)
186         lsx_report("%.3gdB not reclaimed", linear_to_dB(p->reclaim / p->mult));
187       else p->mult = p->reclaim;
188     }
189     p->mult *= p->fixed_gain;
190     rewind(p->tmp_file);
191   }
192 }
193 
drain(sox_effect_t * effp,sox_sample_t * obuf,size_t * osamp)194 static int drain(sox_effect_t * effp, sox_sample_t * obuf, size_t * osamp)
195 {
196   priv_t * p = (priv_t *)effp->priv;
197   size_t len;
198   int result = SOX_SUCCESS;
199 
200   *osamp -= *osamp % effp->in_signal.channels;
201 
202   if (p->do_scan) {
203     if (!p->mult)
204       start_drain(effp);
205     len = fread(obuf, sizeof(*obuf), *osamp, p->tmp_file);
206     if (len != *osamp && !feof(p->tmp_file)) {
207       lsx_fail("error reading temporary file: %s", strerror(errno));
208       result = SOX_EOF;
209     }
210     if (!p->do_limiter) for (*osamp = len; len; --len, ++obuf)
211       *obuf = SOX_ROUND_CLIP_COUNT(*obuf * p->mult, effp->clips);
212     else for (*osamp = len; len; --len) {
213       double d = *obuf * p->mult;
214       *obuf++ = d < 0 ? 1 / (1 / d - p->limiter) - .5 :
215                 d > 0 ? 1 / (1 / d + p->limiter) + .5 : 0;
216     }
217   }
218   else *osamp = 0;
219   return result;
220 }
221 
stop(sox_effect_t * effp)222 static int stop(sox_effect_t * effp)
223 {
224   priv_t * p = (priv_t *)effp->priv;
225   if (p->do_scan)
226     fclose(p->tmp_file); /* auto-deleted by lsx_tmpfile */
227   return SOX_SUCCESS;
228 }
229 
lsx_gain_effect_fn(void)230 sox_effect_handler_t const * lsx_gain_effect_fn(void)
231 {
232   static sox_effect_handler_t handler = {
233     "gain", NULL, SOX_EFF_GAIN,
234     create, start, flow, drain, stop, NULL, sizeof(priv_t)};
235   static char const * lines[] = {
236     "[-e|-b|-B|-r] [-n] [-l|-h] [gain-dB]",
237     "-e\t Equalise channels: peak to that with max peak;",
238     "-B\t Balance channels: rms to that with max rms; no clip protection",
239     "-b\t Balance channels: rms to that with max rms; clip protection",
240     "\t   Note -Bn = -bn",
241     "-r\t Reclaim headroom (as much as possible without clipping); see -h",
242     "-n\t Norm file to 0dBfs(output precision); gain-dB, if present, usually <0",
243     "-l\t Use simple limiter",
244     "-h\t Apply attenuation for headroom for subsequent effects; gain-dB, if",
245     "\t   present, is subject to reclaim by a subsequent gain -r",
246     "gain-dB\t Apply gain in dB",
247   };
248   static char * usage;
249   handler.usage = lsx_usage_lines(&usage, lines, array_length(lines));
250   return &handler;
251 }
252 
253 /*------------------ emulation of the old `normalise' effect -----------------*/
254 
norm_getopts(sox_effect_t * effp,int argc,char ** argv)255 static int norm_getopts(sox_effect_t * effp, int argc, char * * argv)
256 {
257   char * argv2[3];
258   int argc2 = 2;
259 
260   argv2[0] = argv[0], --argc, ++argv;
261   argv2[1] = "-n";
262   if (argc)
263     argv2[argc2++] = *argv, --argc, ++argv;
264   return argc? lsx_usage(effp) :
265     lsx_gain_effect_fn()->getopts(effp, argc2, argv2);
266 }
267 
lsx_norm_effect_fn(void)268 sox_effect_handler_t const * lsx_norm_effect_fn(void)
269 {
270   static sox_effect_handler_t handler;
271   handler = *lsx_gain_effect_fn();
272   handler.name = "norm";
273   handler.usage = "[level]";
274   handler.getopts = norm_getopts;
275   return &handler;
276 }
277