view flattendynamics-ladspa.cpp @ 3:12d364f12d37

delete/delete[] fix; max gain
author Chris Cannam
date Thu, 17 Jul 2014 16:23:17 +0100
parents 57990edc441b
children e36fe9312ad4
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 historySeconds = 4.f;
const float catchUpSeconds = 0.5f;
const float targetRMS = 0.1f;
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_sumOfSquares(0),
    m_rms(0),
    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 * historySeconds));
    if (m_histlen < 1) m_histlen = 1;
    m_history = new float[m_histlen];
    m_histwrite = 0;
    m_histread = 0;

    m_sumOfSquares = 0.0;
    m_rms = 0.0;
    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_rms == 0.f) {
        return f;
    }

    float targetGain = targetRMS / m_rms;
    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 lose;

    if (nextWrite == m_histread) {
        // full
        lose = m_history[m_histread];
        m_histread = (m_histread + 1) % m_histlen;
    } else {
        // not full
        lose = 0.f;
    }

    m_history[m_histwrite] = f;
    m_histwrite = nextWrite;

    int fill = (m_histwrite - m_histread + m_histlen) % m_histlen;

    m_sumOfSquares -= lose * lose;
    m_sumOfSquares += f * f;

    m_rms = sqrt(m_sumOfSquares / fill);
//    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);
}