summaryrefslogtreecommitdiff
path: root/apps/compressor.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/compressor.c')
-rw-r--r--apps/compressor.c363
1 files changed, 363 insertions, 0 deletions
diff --git a/apps/compressor.c b/apps/compressor.c
new file mode 100644
index 0000000000..3a8d52e4da
--- /dev/null
+++ b/apps/compressor.c
@@ -0,0 +1,363 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2009 Jeffrey Goode
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "config.h"
22#include "fixedpoint.h"
23#include "fracmul.h"
24#include "settings.h"
25#include "dsp.h"
26#include "compressor.h"
27
28/* Define LOGF_ENABLE to enable logf output in this file */
29/*#define LOGF_ENABLE*/
30#include "logf.h"
31
32static int32_t comp_rel_slope IBSS_ATTR; /* S7.24 format */
33static int32_t comp_makeup_gain IBSS_ATTR; /* S7.24 format */
34static int32_t comp_curve[66] IBSS_ATTR; /* S7.24 format */
35static int32_t release_gain IBSS_ATTR; /* S7.24 format */
36
37#define UNITY (1L << 24) /* unity gain in S7.24 format */
38
39/** COMPRESSOR UPDATE
40 * Called via the menu system to configure the compressor process */
41bool compressor_update(void)
42{
43 static int curr_set[5];
44 int new_set[5] = {
45 global_settings.compressor_threshold,
46 global_settings.compressor_makeup_gain,
47 global_settings.compressor_ratio,
48 global_settings.compressor_knee,
49 global_settings.compressor_release_time};
50
51 /* make menu values useful */
52 int threshold = new_set[0];
53 bool auto_gain = (new_set[1] == 1);
54 const int comp_ratios[] = {2, 4, 6, 10, 0};
55 int ratio = comp_ratios[new_set[2]];
56 bool soft_knee = (new_set[3] == 1);
57 int release = new_set[4] * NATIVE_FREQUENCY / 1000;
58
59 bool changed = false;
60 bool active = (threshold < 0);
61
62 for (int i = 0; i < 5; i++)
63 {
64 if (curr_set[i] != new_set[i])
65 {
66 changed = true;
67 curr_set[i] = new_set[i];
68
69#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE)
70 switch (i)
71 {
72 case 0:
73 logf(" Compressor Threshold: %d dB\tEnabled: %s",
74 threshold, active ? "Yes" : "No");
75 break;
76 case 1:
77 logf(" Compressor Makeup Gain: %s",
78 auto_gain ? "Auto" : "Off");
79 break;
80 case 2:
81 if (ratio)
82 { logf(" Compressor Ratio: %d:1", ratio); }
83 else
84 { logf(" Compressor Ratio: Limit"); }
85 break;
86 case 3:
87 logf(" Compressor Knee: %s", soft_knee?"Soft":"Hard");
88 break;
89 case 4:
90 logf(" Compressor Release: %d", release);
91 break;
92 }
93#endif
94 }
95 }
96
97 if (changed && active)
98 {
99 /* configure variables for compressor operation */
100 static const int32_t db[] = {
101 /* positive db equivalents in S15.16 format */
102 0x000000, 0x241FA4, 0x1E1A5E, 0x1A94C8,
103 0x181518, 0x1624EA, 0x148F82, 0x1338BD,
104 0x120FD2, 0x1109EB, 0x101FA4, 0x0F4BB6,
105 0x0E8A3C, 0x0DD840, 0x0D3377, 0x0C9A0E,
106 0x0C0A8C, 0x0B83BE, 0x0B04A5, 0x0A8C6C,
107 0x0A1A5E, 0x09ADE1, 0x094670, 0x08E398,
108 0x0884F6, 0x082A30, 0x07D2FA, 0x077F0F,
109 0x072E31, 0x06E02A, 0x0694C8, 0x064BDF,
110 0x060546, 0x05C0DA, 0x057E78, 0x053E03,
111 0x04FF5F, 0x04C273, 0x048726, 0x044D64,
112 0x041518, 0x03DE30, 0x03A89B, 0x037448,
113 0x03412A, 0x030F32, 0x02DE52, 0x02AE80,
114 0x027FB0, 0x0251D6, 0x0224EA, 0x01F8E2,
115 0x01CDB4, 0x01A359, 0x0179C9, 0x0150FC,
116 0x0128EB, 0x010190, 0x00DAE4, 0x00B4E1,
117 0x008F82, 0x006AC1, 0x004699, 0x002305};
118
119 struct curve_point
120 {
121 int32_t db; /* S15.16 format */
122 int32_t offset; /* S15.16 format */
123 } db_curve[5];
124
125 /** Set up the shape of the compression curve first as decibel
126 values */
127 /* db_curve[0] = bottom of knee
128 [1] = threshold
129 [2] = top of knee
130 [3] = 0 db input
131 [4] = ~+12db input (2 bits clipping overhead) */
132
133 db_curve[1].db = threshold << 16;
134 if (soft_knee)
135 {
136 /* bottom of knee is 3dB below the threshold for soft knee*/
137 db_curve[0].db = db_curve[1].db - (3 << 16);
138 /* top of knee is 3dB above the threshold for soft knee */
139 db_curve[2].db = db_curve[1].db + (3 << 16);
140 if (ratio)
141 /* offset = -3db * (ratio - 1) / ratio */
142 db_curve[2].offset = (int32_t)((long long)(-3 << 16)
143 * (ratio - 1) / ratio);
144 else
145 /* offset = -3db for hard limit */
146 db_curve[2].offset = (-3 << 16);
147 }
148 else
149 {
150 /* bottom of knee is at the threshold for hard knee */
151 db_curve[0].db = threshold << 16;
152 /* top of knee is at the threshold for hard knee */
153 db_curve[2].db = threshold << 16;
154 db_curve[2].offset = 0;
155 }
156
157 /* Calculate 0db and ~+12db offsets */
158 db_curve[4].db = 0xC0A8C; /* db of 2 bits clipping */
159 if (ratio)
160 {
161 /* offset = threshold * (ratio - 1) / ratio */
162 db_curve[3].offset = (int32_t)((long long)(threshold << 16)
163 * (ratio - 1) / ratio);
164 db_curve[4].offset = (int32_t)((long long)-db_curve[4].db
165 * (ratio - 1) / ratio) + db_curve[3].offset;
166 }
167 else
168 {
169 /* offset = threshold for hard limit */
170 db_curve[3].offset = (threshold << 16);
171 db_curve[4].offset = -db_curve[4].db + db_curve[3].offset;
172 }
173
174 /** Now set up the comp_curve table with compression offsets in the
175 form of gain factors in S7.24 format */
176 /* comp_curve[0] is 0 (-infinity db) input */
177 comp_curve[0] = UNITY;
178 /* comp_curve[1 to 63] are intermediate compression values
179 corresponding to the 6 MSB of the input values of a non-clipped
180 signal */
181 for (int i = 1; i < 64; i++)
182 {
183 /* db constants are stored as positive numbers;
184 make them negative here */
185 int32_t this_db = -db[i];
186
187 /* no compression below the knee */
188 if (this_db <= db_curve[0].db)
189 comp_curve[i] = UNITY;
190
191 /* if soft knee and below top of knee,
192 interpolate along soft knee slope */
193 else if (soft_knee && (this_db <= db_curve[2].db))
194 comp_curve[i] = fp_factor(fp_mul(
195 ((this_db - db_curve[0].db) / 6),
196 db_curve[2].offset, 16), 16) << 8;
197
198 /* interpolate along ratio slope above the knee */
199 else
200 comp_curve[i] = fp_factor(fp_mul(
201 fp_div((db_curve[1].db - this_db), db_curve[1].db, 16),
202 db_curve[3].offset, 16), 16) << 8;
203 }
204 /* comp_curve[64] is the compression level of a maximum level,
205 non-clipped signal */
206 comp_curve[64] = fp_factor(db_curve[3].offset, 16) << 8;
207
208 /* comp_curve[65] is the compression level of a maximum level,
209 clipped signal */
210 comp_curve[65] = fp_factor(db_curve[4].offset, 16) << 8;
211
212#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE)
213 logf("\n *** Compression Offsets ***");
214 /* some settings for display only, not used in calculations */
215 db_curve[0].offset = 0;
216 db_curve[1].offset = 0;
217 db_curve[3].db = 0;
218
219 for (int i = 0; i <= 4; i++)
220 {
221 logf("Curve[%d]: db: % 6.2f\toffset: % 6.2f", i,
222 (float)db_curve[i].db / (1 << 16),
223 (float)db_curve[i].offset / (1 << 16));
224 }
225
226 logf("\nGain factors:");
227 for (int i = 1; i <= 65; i++)
228 {
229 debugf("%02d: %.6f ", i, (float)comp_curve[i] / UNITY);
230 if (i % 4 == 0) debugf("\n");
231 }
232 debugf("\n");
233#endif
234
235 /* if using auto peak, then makeup gain is max offset -
236 .1dB headroom */
237 comp_makeup_gain = auto_gain ?
238 fp_factor(-(db_curve[3].offset) - 0x199A, 16) << 8 : UNITY;
239 logf("Makeup gain:\t%.6f", (float)comp_makeup_gain / UNITY);
240
241 /* calculate per-sample gain change a rate of 10db over release time
242 */
243 comp_rel_slope = 0xAF0BB2 / release;
244 logf("Release slope:\t%.6f", (float)comp_rel_slope / UNITY);
245
246 release_gain = UNITY;
247 }
248
249 return active;
250}
251
252/** GET COMPRESSION GAIN
253 * Returns the required gain factor in S7.24 format in order to compress the
254 * sample in accordance with the compression curve. Always 1 or less.
255 */
256static inline int32_t get_compression_gain(struct dsp_data *data,
257 int32_t sample)
258{
259 const int frac_bits_offset = data->frac_bits - 15;
260
261 /* sample must be positive */
262 if (sample < 0)
263 sample = -(sample + 1);
264
265 /* shift sample into 15 frac bit range */
266 if (frac_bits_offset > 0)
267 sample >>= frac_bits_offset;
268 if (frac_bits_offset < 0)
269 sample <<= -frac_bits_offset;
270
271 /* normal case: sample isn't clipped */
272 if (sample < (1 << 15))
273 {
274 /* index is 6 MSB, rem is 9 LSB */
275 int index = sample >> 9;
276 int32_t rem = (sample & 0x1FF) << 22;
277
278 /* interpolate from the compression curve:
279 higher gain - ((rem / (1 << 31)) * (higher gain - lower gain)) */
280 return comp_curve[index] - (FRACMUL(rem,
281 (comp_curve[index] - comp_curve[index + 1])));
282 }
283 /* sample is somewhat clipped, up to 2 bits of overhead */
284 if (sample < (1 << 17))
285 {
286 /* straight interpolation:
287 higher gain - ((clipped portion of sample * 4/3
288 / (1 << 31)) * (higher gain - lower gain)) */
289 return comp_curve[64] - (FRACMUL(((sample - (1 << 15)) / 3) << 16,
290 (comp_curve[64] - comp_curve[65])));
291 }
292
293 /* sample is too clipped, return invalid value */
294 return -1;
295}
296
297/** COMPRESSOR PROCESS
298 * Changes the gain of the samples according to the compressor curve
299 */
300void compressor_process(int count, struct dsp_data *data, int32_t *buf[])
301{
302 const int num_chan = data->num_channels;
303 int32_t *in_buf[2] = {buf[0], buf[1]};
304
305 while (count-- > 0)
306 {
307 int ch;
308 /* use lowest (most compressed) gain factor of the output buffer
309 sample pair for both samples (mono is also handled correctly here)
310 */
311 int32_t sample_gain = UNITY;
312 for (ch = 0; ch < num_chan; ch++)
313 {
314 int32_t this_gain = get_compression_gain(data, *in_buf[ch]);
315 if (this_gain < sample_gain)
316 sample_gain = this_gain;
317 }
318
319 /* perform release slope; skip if no compression and no release slope
320 */
321 if ((sample_gain != UNITY) || (release_gain != UNITY))
322 {
323 /* if larger offset than previous slope, start new release slope
324 */
325 if ((sample_gain <= release_gain) && (sample_gain > 0))
326 {
327 release_gain = sample_gain;
328 }
329 else
330 /* keep sloping towards unity gain (and ignore invalid value) */
331 {
332 release_gain += comp_rel_slope;
333 if (release_gain > UNITY)
334 {
335 release_gain = UNITY;
336 }
337 }
338 }
339
340 /* total gain factor is the product of release gain and makeup gain,
341 but avoid computation if possible */
342 int32_t total_gain = ((release_gain == UNITY) ? comp_makeup_gain :
343 (comp_makeup_gain == UNITY) ? release_gain :
344 FRACMUL_SHL(release_gain, comp_makeup_gain, 7));
345
346 /* Implement the compressor: apply total gain factor (if any) to the
347 output buffer sample pair/mono sample */
348 if (total_gain != UNITY)
349 {
350 for (ch = 0; ch < num_chan; ch++)
351 {
352 *in_buf[ch] = FRACMUL_SHL(total_gain, *in_buf[ch], 7);
353 }
354 }
355 in_buf[0]++;
356 in_buf[1]++;
357 }
358}
359
360void compressor_reset(void)
361{
362 release_gain = UNITY;
363}