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