annotate flattendynamics-ladspa.cpp @ 15:7bb35203a7bd

Make object accessible as a standalone class as well as through LADSPA
author Chris Cannam
date Tue, 22 Jul 2014 16:11:02 +0100
parents 0e28664bc4fc
children c12cab43141d
rev   line source
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@15 59 ladspaInstantiate,
Chris@15 60 ladspaConnectPort,
Chris@15 61 ladspaActivate,
Chris@15 62 ladspaRun,
Chris@0 63 0, // Run adding
Chris@0 64 0, // Set run adding gain
Chris@15 65 ladspaDeactivate,
Chris@15 66 ladspaCleanup
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@15 102 FlattenDynamics::ladspaInstantiate(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@15 109 FlattenDynamics::ladspaConnectPort(LADSPA_Handle handle,
Chris@15 110 unsigned long port,
Chris@15 111 LADSPA_Data *location)
Chris@0 112 {
Chris@0 113 FlattenDynamics *flatten = (FlattenDynamics *)handle;
Chris@15 114 if (ports[port] & LADSPA_PORT_INPUT) {
Chris@15 115 flatten->connectInputPort(Port(port), location);
Chris@15 116 } else {
Chris@15 117 flatten->connectOutputPort(Port(port), location);
Chris@15 118 }
Chris@0 119 }
Chris@0 120
Chris@0 121 void
Chris@15 122 FlattenDynamics::ladspaActivate(LADSPA_Handle handle)
Chris@0 123 {
Chris@0 124 FlattenDynamics *flatten = (FlattenDynamics *)handle;
Chris@0 125 flatten->reset();
Chris@0 126 }
Chris@0 127
Chris@0 128 void
Chris@15 129 FlattenDynamics::ladspaRun(LADSPA_Handle handle, unsigned long samples)
Chris@0 130 {
Chris@0 131 FlattenDynamics *flatten = (FlattenDynamics *)handle;
Chris@15 132 flatten->process(samples);
Chris@0 133 }
Chris@0 134
Chris@0 135 void
Chris@15 136 FlattenDynamics::ladspaDeactivate(LADSPA_Handle handle)
Chris@0 137 {
Chris@15 138 ladspaActivate(handle); // both functions just reset the plugin
Chris@0 139 }
Chris@0 140
Chris@0 141 void
Chris@15 142 FlattenDynamics::ladspaCleanup(LADSPA_Handle handle)
Chris@0 143 {
Chris@0 144 delete (FlattenDynamics *)handle;
Chris@0 145 }
Chris@0 146
Chris@0 147 void
Chris@15 148 FlattenDynamics::connectInputPort(Port p, const float *location)
Chris@15 149 {
Chris@15 150 const float **ports[PortCount] = {
Chris@15 151 &m_input, 0, 0,
Chris@15 152 };
Chris@15 153
Chris@15 154 *ports[int(p)] = location;
Chris@15 155 }
Chris@15 156
Chris@15 157 void
Chris@15 158 FlattenDynamics::connectOutputPort(Port p, float *location)
Chris@15 159 {
Chris@15 160 float **ports[PortCount] = {
Chris@15 161 0, &m_output, &m_pgain,
Chris@15 162 };
Chris@15 163
Chris@15 164 *ports[int(p)] = location;
Chris@15 165 }
Chris@15 166
Chris@15 167 void
Chris@0 168 FlattenDynamics::reset()
Chris@0 169 {
Chris@3 170 delete[] m_history;
Chris@7 171 m_histlen = int(round(m_sampleRate * longTermSeconds));
Chris@2 172 if (m_histlen < 1) m_histlen = 1;
Chris@2 173 m_history = new float[m_histlen];
Chris@7 174 for (int i = 0; i < m_histlen; ++i) {
Chris@7 175 m_history[i] = 0.f;
Chris@7 176 }
Chris@1 177 m_histwrite = 0;
Chris@1 178 m_histread = 0;
Chris@1 179
Chris@7 180 m_sumOfSquaresLongTerm = 0.0;
Chris@7 181 m_sumOfSquaresShortTerm = 0.0;
Chris@7 182 m_rmsLongTerm = 0.f;
Chris@7 183 m_rmsShortTerm = 0.f;
Chris@7 184 m_maxRmsLongTerm = 0.f;
Chris@8 185 m_maxRmsShortTerm = 0.f;
Chris@1 186 m_gain = 1.f;
Chris@0 187 }
Chris@0 188
Chris@0 189 void
Chris@0 190 FlattenDynamics::updateParameters()
Chris@0 191 {
Chris@1 192 if (m_pgain) *m_pgain = m_gain;
Chris@0 193 }
Chris@0 194
Chris@0 195 void
Chris@15 196 FlattenDynamics::process(int sampleCount)
Chris@0 197 {
Chris@0 198 if (!m_input || !m_output) return;
Chris@0 199 updateParameters();
Chris@0 200
Chris@1 201 // Adjust gain so that
Chris@1 202 // * RMS level of the past N seconds is some fixed R, but
Chris@1 203 // * peak level does not clip
Chris@1 204 // We aim to take M seconds to move to our target gain
Chris@1 205
Chris@2 206 for (int i = 0; i < sampleCount; ++i) {
Chris@15 207 m_output[i] = processSingle(m_input[i]);
Chris@0 208 }
Chris@0 209 }
Chris@0 210
Chris@1 211 float
Chris@15 212 FlattenDynamics::processSingle(float f)
Chris@1 213 {
Chris@1 214 updateRMS(f);
Chris@2 215
Chris@7 216 if (m_rmsLongTerm == 0.f) {
Chris@2 217 return f;
Chris@2 218 }
Chris@2 219
Chris@10 220 if (m_rmsLongTerm >= m_maxRmsLongTerm) {
Chris@7 221 m_maxRmsLongTerm = m_rmsLongTerm;
Chris@11 222 } else if (m_rmsLongTerm < m_maxRmsLongTerm * rmsMaxDecay) {
Chris@11 223 m_maxRmsLongTerm *= rmsMaxDecay;
Chris@5 224 }
Chris@5 225
Chris@8 226 if (m_rmsShortTerm > m_maxRmsShortTerm) {
Chris@8 227 m_maxRmsShortTerm = m_rmsShortTerm;
Chris@8 228 }
Chris@8 229
Chris@9 230 float fixedGain = targetMaxRMS / m_maxRmsLongTerm;
Chris@8 231
Chris@9 232 float frac = m_rmsShortTerm / m_maxRmsShortTerm;
Chris@9 233
Chris@9 234 // push up toward top of 0,1 range
Chris@9 235 frac = pow(frac, 0.3);
Chris@9 236
Chris@9 237 float targetRMS = (frac * m_maxRmsShortTerm);
Chris@9 238
Chris@9 239 float targetGain = fixedGain * (targetRMS / m_rmsShortTerm);
Chris@8 240
Chris@3 241 if (targetGain > maxGain) {
Chris@3 242 targetGain = maxGain;
Chris@3 243 }
Chris@7 244
Chris@2 245 float catchUpSamples = catchUpSeconds * m_sampleRate;
Chris@2 246 // asymptotic, could improve?
Chris@2 247 m_gain = m_gain + (targetGain - m_gain) / catchUpSamples;
Chris@7 248
Chris@2 249 if (fabsf(f) * m_gain > 1.f) {
Chris@2 250 m_gain = 1.f / fabsf(f);
Chris@2 251 }
Chris@7 252
Chris@2 253 // cerr << "target gain = " << targetGain << ", gain = " << m_gain << endl;
Chris@2 254 return f * m_gain;
Chris@1 255 }
Chris@1 256
Chris@1 257 void
Chris@1 258 FlattenDynamics::updateRMS(float f)
Chris@1 259 {
Chris@11 260 // We update the RMS values by maintaining a sum-of-last-n-squares
Chris@11 261 // total (which the RMS is the square root of 1/n of) and
Chris@11 262 // recording the last n samples of history in a circular
Chris@11 263 // buffer. When a sample drops off the start of the history, we
Chris@11 264 // remove the square of it from the sum-of-squares total; then we
Chris@11 265 // add the square of the new sample.
Chris@11 266
Chris@1 267 int nextWrite = (m_histwrite + 1) % m_histlen;
Chris@1 268
Chris@7 269 float loseLongTerm = 0.f;
Chris@7 270 float loseShortTerm = 0.f;
Chris@1 271
Chris@1 272 if (nextWrite == m_histread) {
Chris@1 273 // full
Chris@7 274 loseLongTerm = m_history[m_histread];
Chris@1 275 m_histread = (m_histread + 1) % m_histlen;
Chris@1 276 }
Chris@1 277
Chris@7 278 int shortTermLength = round(shortTermSeconds * m_sampleRate);
Chris@7 279 int shortTermLoseIndex = nextWrite - shortTermLength;
Chris@7 280 if (shortTermLoseIndex < 0) {
Chris@7 281 shortTermLoseIndex += m_histlen;
Chris@7 282 }
Chris@7 283 // This depends on history being zero-initialised, to be correct at start:
Chris@7 284 loseShortTerm = m_history[shortTermLoseIndex];
Chris@7 285
Chris@1 286 m_history[m_histwrite] = f;
Chris@1 287 m_histwrite = nextWrite;
Chris@1 288
Chris@2 289 int fill = (m_histwrite - m_histread + m_histlen) % m_histlen;
Chris@2 290
Chris@7 291 m_sumOfSquaresLongTerm -= loseLongTerm * loseLongTerm;
Chris@7 292 m_sumOfSquaresLongTerm += f * f;
Chris@1 293
Chris@7 294 m_sumOfSquaresShortTerm -= loseShortTerm * loseShortTerm;
Chris@7 295 m_sumOfSquaresShortTerm += f * f;
Chris@7 296
Chris@7 297 m_rmsLongTerm = sqrt(m_sumOfSquaresLongTerm / fill);
Chris@7 298 m_rmsShortTerm = sqrt(m_sumOfSquaresShortTerm / shortTermLength);
Chris@2 299 // cerr << "rms = " << m_rms << " (from " << fill << " samples of " << m_histlen << ", latest " << f << ")" << endl;
Chris@1 300 }
Chris@1 301
Chris@0 302 const LADSPA_Descriptor *
Chris@0 303 ladspa_descriptor(unsigned long ix)
Chris@0 304 {
Chris@0 305 return FlattenDynamics::getDescriptor(ix);
Chris@0 306 }
Chris@0 307