Mercurial > hg > flattendynamics
view flattendynamics-ladspa.cpp @ 7:4d48ab57fbcc
A global/local version that works approximately as well in first tests as the global-only version (r8)
author | Chris Cannam |
---|---|
date | Tue, 22 Jul 2014 11:27:59 +0100 |
parents | 231e20ba3168 |
children | 355fb9ea3888 |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ #include "flattendynamics-ladspa.h" #include <iostream> #include <cmath> using std::cerr; using std::endl; const float longTermSeconds = 4.f; const float shortTermSeconds = 0.5f; const float catchUpSeconds = 0.1f; const float targetMaxRMS = 0.07f; const float maxGain = 20.f; const char *const FlattenDynamics::portNames[PortCount] = { "Input", "Output", "Gain", }; const LADSPA_PortDescriptor FlattenDynamics::ports[PortCount] = { LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, }; const LADSPA_PortRangeHint FlattenDynamics::hints[PortCount] = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, }; const LADSPA_Properties FlattenDynamics::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; const LADSPA_Descriptor FlattenDynamics::ladspaDescriptor = { 0xf0b375, // "Unique" ID "flattendynamics", // Label properties, "Flatten Dynamics", // Name "Queen Mary, University of London", // Maker "BSD", // Copyright PortCount, ports, portNames, hints, 0, // Implementation data instantiate, connectPort, activate, run, 0, // Run adding 0, // Set run adding gain deactivate, cleanup }; const LADSPA_Descriptor * FlattenDynamics::getDescriptor(unsigned long index) { if (index == 0) return &ladspaDescriptor; return 0; } FlattenDynamics::FlattenDynamics(int sampleRate) : m_sampleRate(sampleRate), m_input(0), m_output(0), m_pgain(0), m_history(0), m_histlen(0), m_histwrite(0), m_histread(0), m_sumOfSquaresLongTerm(0.f), m_sumOfSquaresShortTerm(0.f), m_rmsLongTerm(0.f), m_rmsShortTerm(0.f), m_maxRmsLongTerm(0.f), m_gain(1.f) { reset(); } FlattenDynamics::~FlattenDynamics() { delete[] m_history; } LADSPA_Handle FlattenDynamics::instantiate(const LADSPA_Descriptor *, unsigned long rate) { FlattenDynamics *flatten = new FlattenDynamics(rate); return flatten; } void FlattenDynamics::connectPort(LADSPA_Handle handle, unsigned long port, LADSPA_Data *location) { FlattenDynamics *flatten = (FlattenDynamics *)handle; float **ports[PortCount] = { &flatten->m_input, &flatten->m_output, &flatten->m_pgain, }; *ports[port] = (float *)location; } void FlattenDynamics::activate(LADSPA_Handle handle) { FlattenDynamics *flatten = (FlattenDynamics *)handle; flatten->reset(); } void FlattenDynamics::run(LADSPA_Handle handle, unsigned long samples) { FlattenDynamics *flatten = (FlattenDynamics *)handle; flatten->runImpl(samples); } void FlattenDynamics::deactivate(LADSPA_Handle handle) { activate(handle); // both functions just reset the plugin } void FlattenDynamics::cleanup(LADSPA_Handle handle) { delete (FlattenDynamics *)handle; } void FlattenDynamics::reset() { delete[] m_history; m_histlen = int(round(m_sampleRate * longTermSeconds)); if (m_histlen < 1) m_histlen = 1; m_history = new float[m_histlen]; for (int i = 0; i < m_histlen; ++i) { m_history[i] = 0.f; } m_histwrite = 0; m_histread = 0; m_sumOfSquaresLongTerm = 0.0; m_sumOfSquaresShortTerm = 0.0; m_rmsLongTerm = 0.f; m_rmsShortTerm = 0.f; m_maxRmsLongTerm = 0.f; m_gain = 1.f; } void FlattenDynamics::updateParameters() { if (m_pgain) *m_pgain = m_gain; } void FlattenDynamics::runImpl(unsigned long sampleCount) { if (!m_input || !m_output) return; updateParameters(); // Adjust gain so that // * RMS level of the past N seconds is some fixed R, but // * peak level does not clip // We aim to take M seconds to move to our target gain for (int i = 0; i < sampleCount; ++i) { m_output[i] = process(m_input[i]); } } float FlattenDynamics::process(float f) { updateRMS(f); if (m_rmsLongTerm == 0.f) { return f; } if (m_rmsLongTerm > m_maxRmsLongTerm) { m_maxRmsLongTerm = m_rmsLongTerm; } float frac = m_rmsShortTerm / m_maxRmsLongTerm; // push up toward top of 0,1 range frac = pow(frac, 0.5); float targetRMS = targetMaxRMS * frac; float targetGain = targetRMS / m_rmsShortTerm; if (targetGain > maxGain) { targetGain = maxGain; } float catchUpSamples = catchUpSeconds * m_sampleRate; // asymptotic, could improve? m_gain = m_gain + (targetGain - m_gain) / catchUpSamples; if (fabsf(f) * m_gain > 1.f) { m_gain = 1.f / fabsf(f); } // cerr << "target gain = " << targetGain << ", gain = " << m_gain << endl; return f * m_gain; } void FlattenDynamics::updateRMS(float f) { int nextWrite = (m_histwrite + 1) % m_histlen; float loseLongTerm = 0.f; float loseShortTerm = 0.f; if (nextWrite == m_histread) { // full loseLongTerm = m_history[m_histread]; m_histread = (m_histread + 1) % m_histlen; } int shortTermLength = round(shortTermSeconds * m_sampleRate); int shortTermLoseIndex = nextWrite - shortTermLength; if (shortTermLoseIndex < 0) { shortTermLoseIndex += m_histlen; } // This depends on history being zero-initialised, to be correct at start: loseShortTerm = m_history[shortTermLoseIndex]; m_history[m_histwrite] = f; m_histwrite = nextWrite; int fill = (m_histwrite - m_histread + m_histlen) % m_histlen; m_sumOfSquaresLongTerm -= loseLongTerm * loseLongTerm; m_sumOfSquaresLongTerm += f * f; m_sumOfSquaresShortTerm -= loseShortTerm * loseShortTerm; m_sumOfSquaresShortTerm += f * f; m_rmsLongTerm = sqrt(m_sumOfSquaresLongTerm / fill); m_rmsShortTerm = sqrt(m_sumOfSquaresShortTerm / shortTermLength); // cerr << "rms = " << m_rms << " (from " << fill << " samples of " << m_histlen << ", latest " << f << ")" << endl; } const LADSPA_Descriptor * ladspa_descriptor(unsigned long ix) { return FlattenDynamics::getDescriptor(ix); }