Chris@0
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@0
|
2
|
Chris@0
|
3 #include "flattendynamics-ladspa.h"
|
Chris@0
|
4
|
Chris@0
|
5 #include <iostream>
|
Chris@0
|
6 #include <cmath>
|
Chris@0
|
7
|
Chris@2
|
8 using std::cerr;
|
Chris@2
|
9 using std::endl;
|
Chris@2
|
10
|
Chris@7
|
11 const float longTermSeconds = 4.f;
|
Chris@9
|
12 const float shortTermSeconds = 1.f;
|
Chris@9
|
13 const float catchUpSeconds = 0.2f;
|
Chris@13
|
14 const float targetMaxRMS = 0.04f;
|
Chris@14
|
15 const float rmsMaxDecay = 0.999f; // per sample
|
Chris@3
|
16 const float maxGain = 20.f;
|
Chris@1
|
17
|
Chris@0
|
18 const char *const
|
Chris@0
|
19 FlattenDynamics::portNames[PortCount] =
|
Chris@0
|
20 {
|
Chris@0
|
21 "Input",
|
Chris@0
|
22 "Output",
|
Chris@1
|
23 "Gain",
|
Chris@0
|
24 };
|
Chris@0
|
25
|
Chris@0
|
26 const LADSPA_PortDescriptor
|
Chris@0
|
27 FlattenDynamics::ports[PortCount] =
|
Chris@0
|
28 {
|
Chris@0
|
29 LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO,
|
Chris@0
|
30 LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO,
|
Chris@1
|
31 LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL,
|
Chris@0
|
32 };
|
Chris@0
|
33
|
Chris@0
|
34 const LADSPA_PortRangeHint
|
Chris@0
|
35 FlattenDynamics::hints[PortCount] =
|
Chris@0
|
36 {
|
Chris@0
|
37 { 0, 0, 0 },
|
Chris@0
|
38 { 0, 0, 0 },
|
Chris@1
|
39 { 0, 0, 0 },
|
Chris@0
|
40 };
|
Chris@0
|
41
|
Chris@0
|
42 const LADSPA_Properties
|
Chris@0
|
43 FlattenDynamics::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE;
|
Chris@0
|
44
|
Chris@0
|
45 const LADSPA_Descriptor
|
Chris@0
|
46 FlattenDynamics::ladspaDescriptor =
|
Chris@0
|
47 {
|
Chris@0
|
48 0xf0b375, // "Unique" ID
|
Chris@0
|
49 "flattendynamics", // Label
|
Chris@0
|
50 properties,
|
Chris@2
|
51 "Flatten Dynamics", // Name
|
Chris@0
|
52 "Queen Mary, University of London", // Maker
|
Chris@0
|
53 "BSD", // Copyright
|
Chris@0
|
54 PortCount,
|
Chris@0
|
55 ports,
|
Chris@0
|
56 portNames,
|
Chris@0
|
57 hints,
|
Chris@0
|
58 0, // Implementation data
|
Chris@0
|
59 instantiate,
|
Chris@0
|
60 connectPort,
|
Chris@0
|
61 activate,
|
Chris@0
|
62 run,
|
Chris@0
|
63 0, // Run adding
|
Chris@0
|
64 0, // Set run adding gain
|
Chris@0
|
65 deactivate,
|
Chris@0
|
66 cleanup
|
Chris@0
|
67 };
|
Chris@0
|
68
|
Chris@0
|
69 const LADSPA_Descriptor *
|
Chris@0
|
70 FlattenDynamics::getDescriptor(unsigned long index)
|
Chris@0
|
71 {
|
Chris@0
|
72 if (index == 0) return &ladspaDescriptor;
|
Chris@0
|
73 return 0;
|
Chris@0
|
74 }
|
Chris@0
|
75
|
Chris@0
|
76 FlattenDynamics::FlattenDynamics(int sampleRate) :
|
Chris@0
|
77 m_sampleRate(sampleRate),
|
Chris@0
|
78 m_input(0),
|
Chris@1
|
79 m_output(0),
|
Chris@1
|
80 m_pgain(0),
|
Chris@1
|
81 m_history(0),
|
Chris@1
|
82 m_histlen(0),
|
Chris@1
|
83 m_histwrite(0),
|
Chris@1
|
84 m_histread(0),
|
Chris@7
|
85 m_sumOfSquaresLongTerm(0.f),
|
Chris@7
|
86 m_sumOfSquaresShortTerm(0.f),
|
Chris@7
|
87 m_rmsLongTerm(0.f),
|
Chris@7
|
88 m_rmsShortTerm(0.f),
|
Chris@7
|
89 m_maxRmsLongTerm(0.f),
|
Chris@8
|
90 m_maxRmsShortTerm(0.f),
|
Chris@1
|
91 m_gain(1.f)
|
Chris@0
|
92 {
|
Chris@0
|
93 reset();
|
Chris@0
|
94 }
|
Chris@0
|
95
|
Chris@0
|
96 FlattenDynamics::~FlattenDynamics()
|
Chris@0
|
97 {
|
Chris@3
|
98 delete[] m_history;
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@0
|
101 LADSPA_Handle
|
Chris@0
|
102 FlattenDynamics::instantiate(const LADSPA_Descriptor *, unsigned long rate)
|
Chris@0
|
103 {
|
Chris@0
|
104 FlattenDynamics *flatten = new FlattenDynamics(rate);
|
Chris@0
|
105 return flatten;
|
Chris@0
|
106 }
|
Chris@0
|
107
|
Chris@0
|
108 void
|
Chris@0
|
109 FlattenDynamics::connectPort(LADSPA_Handle handle,
|
Chris@2
|
110 unsigned long port, LADSPA_Data *location)
|
Chris@0
|
111 {
|
Chris@0
|
112 FlattenDynamics *flatten = (FlattenDynamics *)handle;
|
Chris@0
|
113
|
Chris@0
|
114 float **ports[PortCount] = {
|
Chris@0
|
115 &flatten->m_input,
|
Chris@0
|
116 &flatten->m_output,
|
Chris@1
|
117 &flatten->m_pgain,
|
Chris@0
|
118 };
|
Chris@0
|
119
|
Chris@0
|
120 *ports[port] = (float *)location;
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 void
|
Chris@0
|
124 FlattenDynamics::activate(LADSPA_Handle handle)
|
Chris@0
|
125 {
|
Chris@0
|
126 FlattenDynamics *flatten = (FlattenDynamics *)handle;
|
Chris@0
|
127 flatten->reset();
|
Chris@0
|
128 }
|
Chris@0
|
129
|
Chris@0
|
130 void
|
Chris@0
|
131 FlattenDynamics::run(LADSPA_Handle handle, unsigned long samples)
|
Chris@0
|
132 {
|
Chris@0
|
133 FlattenDynamics *flatten = (FlattenDynamics *)handle;
|
Chris@0
|
134 flatten->runImpl(samples);
|
Chris@0
|
135 }
|
Chris@0
|
136
|
Chris@0
|
137 void
|
Chris@0
|
138 FlattenDynamics::deactivate(LADSPA_Handle handle)
|
Chris@0
|
139 {
|
Chris@0
|
140 activate(handle); // both functions just reset the plugin
|
Chris@0
|
141 }
|
Chris@0
|
142
|
Chris@0
|
143 void
|
Chris@0
|
144 FlattenDynamics::cleanup(LADSPA_Handle handle)
|
Chris@0
|
145 {
|
Chris@0
|
146 delete (FlattenDynamics *)handle;
|
Chris@0
|
147 }
|
Chris@0
|
148
|
Chris@0
|
149 void
|
Chris@0
|
150 FlattenDynamics::reset()
|
Chris@0
|
151 {
|
Chris@3
|
152 delete[] m_history;
|
Chris@7
|
153 m_histlen = int(round(m_sampleRate * longTermSeconds));
|
Chris@2
|
154 if (m_histlen < 1) m_histlen = 1;
|
Chris@2
|
155 m_history = new float[m_histlen];
|
Chris@7
|
156 for (int i = 0; i < m_histlen; ++i) {
|
Chris@7
|
157 m_history[i] = 0.f;
|
Chris@7
|
158 }
|
Chris@1
|
159 m_histwrite = 0;
|
Chris@1
|
160 m_histread = 0;
|
Chris@1
|
161
|
Chris@7
|
162 m_sumOfSquaresLongTerm = 0.0;
|
Chris@7
|
163 m_sumOfSquaresShortTerm = 0.0;
|
Chris@7
|
164 m_rmsLongTerm = 0.f;
|
Chris@7
|
165 m_rmsShortTerm = 0.f;
|
Chris@7
|
166 m_maxRmsLongTerm = 0.f;
|
Chris@8
|
167 m_maxRmsShortTerm = 0.f;
|
Chris@1
|
168 m_gain = 1.f;
|
Chris@0
|
169 }
|
Chris@0
|
170
|
Chris@0
|
171 void
|
Chris@0
|
172 FlattenDynamics::updateParameters()
|
Chris@0
|
173 {
|
Chris@1
|
174 if (m_pgain) *m_pgain = m_gain;
|
Chris@0
|
175 }
|
Chris@0
|
176
|
Chris@0
|
177 void
|
Chris@0
|
178 FlattenDynamics::runImpl(unsigned long sampleCount)
|
Chris@0
|
179 {
|
Chris@0
|
180 if (!m_input || !m_output) return;
|
Chris@0
|
181 updateParameters();
|
Chris@0
|
182
|
Chris@1
|
183 // Adjust gain so that
|
Chris@1
|
184 // * RMS level of the past N seconds is some fixed R, but
|
Chris@1
|
185 // * peak level does not clip
|
Chris@1
|
186 // We aim to take M seconds to move to our target gain
|
Chris@1
|
187
|
Chris@2
|
188 for (int i = 0; i < sampleCount; ++i) {
|
Chris@1
|
189 m_output[i] = process(m_input[i]);
|
Chris@0
|
190 }
|
Chris@0
|
191 }
|
Chris@0
|
192
|
Chris@1
|
193 float
|
Chris@1
|
194 FlattenDynamics::process(float f)
|
Chris@1
|
195 {
|
Chris@1
|
196 updateRMS(f);
|
Chris@2
|
197
|
Chris@7
|
198 if (m_rmsLongTerm == 0.f) {
|
Chris@2
|
199 return f;
|
Chris@2
|
200 }
|
Chris@2
|
201
|
Chris@10
|
202 if (m_rmsLongTerm >= m_maxRmsLongTerm) {
|
Chris@7
|
203 m_maxRmsLongTerm = m_rmsLongTerm;
|
Chris@11
|
204 } else if (m_rmsLongTerm < m_maxRmsLongTerm * rmsMaxDecay) {
|
Chris@11
|
205 m_maxRmsLongTerm *= rmsMaxDecay;
|
Chris@5
|
206 }
|
Chris@5
|
207
|
Chris@8
|
208 if (m_rmsShortTerm > m_maxRmsShortTerm) {
|
Chris@8
|
209 m_maxRmsShortTerm = m_rmsShortTerm;
|
Chris@8
|
210 }
|
Chris@8
|
211
|
Chris@9
|
212 float fixedGain = targetMaxRMS / m_maxRmsLongTerm;
|
Chris@8
|
213
|
Chris@9
|
214 float frac = m_rmsShortTerm / m_maxRmsShortTerm;
|
Chris@9
|
215
|
Chris@9
|
216 // push up toward top of 0,1 range
|
Chris@9
|
217 frac = pow(frac, 0.3);
|
Chris@9
|
218
|
Chris@9
|
219 float targetRMS = (frac * m_maxRmsShortTerm);
|
Chris@9
|
220
|
Chris@9
|
221 float targetGain = fixedGain * (targetRMS / m_rmsShortTerm);
|
Chris@8
|
222
|
Chris@3
|
223 if (targetGain > maxGain) {
|
Chris@3
|
224 targetGain = maxGain;
|
Chris@3
|
225 }
|
Chris@7
|
226
|
Chris@2
|
227 float catchUpSamples = catchUpSeconds * m_sampleRate;
|
Chris@2
|
228 // asymptotic, could improve?
|
Chris@2
|
229 m_gain = m_gain + (targetGain - m_gain) / catchUpSamples;
|
Chris@7
|
230
|
Chris@2
|
231 if (fabsf(f) * m_gain > 1.f) {
|
Chris@2
|
232 m_gain = 1.f / fabsf(f);
|
Chris@2
|
233 }
|
Chris@7
|
234
|
Chris@2
|
235 // cerr << "target gain = " << targetGain << ", gain = " << m_gain << endl;
|
Chris@2
|
236 return f * m_gain;
|
Chris@1
|
237 }
|
Chris@1
|
238
|
Chris@1
|
239 void
|
Chris@1
|
240 FlattenDynamics::updateRMS(float f)
|
Chris@1
|
241 {
|
Chris@11
|
242 // We update the RMS values by maintaining a sum-of-last-n-squares
|
Chris@11
|
243 // total (which the RMS is the square root of 1/n of) and
|
Chris@11
|
244 // recording the last n samples of history in a circular
|
Chris@11
|
245 // buffer. When a sample drops off the start of the history, we
|
Chris@11
|
246 // remove the square of it from the sum-of-squares total; then we
|
Chris@11
|
247 // add the square of the new sample.
|
Chris@11
|
248
|
Chris@1
|
249 int nextWrite = (m_histwrite + 1) % m_histlen;
|
Chris@1
|
250
|
Chris@7
|
251 float loseLongTerm = 0.f;
|
Chris@7
|
252 float loseShortTerm = 0.f;
|
Chris@1
|
253
|
Chris@1
|
254 if (nextWrite == m_histread) {
|
Chris@1
|
255 // full
|
Chris@7
|
256 loseLongTerm = m_history[m_histread];
|
Chris@1
|
257 m_histread = (m_histread + 1) % m_histlen;
|
Chris@1
|
258 }
|
Chris@1
|
259
|
Chris@7
|
260 int shortTermLength = round(shortTermSeconds * m_sampleRate);
|
Chris@7
|
261 int shortTermLoseIndex = nextWrite - shortTermLength;
|
Chris@7
|
262 if (shortTermLoseIndex < 0) {
|
Chris@7
|
263 shortTermLoseIndex += m_histlen;
|
Chris@7
|
264 }
|
Chris@7
|
265 // This depends on history being zero-initialised, to be correct at start:
|
Chris@7
|
266 loseShortTerm = m_history[shortTermLoseIndex];
|
Chris@7
|
267
|
Chris@1
|
268 m_history[m_histwrite] = f;
|
Chris@1
|
269 m_histwrite = nextWrite;
|
Chris@1
|
270
|
Chris@2
|
271 int fill = (m_histwrite - m_histread + m_histlen) % m_histlen;
|
Chris@2
|
272
|
Chris@7
|
273 m_sumOfSquaresLongTerm -= loseLongTerm * loseLongTerm;
|
Chris@7
|
274 m_sumOfSquaresLongTerm += f * f;
|
Chris@1
|
275
|
Chris@7
|
276 m_sumOfSquaresShortTerm -= loseShortTerm * loseShortTerm;
|
Chris@7
|
277 m_sumOfSquaresShortTerm += f * f;
|
Chris@7
|
278
|
Chris@7
|
279 m_rmsLongTerm = sqrt(m_sumOfSquaresLongTerm / fill);
|
Chris@7
|
280 m_rmsShortTerm = sqrt(m_sumOfSquaresShortTerm / shortTermLength);
|
Chris@2
|
281 // cerr << "rms = " << m_rms << " (from " << fill << " samples of " << m_histlen << ", latest " << f << ")" << endl;
|
Chris@1
|
282 }
|
Chris@1
|
283
|
Chris@0
|
284 const LADSPA_Descriptor *
|
Chris@0
|
285 ladspa_descriptor(unsigned long ix)
|
Chris@0
|
286 {
|
Chris@0
|
287 return FlattenDynamics::getDescriptor(ix);
|
Chris@0
|
288 }
|
Chris@0
|
289
|