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