annotate flattendynamics-ladspa.cpp @ 14:0e28664bc4fc

Comment only
author Chris Cannam
date Tue, 22 Jul 2014 15:40:45 +0100
parents 98fd2543a5f2
children 7bb35203a7bd
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@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