Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: #include "flattendynamics-ladspa.h" Chris@0: Chris@0: #include Chris@0: #include Chris@0: Chris@2: using std::cerr; Chris@2: using std::endl; Chris@2: Chris@7: const float longTermSeconds = 4.f; Chris@9: const float shortTermSeconds = 1.f; Chris@9: const float catchUpSeconds = 0.2f; Chris@13: const float targetMaxRMS = 0.04f; Chris@14: const float rmsMaxDecay = 0.999f; // per sample Chris@3: const float maxGain = 20.f; Chris@1: Chris@0: const char *const Chris@0: FlattenDynamics::portNames[PortCount] = Chris@0: { Chris@0: "Input", Chris@0: "Output", Chris@1: "Gain", Chris@0: }; Chris@0: Chris@0: const LADSPA_PortDescriptor Chris@0: FlattenDynamics::ports[PortCount] = Chris@0: { Chris@0: LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, Chris@0: LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, Chris@1: LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, Chris@0: }; Chris@0: Chris@0: const LADSPA_PortRangeHint Chris@0: FlattenDynamics::hints[PortCount] = Chris@0: { Chris@0: { 0, 0, 0 }, Chris@0: { 0, 0, 0 }, Chris@1: { 0, 0, 0 }, Chris@0: }; Chris@0: Chris@0: const LADSPA_Properties Chris@0: FlattenDynamics::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; Chris@0: Chris@0: const LADSPA_Descriptor Chris@0: FlattenDynamics::ladspaDescriptor = Chris@0: { Chris@0: 0xf0b375, // "Unique" ID Chris@0: "flattendynamics", // Label Chris@0: properties, Chris@2: "Flatten Dynamics", // Name Chris@0: "Queen Mary, University of London", // Maker Chris@0: "BSD", // Copyright Chris@0: PortCount, Chris@0: ports, Chris@0: portNames, Chris@0: hints, Chris@0: 0, // Implementation data Chris@0: instantiate, Chris@0: connectPort, Chris@0: activate, Chris@0: run, Chris@0: 0, // Run adding Chris@0: 0, // Set run adding gain Chris@0: deactivate, Chris@0: cleanup Chris@0: }; Chris@0: Chris@0: const LADSPA_Descriptor * Chris@0: FlattenDynamics::getDescriptor(unsigned long index) Chris@0: { Chris@0: if (index == 0) return &ladspaDescriptor; Chris@0: return 0; Chris@0: } Chris@0: Chris@0: FlattenDynamics::FlattenDynamics(int sampleRate) : Chris@0: m_sampleRate(sampleRate), Chris@0: m_input(0), Chris@1: m_output(0), Chris@1: m_pgain(0), Chris@1: m_history(0), Chris@1: m_histlen(0), Chris@1: m_histwrite(0), Chris@1: m_histread(0), Chris@7: m_sumOfSquaresLongTerm(0.f), Chris@7: m_sumOfSquaresShortTerm(0.f), Chris@7: m_rmsLongTerm(0.f), Chris@7: m_rmsShortTerm(0.f), Chris@7: m_maxRmsLongTerm(0.f), Chris@8: m_maxRmsShortTerm(0.f), Chris@1: m_gain(1.f) Chris@0: { Chris@0: reset(); Chris@0: } Chris@0: Chris@0: FlattenDynamics::~FlattenDynamics() Chris@0: { Chris@3: delete[] m_history; Chris@0: } Chris@0: Chris@0: LADSPA_Handle Chris@0: FlattenDynamics::instantiate(const LADSPA_Descriptor *, unsigned long rate) Chris@0: { Chris@0: FlattenDynamics *flatten = new FlattenDynamics(rate); Chris@0: return flatten; Chris@0: } Chris@0: Chris@0: void Chris@0: FlattenDynamics::connectPort(LADSPA_Handle handle, Chris@2: unsigned long port, LADSPA_Data *location) Chris@0: { Chris@0: FlattenDynamics *flatten = (FlattenDynamics *)handle; Chris@0: Chris@0: float **ports[PortCount] = { Chris@0: &flatten->m_input, Chris@0: &flatten->m_output, Chris@1: &flatten->m_pgain, Chris@0: }; Chris@0: Chris@0: *ports[port] = (float *)location; Chris@0: } Chris@0: Chris@0: void Chris@0: FlattenDynamics::activate(LADSPA_Handle handle) Chris@0: { Chris@0: FlattenDynamics *flatten = (FlattenDynamics *)handle; Chris@0: flatten->reset(); Chris@0: } Chris@0: Chris@0: void Chris@0: FlattenDynamics::run(LADSPA_Handle handle, unsigned long samples) Chris@0: { Chris@0: FlattenDynamics *flatten = (FlattenDynamics *)handle; Chris@0: flatten->runImpl(samples); Chris@0: } Chris@0: Chris@0: void Chris@0: FlattenDynamics::deactivate(LADSPA_Handle handle) Chris@0: { Chris@0: activate(handle); // both functions just reset the plugin Chris@0: } Chris@0: Chris@0: void Chris@0: FlattenDynamics::cleanup(LADSPA_Handle handle) Chris@0: { Chris@0: delete (FlattenDynamics *)handle; Chris@0: } Chris@0: Chris@0: void Chris@0: FlattenDynamics::reset() Chris@0: { Chris@3: delete[] m_history; Chris@7: m_histlen = int(round(m_sampleRate * longTermSeconds)); Chris@2: if (m_histlen < 1) m_histlen = 1; Chris@2: m_history = new float[m_histlen]; Chris@7: for (int i = 0; i < m_histlen; ++i) { Chris@7: m_history[i] = 0.f; Chris@7: } Chris@1: m_histwrite = 0; Chris@1: m_histread = 0; Chris@1: Chris@7: m_sumOfSquaresLongTerm = 0.0; Chris@7: m_sumOfSquaresShortTerm = 0.0; Chris@7: m_rmsLongTerm = 0.f; Chris@7: m_rmsShortTerm = 0.f; Chris@7: m_maxRmsLongTerm = 0.f; Chris@8: m_maxRmsShortTerm = 0.f; Chris@1: m_gain = 1.f; Chris@0: } Chris@0: Chris@0: void Chris@0: FlattenDynamics::updateParameters() Chris@0: { Chris@1: if (m_pgain) *m_pgain = m_gain; Chris@0: } Chris@0: Chris@0: void Chris@0: FlattenDynamics::runImpl(unsigned long sampleCount) Chris@0: { Chris@0: if (!m_input || !m_output) return; Chris@0: updateParameters(); Chris@0: Chris@1: // Adjust gain so that Chris@1: // * RMS level of the past N seconds is some fixed R, but Chris@1: // * peak level does not clip Chris@1: // We aim to take M seconds to move to our target gain Chris@1: Chris@2: for (int i = 0; i < sampleCount; ++i) { Chris@1: m_output[i] = process(m_input[i]); Chris@0: } Chris@0: } Chris@0: Chris@1: float Chris@1: FlattenDynamics::process(float f) Chris@1: { Chris@1: updateRMS(f); Chris@2: Chris@7: if (m_rmsLongTerm == 0.f) { Chris@2: return f; Chris@2: } Chris@2: Chris@10: if (m_rmsLongTerm >= m_maxRmsLongTerm) { Chris@7: m_maxRmsLongTerm = m_rmsLongTerm; Chris@11: } else if (m_rmsLongTerm < m_maxRmsLongTerm * rmsMaxDecay) { Chris@11: m_maxRmsLongTerm *= rmsMaxDecay; Chris@5: } Chris@5: Chris@8: if (m_rmsShortTerm > m_maxRmsShortTerm) { Chris@8: m_maxRmsShortTerm = m_rmsShortTerm; Chris@8: } Chris@8: Chris@9: float fixedGain = targetMaxRMS / m_maxRmsLongTerm; Chris@8: Chris@9: float frac = m_rmsShortTerm / m_maxRmsShortTerm; Chris@9: Chris@9: // push up toward top of 0,1 range Chris@9: frac = pow(frac, 0.3); Chris@9: Chris@9: float targetRMS = (frac * m_maxRmsShortTerm); Chris@9: Chris@9: float targetGain = fixedGain * (targetRMS / m_rmsShortTerm); Chris@8: Chris@3: if (targetGain > maxGain) { Chris@3: targetGain = maxGain; Chris@3: } Chris@7: Chris@2: float catchUpSamples = catchUpSeconds * m_sampleRate; Chris@2: // asymptotic, could improve? Chris@2: m_gain = m_gain + (targetGain - m_gain) / catchUpSamples; Chris@7: Chris@2: if (fabsf(f) * m_gain > 1.f) { Chris@2: m_gain = 1.f / fabsf(f); Chris@2: } Chris@7: Chris@2: // cerr << "target gain = " << targetGain << ", gain = " << m_gain << endl; Chris@2: return f * m_gain; Chris@1: } Chris@1: Chris@1: void Chris@1: FlattenDynamics::updateRMS(float f) Chris@1: { Chris@11: // We update the RMS values by maintaining a sum-of-last-n-squares Chris@11: // total (which the RMS is the square root of 1/n of) and Chris@11: // recording the last n samples of history in a circular Chris@11: // buffer. When a sample drops off the start of the history, we Chris@11: // remove the square of it from the sum-of-squares total; then we Chris@11: // add the square of the new sample. Chris@11: Chris@1: int nextWrite = (m_histwrite + 1) % m_histlen; Chris@1: Chris@7: float loseLongTerm = 0.f; Chris@7: float loseShortTerm = 0.f; Chris@1: Chris@1: if (nextWrite == m_histread) { Chris@1: // full Chris@7: loseLongTerm = m_history[m_histread]; Chris@1: m_histread = (m_histread + 1) % m_histlen; Chris@1: } Chris@1: Chris@7: int shortTermLength = round(shortTermSeconds * m_sampleRate); Chris@7: int shortTermLoseIndex = nextWrite - shortTermLength; Chris@7: if (shortTermLoseIndex < 0) { Chris@7: shortTermLoseIndex += m_histlen; Chris@7: } Chris@7: // This depends on history being zero-initialised, to be correct at start: Chris@7: loseShortTerm = m_history[shortTermLoseIndex]; Chris@7: Chris@1: m_history[m_histwrite] = f; Chris@1: m_histwrite = nextWrite; Chris@1: Chris@2: int fill = (m_histwrite - m_histread + m_histlen) % m_histlen; Chris@2: Chris@7: m_sumOfSquaresLongTerm -= loseLongTerm * loseLongTerm; Chris@7: m_sumOfSquaresLongTerm += f * f; Chris@1: Chris@7: m_sumOfSquaresShortTerm -= loseShortTerm * loseShortTerm; Chris@7: m_sumOfSquaresShortTerm += f * f; Chris@7: Chris@7: m_rmsLongTerm = sqrt(m_sumOfSquaresLongTerm / fill); Chris@7: m_rmsShortTerm = sqrt(m_sumOfSquaresShortTerm / shortTermLength); Chris@2: // cerr << "rms = " << m_rms << " (from " << fill << " samples of " << m_histlen << ", latest " << f << ")" << endl; Chris@1: } Chris@1: Chris@0: const LADSPA_Descriptor * Chris@0: ladspa_descriptor(unsigned long ix) Chris@0: { Chris@0: return FlattenDynamics::getDescriptor(ix); Chris@0: } Chris@0: