annotate flattendynamics-ladspa.cpp @ 8:355fb9ea3888

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