1 /*
2 * Dynamic Range Compression Plugin for Audacious
3 * Copyright 2010-2014 John Lindgren
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions, and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions, and the following disclaimer in the documentation
13 * provided with the distribution.
14 *
15 * This software is provided "as is" and without any warranty, express or
16 * implied. In no event shall the authors be liable for any damages arising from
17 * the use of this software.
18 */
19
20 #include <math.h>
21 #include <stdint.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <libaudcore/i18n.h>
26 #include <libaudcore/plugin.h>
27 #include <libaudcore/preferences.h>
28 #include <libaudcore/ringbuf.h>
29 #include <libaudcore/runtime.h>
30
31 /* Response time adjustments. Maybe this should be adjustable? */
32 #define CHUNK_TIME 0.2f /* seconds */
33 #define CHUNKS 5
34 #define DECAY 0.3f
35
36 /* What is a "normal" volume? Replay Gain stuff claims to use 89 dB, but what
37 * does that translate to in our PCM range? */
38 static const char * const compressor_defaults[] = {
39 "center", "0.5",
40 "range", "0.5",
41 nullptr
42 };
43
44 static const PreferencesWidget compressor_widgets[] = {
45 WidgetLabel (N_("<b>Compression</b>")),
46 WidgetSpin (N_("Center volume:"),
47 WidgetFloat ("compressor", "center"),
48 {0.1, 1, 0.1}),
49 WidgetSpin (N_("Dynamic range:"),
50 WidgetFloat ("compressor", "range"),
51 {0.0, 3.0, 0.1})
52 };
53
54 static const PluginPreferences compressor_prefs = {{compressor_widgets}};
55
56 static const char compressor_about[] =
57 N_("Dynamic Range Compression Plugin for Audacious\n"
58 "Copyright 2010-2014 John Lindgren");
59
60 class Compressor : public EffectPlugin
61 {
62 public:
63 static constexpr PluginInfo info = {
64 N_("Dynamic Range Compressor"),
65 PACKAGE,
66 compressor_about,
67 & compressor_prefs
68 };
69
Compressor()70 constexpr Compressor () : EffectPlugin (info, 0, true) {}
71
72 bool init ();
73 void cleanup ();
74
75 void start (int & channels, int & rate);
76 Index<float> & process (Index<float> & data);
77 bool flush (bool force);
78 Index<float> & finish (Index<float> & data, bool end_of_playlist);
79 int adjust_delay (int delay);
80 };
81
82 EXPORT Compressor aud_plugin_instance;
83
84 /* The read pointer of the ring buffer is kept aligned to the chunk size at all
85 * times. To preserve the alignment, each read from the buffer must either (a)
86 * read a multiple of the chunk size or (b) empty the buffer completely. Writes
87 * to the buffer need not be aligned to the chunk size. */
88
89 static RingBuf<float> buffer, peaks;
90 static Index<float> output;
91 static int chunk_size;
92 static float current_peak;
93 static int current_channels, current_rate;
94
95 /* I used to find the maximum sample and take that as the peak, but that doesn't
96 * work well on badly clipped tracks. Now, I use the highly sophisticated
97 * method of averaging the absolute value of the samples and multiplying by 6, a
98 * number proved by experiment (on exactly three files) to best approximate the
99 * actual peak. */
100
calc_peak(float * data,int length)101 static float calc_peak (float * data, int length)
102 {
103 float sum = 0;
104
105 float * end = data + length;
106 while (data < end)
107 sum += fabsf (* data ++);
108
109 return aud::max (0.01f, sum / length * 6);
110 }
111
do_ramp(float * data,int length,float peak_a,float peak_b)112 static void do_ramp (float * data, int length, float peak_a, float peak_b)
113 {
114 float center = aud_get_double ("compressor", "center");
115 float range = aud_get_double ("compressor", "range");
116 float a = powf (peak_a / center, range - 1);
117 float b = powf (peak_b / center, range - 1);
118
119 for (int count = 0; count < length; count ++)
120 {
121 * data = (* data) * (a * (length - count) + b * count) / length;
122 data ++;
123 }
124 }
125
init()126 bool Compressor::init ()
127 {
128 aud_config_set_defaults ("compressor", compressor_defaults);
129 return true;
130 }
131
cleanup()132 void Compressor::cleanup ()
133 {
134 buffer.destroy ();
135 peaks.destroy ();
136 output.clear ();
137 }
138
start(int & channels,int & rate)139 void Compressor::start (int & channels, int & rate)
140 {
141 current_channels = channels;
142 current_rate = rate;
143
144 chunk_size = channels * (int) (rate * CHUNK_TIME);
145
146 buffer.alloc (chunk_size * CHUNKS);
147 peaks.alloc (CHUNKS);
148
149 flush (true);
150 }
151
process(Index<float> & data)152 Index<float> & Compressor::process (Index<float> & data)
153 {
154 output.resize (0);
155
156 int offset = 0;
157 int remain = data.len ();
158
159 while (1)
160 {
161 int writable = aud::min (remain, buffer.space ());
162
163 buffer.copy_in (& data[offset], writable);
164
165 offset += writable;
166 remain -= writable;
167
168 if (buffer.space ())
169 break;
170
171 while (peaks.len () < CHUNKS)
172 peaks.push (calc_peak (& buffer[chunk_size * peaks.len ()], chunk_size));
173
174 if (current_peak == 0.0f)
175 {
176 for (int i = 0; i < CHUNKS; i ++)
177 current_peak = aud::max (current_peak, peaks[i]);
178 }
179
180 float new_peak = aud::max (peaks[0], current_peak * (1.0f - DECAY));
181
182 for (int count = 1; count < CHUNKS; count ++)
183 new_peak = aud::max (new_peak, current_peak + (peaks[count] - current_peak) / count);
184
185 do_ramp (& buffer[0], chunk_size, current_peak, new_peak);
186
187 buffer.move_out (output, -1, chunk_size);
188
189 current_peak = new_peak;
190 peaks.pop ();
191 }
192
193 return output;
194 }
195
flush(bool force)196 bool Compressor::flush (bool force)
197 {
198 buffer.discard ();
199 peaks.discard ();
200
201 current_peak = 0.0f;
202 return true;
203 }
204
finish(Index<float> & data,bool end_of_playlist)205 Index<float> & Compressor::finish (Index<float> & data, bool end_of_playlist)
206 {
207 output.resize (0);
208
209 peaks.discard ();
210
211 while (buffer.len ())
212 {
213 int writable = buffer.linear ();
214
215 if (current_peak != 0.0f)
216 do_ramp (& buffer[0], writable, current_peak, current_peak);
217
218 buffer.move_out (output, -1, writable);
219 }
220
221 if (current_peak != 0.0f)
222 do_ramp (data.begin (), data.len (), current_peak, current_peak);
223
224 output.insert (data.begin (), -1, data.len ());
225
226 return output;
227 }
228
adjust_delay(int delay)229 int Compressor::adjust_delay (int delay)
230 {
231 return delay + aud::rescale<int64_t> (buffer.len () / current_channels, current_rate, 1000);
232 }
233