Chris@366: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@366: Chris@366: #include "flattendynamics-ladspa.h" Chris@366: Chris@366: #include Chris@366: #include Chris@366: Chris@366: using std::cerr; Chris@366: using std::endl; Chris@366: Chris@366: const float historySeconds = 4.f; Chris@366: const float catchUpSeconds = 0.2f; Chris@366: const float targetMaxRMS = 0.05f; Chris@366: const float rmsMaxDecay = 0.999f; // per sample Chris@366: const float maxGain = 10.f; Chris@366: Chris@366: const char *const Chris@366: FlattenDynamics::portNames[PortCount] = Chris@366: { Chris@366: "Input", Chris@366: "Output", Chris@366: "Gain", Chris@366: }; Chris@366: Chris@366: const LADSPA_PortDescriptor Chris@366: FlattenDynamics::ports[PortCount] = Chris@366: { Chris@366: LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, Chris@366: LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, Chris@366: LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, Chris@366: }; Chris@366: Chris@366: const LADSPA_PortRangeHint Chris@366: FlattenDynamics::hints[PortCount] = Chris@366: { Chris@366: { 0, 0, 0 }, Chris@366: { 0, 0, 0 }, Chris@366: { 0, 0, 0 }, Chris@366: }; Chris@366: Chris@366: const LADSPA_Properties Chris@366: FlattenDynamics::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; Chris@366: Chris@366: const LADSPA_Descriptor Chris@366: FlattenDynamics::ladspaDescriptor = Chris@366: { Chris@366: 0xf0b375, // "Unique" ID Chris@366: "flattendynamics", // Label Chris@366: properties, Chris@366: "Flatten Dynamics", // Name Chris@366: "Queen Mary, University of London", // Maker Chris@366: "BSD", // Copyright Chris@366: PortCount, Chris@366: ports, Chris@366: portNames, Chris@366: hints, Chris@366: 0, // Implementation data Chris@366: ladspaInstantiate, Chris@366: ladspaConnectPort, Chris@366: ladspaActivate, Chris@366: ladspaRun, Chris@366: 0, // Run adding Chris@366: 0, // Set run adding gain Chris@366: ladspaDeactivate, Chris@366: ladspaCleanup Chris@366: }; Chris@366: Chris@366: const LADSPA_Descriptor * Chris@366: FlattenDynamics::getDescriptor(unsigned long index) Chris@366: { Chris@366: if (index == 0) return &ladspaDescriptor; Chris@366: return 0; Chris@366: } Chris@366: Chris@366: FlattenDynamics::FlattenDynamics(int sampleRate) : Chris@366: m_sampleRate(sampleRate), Chris@366: m_input(0), Chris@366: m_output(0), Chris@366: m_pgain(0), Chris@366: m_history(0), Chris@366: m_histlen(0), Chris@366: m_histwrite(0), Chris@366: m_histread(0), Chris@366: m_sumOfSquares(0.f), Chris@366: m_rms(0.f), Chris@366: m_maxRms(0.f), Chris@366: m_gain(1.f) Chris@366: { Chris@366: reset(); Chris@366: } Chris@366: Chris@366: FlattenDynamics::~FlattenDynamics() Chris@366: { Chris@366: delete[] m_history; Chris@366: } Chris@366: Chris@366: LADSPA_Handle Chris@366: FlattenDynamics::ladspaInstantiate(const LADSPA_Descriptor *, unsigned long rate) Chris@366: { Chris@366: FlattenDynamics *flatten = new FlattenDynamics(rate); Chris@366: return flatten; Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::ladspaConnectPort(LADSPA_Handle handle, Chris@366: unsigned long port, Chris@366: LADSPA_Data *location) Chris@366: { Chris@366: FlattenDynamics *flatten = (FlattenDynamics *)handle; Chris@366: if (ports[port] & LADSPA_PORT_INPUT) { Chris@366: flatten->connectInputPort(Port(port), location); Chris@366: } else { Chris@366: flatten->connectOutputPort(Port(port), location); Chris@366: } Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::ladspaActivate(LADSPA_Handle handle) Chris@366: { Chris@366: FlattenDynamics *flatten = (FlattenDynamics *)handle; Chris@366: flatten->reset(); Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::ladspaRun(LADSPA_Handle handle, unsigned long samples) Chris@366: { Chris@366: FlattenDynamics *flatten = (FlattenDynamics *)handle; Chris@366: flatten->process(samples); Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::ladspaDeactivate(LADSPA_Handle handle) Chris@366: { Chris@366: ladspaActivate(handle); // both functions just reset the plugin Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::ladspaCleanup(LADSPA_Handle handle) Chris@366: { Chris@366: delete (FlattenDynamics *)handle; Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::connectInputPort(Port p, const float *location) Chris@366: { Chris@366: const float **ports[PortCount] = { Chris@366: &m_input, 0, 0, Chris@366: }; Chris@366: Chris@366: *ports[int(p)] = location; Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::connectOutputPort(Port p, float *location) Chris@366: { Chris@366: float **ports[PortCount] = { Chris@366: 0, &m_output, &m_pgain, Chris@366: }; Chris@366: Chris@366: *ports[int(p)] = location; Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::reset() Chris@366: { Chris@366: delete[] m_history; Chris@366: m_histlen = int(round(m_sampleRate * historySeconds)); Chris@366: if (m_histlen < 1) m_histlen = 1; Chris@366: m_history = new float[m_histlen]; Chris@366: for (int i = 0; i < m_histlen; ++i) { Chris@366: m_history[i] = 0.f; Chris@366: } Chris@366: m_histwrite = 0; Chris@366: m_histread = 0; Chris@366: Chris@366: m_sumOfSquares = 0.0; Chris@366: m_rms = 0.f; Chris@366: m_maxRms = 0.f; Chris@366: m_gain = 1.f; Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::updateParameters() Chris@366: { Chris@366: if (m_pgain) *m_pgain = m_gain; Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::process(int sampleCount) Chris@366: { Chris@366: if (!m_input || !m_output) return; Chris@366: Chris@366: updateParameters(); Chris@366: Chris@366: for (int i = 0; i < sampleCount; ++i) { Chris@366: m_output[i] = processSingle(m_input[i]); Chris@366: } Chris@366: } Chris@366: Chris@366: float Chris@366: FlattenDynamics::processSingle(float f) Chris@366: { Chris@366: updateRMS(f); Chris@366: Chris@366: if (m_rms == 0.f) { Chris@366: return f; Chris@366: } Chris@366: Chris@366: if (m_rms >= m_maxRms) { Chris@366: m_maxRms = m_rms; Chris@366: } else { Chris@366: m_maxRms = m_rms + (m_maxRms - m_rms) * rmsMaxDecay; Chris@366: } Chris@366: Chris@366: float targetGain = targetMaxRMS / m_maxRms; Chris@366: Chris@366: if (targetGain > maxGain) { Chris@366: targetGain = maxGain; Chris@366: } Chris@366: Chris@366: float catchUpSamples = catchUpSeconds * m_sampleRate; Chris@366: // asymptotic, could improve? Chris@366: m_gain = m_gain + (targetGain - m_gain) / catchUpSamples; Chris@366: Chris@366: if (fabsf(f) * m_gain > 1.f) { Chris@366: m_gain = 1.f / fabsf(f); Chris@366: } Chris@366: Chris@366: // cerr << "target gain = " << targetGain << ", gain = " << m_gain << endl; Chris@366: return f * m_gain; Chris@366: } Chris@366: Chris@366: void Chris@366: FlattenDynamics::updateRMS(float f) Chris@366: { Chris@366: // We update the RMS values by maintaining a sum-of-last-n-squares Chris@366: // total (which the RMS is the square root of 1/n of) and Chris@366: // recording the last n samples of history in a circular Chris@366: // buffer. When a sample drops off the start of the history, we Chris@366: // remove the square of it from the sum-of-squares total; then we Chris@366: // add the square of the new sample. Chris@366: Chris@366: int nextWrite = (m_histwrite + 1) % m_histlen; Chris@366: Chris@366: float lose = 0.f; Chris@366: Chris@366: if (nextWrite == m_histread) { Chris@366: // full Chris@366: lose = m_history[m_histread]; Chris@366: m_histread = (m_histread + 1) % m_histlen; Chris@366: } Chris@366: Chris@366: m_history[m_histwrite] = f; Chris@366: m_histwrite = nextWrite; Chris@366: Chris@366: int fill = (m_histwrite - m_histread + m_histlen) % m_histlen; Chris@366: Chris@366: m_sumOfSquares -= lose * lose; Chris@366: m_sumOfSquares += f * f; Chris@366: Chris@366: m_rms = sqrt(m_sumOfSquares / fill); Chris@366: // cerr << "rms = " << m_rms << " (from " << fill << " samples of " << m_histlen << ", latest " << f << ")" << endl; Chris@366: } Chris@366: Chris@366: const LADSPA_Descriptor * Chris@366: ladspa_descriptor(unsigned long ix) Chris@366: { Chris@366: return FlattenDynamics::getDescriptor(ix); Chris@366: } Chris@366: