annotate flattendynamics-ladspa.cpp @ 10:46ed42184699

Some decay on the long-term-only version
author Chris Cannam
date Tue, 22 Jul 2014 13:28:05 +0100
parents 9853fe9c7820
children e662e661ae65
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@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@10 201 if (m_rmsLongTerm >= m_maxRmsLongTerm) {
Chris@7 202 m_maxRmsLongTerm = m_rmsLongTerm;
Chris@10 203 } else {
Chris@10 204 float decay = 0.999;
Chris@10 205 m_maxRmsLongTerm = m_rmsLongTerm + (m_maxRmsLongTerm - m_rmsLongTerm) * decay;
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@10 214 float targetGain = fixedGain;
Chris@10 215 /*
Chris@9 216 float frac = m_rmsShortTerm / m_maxRmsShortTerm;
Chris@9 217
Chris@9 218 // push up toward top of 0,1 range
Chris@9 219 frac = pow(frac, 0.3);
Chris@9 220
Chris@9 221 float targetRMS = (frac * m_maxRmsShortTerm);
Chris@9 222
Chris@9 223 float targetGain = fixedGain * (targetRMS / m_rmsShortTerm);
Chris@8 224
Chris@8 225 */
Chris@8 226
Chris@3 227 if (targetGain > maxGain) {
Chris@3 228 targetGain = maxGain;
Chris@3 229 }
Chris@7 230
Chris@2 231 float catchUpSamples = catchUpSeconds * m_sampleRate;
Chris@2 232 // asymptotic, could improve?
Chris@2 233 m_gain = m_gain + (targetGain - m_gain) / catchUpSamples;
Chris@7 234
Chris@2 235 if (fabsf(f) * m_gain > 1.f) {
Chris@2 236 m_gain = 1.f / fabsf(f);
Chris@2 237 }
Chris@7 238
Chris@2 239 // cerr << "target gain = " << targetGain << ", gain = " << m_gain << endl;
Chris@2 240 return f * m_gain;
Chris@1 241 }
Chris@1 242
Chris@1 243 void
Chris@1 244 FlattenDynamics::updateRMS(float f)
Chris@1 245 {
Chris@1 246 int nextWrite = (m_histwrite + 1) % m_histlen;
Chris@1 247
Chris@7 248 float loseLongTerm = 0.f;
Chris@7 249 float loseShortTerm = 0.f;
Chris@1 250
Chris@1 251 if (nextWrite == m_histread) {
Chris@1 252 // full
Chris@7 253 loseLongTerm = m_history[m_histread];
Chris@1 254 m_histread = (m_histread + 1) % m_histlen;
Chris@1 255 }
Chris@1 256
Chris@7 257 int shortTermLength = round(shortTermSeconds * m_sampleRate);
Chris@7 258 int shortTermLoseIndex = nextWrite - shortTermLength;
Chris@7 259 if (shortTermLoseIndex < 0) {
Chris@7 260 shortTermLoseIndex += m_histlen;
Chris@7 261 }
Chris@7 262 // This depends on history being zero-initialised, to be correct at start:
Chris@7 263 loseShortTerm = m_history[shortTermLoseIndex];
Chris@7 264
Chris@1 265 m_history[m_histwrite] = f;
Chris@1 266 m_histwrite = nextWrite;
Chris@1 267
Chris@2 268 int fill = (m_histwrite - m_histread + m_histlen) % m_histlen;
Chris@2 269
Chris@7 270 m_sumOfSquaresLongTerm -= loseLongTerm * loseLongTerm;
Chris@7 271 m_sumOfSquaresLongTerm += f * f;
Chris@1 272
Chris@7 273 m_sumOfSquaresShortTerm -= loseShortTerm * loseShortTerm;
Chris@7 274 m_sumOfSquaresShortTerm += f * f;
Chris@7 275
Chris@7 276 m_rmsLongTerm = sqrt(m_sumOfSquaresLongTerm / fill);
Chris@7 277 m_rmsShortTerm = sqrt(m_sumOfSquaresShortTerm / shortTermLength);
Chris@2 278 // cerr << "rms = " << m_rms << " (from " << fill << " samples of " << m_histlen << ", latest " << f << ")" << endl;
Chris@1 279 }
Chris@1 280
Chris@0 281 const LADSPA_Descriptor *
Chris@0 282 ladspa_descriptor(unsigned long ix)
Chris@0 283 {
Chris@0 284 return FlattenDynamics::getDescriptor(ix);
Chris@0 285 }
Chris@0 286