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@17
|
11 const float historySeconds = 4.f;
|
Chris@9
|
12 const float catchUpSeconds = 0.2f;
|
Chris@17
|
13 const float targetMaxRMS = 0.05f;
|
Chris@14
|
14 const float rmsMaxDecay = 0.999f; // per sample
|
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@15
|
58 ladspaInstantiate,
|
Chris@15
|
59 ladspaConnectPort,
|
Chris@15
|
60 ladspaActivate,
|
Chris@15
|
61 ladspaRun,
|
Chris@0
|
62 0, // Run adding
|
Chris@0
|
63 0, // Set run adding gain
|
Chris@15
|
64 ladspaDeactivate,
|
Chris@15
|
65 ladspaCleanup
|
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@17
|
84 m_sumOfSquares(0.f),
|
Chris@17
|
85 m_rms(0.f),
|
Chris@17
|
86 m_maxRms(0.f),
|
Chris@1
|
87 m_gain(1.f)
|
Chris@0
|
88 {
|
Chris@0
|
89 reset();
|
Chris@0
|
90 }
|
Chris@0
|
91
|
Chris@0
|
92 FlattenDynamics::~FlattenDynamics()
|
Chris@0
|
93 {
|
Chris@3
|
94 delete[] m_history;
|
Chris@0
|
95 }
|
Chris@0
|
96
|
Chris@0
|
97 LADSPA_Handle
|
Chris@15
|
98 FlattenDynamics::ladspaInstantiate(const LADSPA_Descriptor *, unsigned long rate)
|
Chris@0
|
99 {
|
Chris@0
|
100 FlattenDynamics *flatten = new FlattenDynamics(rate);
|
Chris@0
|
101 return flatten;
|
Chris@0
|
102 }
|
Chris@0
|
103
|
Chris@0
|
104 void
|
Chris@15
|
105 FlattenDynamics::ladspaConnectPort(LADSPA_Handle handle,
|
Chris@15
|
106 unsigned long port,
|
Chris@15
|
107 LADSPA_Data *location)
|
Chris@0
|
108 {
|
Chris@0
|
109 FlattenDynamics *flatten = (FlattenDynamics *)handle;
|
Chris@15
|
110 if (ports[port] & LADSPA_PORT_INPUT) {
|
Chris@15
|
111 flatten->connectInputPort(Port(port), location);
|
Chris@15
|
112 } else {
|
Chris@15
|
113 flatten->connectOutputPort(Port(port), location);
|
Chris@15
|
114 }
|
Chris@0
|
115 }
|
Chris@0
|
116
|
Chris@0
|
117 void
|
Chris@15
|
118 FlattenDynamics::ladspaActivate(LADSPA_Handle handle)
|
Chris@0
|
119 {
|
Chris@0
|
120 FlattenDynamics *flatten = (FlattenDynamics *)handle;
|
Chris@0
|
121 flatten->reset();
|
Chris@0
|
122 }
|
Chris@0
|
123
|
Chris@0
|
124 void
|
Chris@15
|
125 FlattenDynamics::ladspaRun(LADSPA_Handle handle, unsigned long samples)
|
Chris@0
|
126 {
|
Chris@0
|
127 FlattenDynamics *flatten = (FlattenDynamics *)handle;
|
Chris@15
|
128 flatten->process(samples);
|
Chris@0
|
129 }
|
Chris@0
|
130
|
Chris@0
|
131 void
|
Chris@15
|
132 FlattenDynamics::ladspaDeactivate(LADSPA_Handle handle)
|
Chris@0
|
133 {
|
Chris@15
|
134 ladspaActivate(handle); // both functions just reset the plugin
|
Chris@0
|
135 }
|
Chris@0
|
136
|
Chris@0
|
137 void
|
Chris@15
|
138 FlattenDynamics::ladspaCleanup(LADSPA_Handle handle)
|
Chris@0
|
139 {
|
Chris@0
|
140 delete (FlattenDynamics *)handle;
|
Chris@0
|
141 }
|
Chris@0
|
142
|
Chris@0
|
143 void
|
Chris@15
|
144 FlattenDynamics::connectInputPort(Port p, const float *location)
|
Chris@15
|
145 {
|
Chris@15
|
146 const float **ports[PortCount] = {
|
Chris@15
|
147 &m_input, 0, 0,
|
Chris@15
|
148 };
|
Chris@15
|
149
|
Chris@15
|
150 *ports[int(p)] = location;
|
Chris@15
|
151 }
|
Chris@15
|
152
|
Chris@15
|
153 void
|
Chris@15
|
154 FlattenDynamics::connectOutputPort(Port p, float *location)
|
Chris@15
|
155 {
|
Chris@15
|
156 float **ports[PortCount] = {
|
Chris@15
|
157 0, &m_output, &m_pgain,
|
Chris@15
|
158 };
|
Chris@15
|
159
|
Chris@15
|
160 *ports[int(p)] = location;
|
Chris@15
|
161 }
|
Chris@15
|
162
|
Chris@15
|
163 void
|
Chris@0
|
164 FlattenDynamics::reset()
|
Chris@0
|
165 {
|
Chris@3
|
166 delete[] m_history;
|
Chris@17
|
167 m_histlen = int(round(m_sampleRate * historySeconds));
|
Chris@2
|
168 if (m_histlen < 1) m_histlen = 1;
|
Chris@2
|
169 m_history = new float[m_histlen];
|
Chris@7
|
170 for (int i = 0; i < m_histlen; ++i) {
|
Chris@7
|
171 m_history[i] = 0.f;
|
Chris@7
|
172 }
|
Chris@1
|
173 m_histwrite = 0;
|
Chris@1
|
174 m_histread = 0;
|
Chris@1
|
175
|
Chris@17
|
176 m_sumOfSquares = 0.0;
|
Chris@17
|
177 m_rms = 0.f;
|
Chris@17
|
178 m_maxRms = 0.f;
|
Chris@1
|
179 m_gain = 1.f;
|
Chris@0
|
180 }
|
Chris@0
|
181
|
Chris@0
|
182 void
|
Chris@0
|
183 FlattenDynamics::updateParameters()
|
Chris@0
|
184 {
|
Chris@1
|
185 if (m_pgain) *m_pgain = m_gain;
|
Chris@0
|
186 }
|
Chris@0
|
187
|
Chris@0
|
188 void
|
Chris@15
|
189 FlattenDynamics::process(int sampleCount)
|
Chris@0
|
190 {
|
Chris@0
|
191 if (!m_input || !m_output) return;
|
Chris@16
|
192
|
Chris@0
|
193 updateParameters();
|
Chris@0
|
194
|
Chris@2
|
195 for (int i = 0; i < sampleCount; ++i) {
|
Chris@15
|
196 m_output[i] = processSingle(m_input[i]);
|
Chris@0
|
197 }
|
Chris@0
|
198 }
|
Chris@0
|
199
|
Chris@1
|
200 float
|
Chris@15
|
201 FlattenDynamics::processSingle(float f)
|
Chris@1
|
202 {
|
Chris@1
|
203 updateRMS(f);
|
Chris@2
|
204
|
Chris@17
|
205 if (m_rms == 0.f) {
|
Chris@2
|
206 return f;
|
Chris@2
|
207 }
|
Chris@2
|
208
|
Chris@17
|
209 if (m_rms >= m_maxRms) {
|
Chris@17
|
210 m_maxRms = m_rms;
|
Chris@17
|
211 } else {
|
Chris@17
|
212 m_maxRms = m_rms + (m_maxRms - m_rms) * rmsMaxDecay;
|
Chris@5
|
213 }
|
Chris@5
|
214
|
Chris@17
|
215 float targetGain = targetMaxRMS / m_maxRms;
|
Chris@8
|
216
|
Chris@3
|
217 if (targetGain > maxGain) {
|
Chris@3
|
218 targetGain = maxGain;
|
Chris@3
|
219 }
|
Chris@7
|
220
|
Chris@2
|
221 float catchUpSamples = catchUpSeconds * m_sampleRate;
|
Chris@2
|
222 // asymptotic, could improve?
|
Chris@2
|
223 m_gain = m_gain + (targetGain - m_gain) / catchUpSamples;
|
Chris@7
|
224
|
Chris@2
|
225 if (fabsf(f) * m_gain > 1.f) {
|
Chris@2
|
226 m_gain = 1.f / fabsf(f);
|
Chris@2
|
227 }
|
Chris@7
|
228
|
Chris@2
|
229 // cerr << "target gain = " << targetGain << ", gain = " << m_gain << endl;
|
Chris@2
|
230 return f * m_gain;
|
Chris@1
|
231 }
|
Chris@1
|
232
|
Chris@1
|
233 void
|
Chris@1
|
234 FlattenDynamics::updateRMS(float f)
|
Chris@1
|
235 {
|
Chris@11
|
236 // We update the RMS values by maintaining a sum-of-last-n-squares
|
Chris@11
|
237 // total (which the RMS is the square root of 1/n of) and
|
Chris@11
|
238 // recording the last n samples of history in a circular
|
Chris@11
|
239 // buffer. When a sample drops off the start of the history, we
|
Chris@11
|
240 // remove the square of it from the sum-of-squares total; then we
|
Chris@11
|
241 // add the square of the new sample.
|
Chris@11
|
242
|
Chris@1
|
243 int nextWrite = (m_histwrite + 1) % m_histlen;
|
Chris@1
|
244
|
Chris@17
|
245 float lose = 0.f;
|
Chris@1
|
246
|
Chris@1
|
247 if (nextWrite == m_histread) {
|
Chris@1
|
248 // full
|
Chris@17
|
249 lose = m_history[m_histread];
|
Chris@1
|
250 m_histread = (m_histread + 1) % m_histlen;
|
Chris@1
|
251 }
|
Chris@1
|
252
|
Chris@1
|
253 m_history[m_histwrite] = f;
|
Chris@1
|
254 m_histwrite = nextWrite;
|
Chris@1
|
255
|
Chris@2
|
256 int fill = (m_histwrite - m_histread + m_histlen) % m_histlen;
|
Chris@2
|
257
|
Chris@17
|
258 m_sumOfSquares -= lose * lose;
|
Chris@17
|
259 m_sumOfSquares += f * f;
|
Chris@1
|
260
|
Chris@17
|
261 m_rms = sqrt(m_sumOfSquares / fill);
|
Chris@2
|
262 // cerr << "rms = " << m_rms << " (from " << fill << " samples of " << m_histlen << ", latest " << f << ")" << endl;
|
Chris@1
|
263 }
|
Chris@1
|
264
|
Chris@0
|
265 const LADSPA_Descriptor *
|
Chris@0
|
266 ladspa_descriptor(unsigned long ix)
|
Chris@0
|
267 {
|
Chris@0
|
268 return FlattenDynamics::getDescriptor(ix);
|
Chris@0
|
269 }
|
Chris@0
|
270
|