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@17: const float historySeconds = 4.f; Chris@9: const float catchUpSeconds = 0.2f; Chris@17: const float targetMaxRMS = 0.05f; Chris@14: const float rmsMaxDecay = 0.999f; // per sample Chris@18: const float maxGain = 10.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@15: ladspaInstantiate, Chris@15: ladspaConnectPort, Chris@15: ladspaActivate, Chris@15: ladspaRun, Chris@0: 0, // Run adding Chris@0: 0, // Set run adding gain Chris@15: ladspaDeactivate, Chris@15: ladspaCleanup 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@17: m_sumOfSquares(0.f), Chris@17: m_rms(0.f), Chris@17: m_maxRms(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@15: FlattenDynamics::ladspaInstantiate(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@15: FlattenDynamics::ladspaConnectPort(LADSPA_Handle handle, Chris@15: unsigned long port, Chris@15: LADSPA_Data *location) Chris@0: { Chris@0: FlattenDynamics *flatten = (FlattenDynamics *)handle; Chris@15: if (ports[port] & LADSPA_PORT_INPUT) { Chris@15: flatten->connectInputPort(Port(port), location); Chris@15: } else { Chris@15: flatten->connectOutputPort(Port(port), location); Chris@15: } Chris@0: } Chris@0: Chris@0: void Chris@15: FlattenDynamics::ladspaActivate(LADSPA_Handle handle) Chris@0: { Chris@0: FlattenDynamics *flatten = (FlattenDynamics *)handle; Chris@0: flatten->reset(); Chris@0: } Chris@0: Chris@0: void Chris@15: FlattenDynamics::ladspaRun(LADSPA_Handle handle, unsigned long samples) Chris@0: { Chris@0: FlattenDynamics *flatten = (FlattenDynamics *)handle; Chris@15: flatten->process(samples); Chris@0: } Chris@0: Chris@0: void Chris@15: FlattenDynamics::ladspaDeactivate(LADSPA_Handle handle) Chris@0: { Chris@15: ladspaActivate(handle); // both functions just reset the plugin Chris@0: } Chris@0: Chris@0: void Chris@15: FlattenDynamics::ladspaCleanup(LADSPA_Handle handle) Chris@0: { Chris@0: delete (FlattenDynamics *)handle; Chris@0: } Chris@0: Chris@0: void Chris@15: FlattenDynamics::connectInputPort(Port p, const float *location) Chris@15: { Chris@15: const float **ports[PortCount] = { Chris@15: &m_input, 0, 0, Chris@15: }; Chris@15: Chris@15: *ports[int(p)] = location; Chris@15: } Chris@15: Chris@15: void Chris@15: FlattenDynamics::connectOutputPort(Port p, float *location) Chris@15: { Chris@15: float **ports[PortCount] = { Chris@15: 0, &m_output, &m_pgain, Chris@15: }; Chris@15: Chris@15: *ports[int(p)] = location; Chris@15: } Chris@15: Chris@15: void Chris@0: FlattenDynamics::reset() Chris@0: { Chris@3: delete[] m_history; Chris@17: m_histlen = int(round(m_sampleRate * historySeconds)); 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@17: m_sumOfSquares = 0.0; Chris@17: m_rms = 0.f; Chris@17: m_maxRms = 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@15: FlattenDynamics::process(int sampleCount) Chris@0: { Chris@0: if (!m_input || !m_output) return; Chris@16: Chris@0: updateParameters(); Chris@0: Chris@2: for (int i = 0; i < sampleCount; ++i) { Chris@15: m_output[i] = processSingle(m_input[i]); Chris@0: } Chris@0: } Chris@0: Chris@1: float Chris@15: FlattenDynamics::processSingle(float f) Chris@1: { Chris@1: updateRMS(f); Chris@2: Chris@17: if (m_rms == 0.f) { Chris@2: return f; Chris@2: } Chris@2: Chris@17: if (m_rms >= m_maxRms) { Chris@17: m_maxRms = m_rms; Chris@17: } else { Chris@17: m_maxRms = m_rms + (m_maxRms - m_rms) * rmsMaxDecay; Chris@5: } Chris@5: Chris@17: float targetGain = targetMaxRMS / m_maxRms; 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@17: float lose = 0.f; Chris@1: Chris@1: if (nextWrite == m_histread) { Chris@1: // full Chris@17: lose = m_history[m_histread]; Chris@1: m_histread = (m_histread + 1) % m_histlen; Chris@1: } Chris@1: 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@17: m_sumOfSquares -= lose * lose; Chris@17: m_sumOfSquares += f * f; Chris@1: Chris@17: m_rms = sqrt(m_sumOfSquares / fill); 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: