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