sintonia/library/ecasound-2.7.2/libecasound/audiofx_compressor.cpp

464 lines
12 KiB
C++

// ------------------------------------------------------------------------
// audiofx_compressor.cpp: C++ implementation of John S. Dyson's
// compressor code. If you want the original
// C-sources, mail me.
// Copyright (C) 1999-2000 Kai Vehmanen
//
// Copyright for the actual algorithm (compressor2.c):
// ***************************************************
/*
* Copyright (c) 1996, John S. Dyson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* This code (easily) runs realtime on a P5-166 w/EDO, Triton-II on FreeBSD.
*
* More info/comments: dyson@freebsd.org
*
* This program provides compression of a stereo 16bit audio stream,
* such as that contained by a 16Bit wav file. Extreme measures have
* been taken to make the compression as subtile as possible. One
* possible purpose for this code would be to master cassette tapes from
* CD's for playback in automobiles where dynamic range needs to be
* restricted.
*
* Suitably recoded for an embedded DSP, this would make a killer audio
* compressor for broadcast or recording. When writing this code, I
* ignored the issues of roundoff error or trucation -- Pentiums have
* really nice FP processors :-).
*/
// ------------------------------------------------------------------------
#include <cmath>
#include <vector>
#include <kvu_dbc.h>
#include <kvu_message_item.h>
#include "samplebuffer_iterators.h"
#include "audiofx_amplitude.h"
#include "eca-logger.h"
ADVANCED_COMPRESSOR::ADVANCED_COMPRESSOR (double peak_limit, double release_time, double cfrate,
double crate)
: rlevelsqn(ADVANCED_COMPRESSOR::NFILT),
rlevelsqe(ADVANCED_COMPRESSOR::NEFILT)
{
init_values();
set_parameter(1, peak_limit);
set_parameter(2, release_time);
set_parameter(3, cfrate);
set_parameter(4, crate);
MESSAGE_ITEM otemp;
otemp.setprecision(2);
otemp << "(audiofx_compressor) Advanced compressor enabled;";
otemp << " peak limit " << peakpercent;
otemp << " release time " << release_time;
otemp << " cfrate " << fastgaincompressionratio;
otemp << " crate " << compressionratio << ".";
ECA_LOG_MSG(ECA_LOGGER::info, otemp.to_string());
}
void ADVANCED_COMPRESSOR::init_values(void) {
mingain = 10000;
maxgain = 0;
/* These filters should filter at least the lowest audio freq */
rlevelsq0filter = .001;
rlevelsq1filter = .010;
/* These are the attack time for the rms measurement */
rlevelsq0ffilter = .001;
rlevelsqefilter = .001;
/*
* maximum gain for fast compressor
*/
maxfastgain = 3;
/*
* maximum gain for slow compressor
*/
maxslowgain = 9;
/*
* Level below which gain tracking shuts off
*/
floorlevel = SAMPLE_SPECS::max_amplitude * 0.06; // was 2000
// floorlevel = 2000;
/*
* Slow compressor time constants
*/
rmastergain0filter = .000003;
rpeakgainfilter = .001;
rpeaklimitdelay = 2500;
rgain = rmastergain0 = 1.0;
rlevelsq0 = levelsq1 = 0;
rlevelsq1 = 0;
compress = 1;
ndelay = (int)(1.0 / rlevelsq0ffilter);
// ECA_LOG_MSG(ECA_LOGGER::user_objects, "(audiofx_compressor) Number of delays : " +
// kvu_numtostr(ndelay) + ".");
rightdelay.resize(ndelay);
leftdelay.resize(ndelay);
// rlevelsqn = new vector<double> (NFILT + 1);
// rlevelsqe = new vector<double> (NEFILT + 1);
rpeakgain0 = 1.0;
rpeakgain1 = 1.0;
rpeaklimitdelay = 0;
ndelayptr = 0;
lastrgain = 1.0;
for(i = 0; i < NFILT;i++)
rlevelsqn[i] = 0.0;
for(i = 0; i < NEFILT;i++)
rlevelsqe[i] = 0.0;
/* set defaults to some sane values */
peakpercent = 100.0f;
releasetime = 0;
fratio = 1.0;
ratio = 1.0;
}
ADVANCED_COMPRESSOR::~ADVANCED_COMPRESSOR (void)
{
}
void ADVANCED_COMPRESSOR::set_parameter(int param, CHAIN_OPERATOR::parameter_t value) {
// cerr << "Param: " << param << ", value: " << value << ".\n";
switch (param) {
case 1:
{
// ---
// target level for compression
// ---
maxlevel = SAMPLE_SPECS::max_amplitude * 0.9; // limiter level (was 32000)
// maxlevel = 32000;
peakpercent = value;
if (peakpercent == 0) peakpercent = 69;
targetlevel = maxlevel * peakpercent / 100.0;
break;
}
case 2:
{
// --
// Linear gain filters as opposed to the level measurement filters
// --
DBC_CHECK(samples_per_second() != 0);
releasetime = value;
if (releasetime == 0) releasetime = 0.01;
rgainfilter = 1.0 / (releasetime * samples_per_second());
break;
}
case 3:
{
// --
// compression ratio for fast gain. This will determine how
// much the audio is made more dense. .5 is equiv to 2:1
// compression. 1.0 is equiv to inf:1 compression.
// --
fratio = value;
if (fratio == 0) fratio = 0.5;
fastgaincompressionratio = fratio;
break;
}
case 4:
{
// --
// overall ompression ratio.
// --
ratio = value;
if (ratio == 0) ratio = 1.0;
compressionratio = ratio;
break;
}
}
}
CHAIN_OPERATOR::parameter_t ADVANCED_COMPRESSOR::get_parameter(int param) const {
switch (param)
{
case 1:
return(peakpercent);
case 2:
return(releasetime);
case 3:
return(fratio);
case 4:
return(ratio);
}
return(0.0);
}
double ADVANCED_COMPRESSOR::hardlimit(double value, double knee, double limit)
{
// double lrange = (limit - knee);
double ab = fabs(value);
/*
if (ab > knee) {
double abslimit = (limit * 1.1);
if (ab < abslimit)
value = knee + lrange * sin( ((value - knee)/abslimit) * (3.14 / (4*1.1)));
}
*/
if (ab >= limit)
value = value > 0 ? limit : -limit;
return value;
}
void ADVANCED_COMPRESSOR::init(SAMPLE_BUFFER* insample) {
iter.init(insample);
set_channels(insample->number_of_channels());
set_samples_per_second(samples_per_second());
}
void ADVANCED_COMPRESSOR::process(void) {
iter.begin();
while(!iter.end()) {
// right = insample->get_right() * 32767.0;
// left = insample->get_left() * 32767.0;
left = (*iter.current(0));
right = (*iter.current(1));
rightdelay[ndelayptr] = right;
leftdelay[ndelayptr] = left;
ndelayptr++;
// cerr << "1.l:" << left << "\n";
// cerr << "1.r:" << right << "\n";
// cerr << "2.l:" << leftdelay[ndelayptr - 1] << "should be 1=2\n";
if (ndelayptr >= ndelay)
ndelayptr = 0;
/* enable/disable compression */
skipmode = 0;
if (compress == 0) {
skipmode = 1;
goto skipagc;
}
levelsq0 = (right) * (right) + (left) * (left);
// if (ndelayptr == 0) {
// cerr << "3.1.l: " << "rlevelsq0 " << rlevelsq0 << "\n";
// cerr << "3.2.l: " << "rlevelsq0ffilter " << rlevelsq0ffilter << "\n";
// cerr << "3.2.l: " << "rlevelsq0filter " << rlevelsq0filter << "\n";
// }
if (levelsq0 > rlevelsq0) {
rlevelsq0 = (levelsq0 * rlevelsq0ffilter) +
rlevelsq0 * (1 - rlevelsq0ffilter);
} else {
rlevelsq0 = (levelsq0 * rlevelsq0filter) +
rlevelsq0 * (1 - rlevelsq0filter);
}
if (rlevelsq0 <= floorlevel * floorlevel)
goto skipagc;
// if (ndelayptr == 0)
// cerr << "3.3.l: " << "rlevelsq0 " << rlevelsq0 << "\n";
if (rlevelsq0 > rlevelsq1) {
rlevelsq1 = rlevelsq0;
} else {
rlevelsq1 = rlevelsq0 * rlevelsq1filter +
rlevelsq1 * (1 - rlevelsq1filter);
}
// vika.. rlevelsq1 joskus menee pahasti yli ayrauden
// if (ndelayptr == 0)
// cerr << "3.3.l: " << "rlevelsq1 " << rlevelsq1 << "\n";
rlevelsqn[0] = rlevelsq1;
for(i = 0; i < NFILT-1; i++) {
if (rlevelsqn[i] > rlevelsqn[i+1])
rlevelsqn[i+1] = rlevelsqn[i];
else
rlevelsqn[i+1] = rlevelsqn[i] * rlevelsq1filter +
rlevelsqn[i+1] * (1 - rlevelsq1filter);
}
efilt = rlevelsqefilter;
levelsqe = rlevelsqe[0] = rlevelsqn[NFILT-1];
for(i = 0; i < NEFILT-1; i++) {
// if (rlevelsqe[i] > FLT_MAX) rlevelsqe[i] = FLT_MAX;
// else {
rlevelsqe[i+1] = rlevelsqe[i] * efilt +
rlevelsqe[i+1] * (1.0 - efilt);
if (rlevelsqe[i+1] > levelsqe)
levelsqe = rlevelsqe[i+1];
efilt *= 1.0 / 1.5;
}
gain = targetlevel / sqrt(levelsqe);
if (compressionratio < 0.99) {
if (compressionratio == 0.50)
gain = sqrt(gain);
else
gain = exp(log(gain) * compressionratio);
}
if (gain < rgain)
rgain = gain * rlevelsqefilter/2 +
rgain * (1 - rlevelsqefilter/2);
else
rgain = gain * rgainfilter +
rgain * (1 - rgainfilter);
lastrgain = rgain;
if ( gain < lastrgain)
lastrgain = gain;
skipagc:;
tgain = lastrgain;
leftd = leftdelay[ndelayptr];
rightd = rightdelay[ndelayptr];
// cerr << "4.l:" << leftd << ", ndelayptr " << ndelayptr << "\n";
fastgain = tgain;
if (fastgain > maxfastgain)
fastgain = maxfastgain;
if (fastgain < 0.0001)
fastgain = 0.0001;
if (fastgaincompressionratio == 0.25) {
qgain = sqrt(sqrt(fastgain));
} else if (fastgaincompressionratio == 0.5) {
qgain = sqrt(fastgain);
} else if (fastgaincompressionratio == 1.0) {
qgain = fastgain;
} else {
qgain = exp(log(fastgain) * fastgaincompressionratio);
}
// cerr << "4.4-qgain: " << qgain << "\n";
tslowgain = tgain / qgain;
if (tslowgain > maxslowgain)
tslowgain = maxslowgain;
if (tslowgain < rmastergain0)
rmastergain0 = tslowgain;
else
rmastergain0 = tslowgain * rmastergain0filter +
(1 - rmastergain0filter) * rmastergain0;
slowgain = rmastergain0;
if (skipmode == 0)
npeakgain = slowgain * qgain;
/**/
newright = rightd * npeakgain;
if (fabs(newright) >= maxlevel)
nrgain = maxlevel / fabs(newright);
else
nrgain = 1.0;
newleft = leftd * npeakgain;
if (fabs(newleft) >= maxlevel)
nlgain = maxlevel / fabs(newleft);
else
nlgain = 1.0;
// cerr << "4.5.l:" << newleft << "\n";
ngain = nrgain;
if (nlgain < ngain)
ngain = nlgain;
ngsq = ngain * ngain;
if (ngsq <= rpeakgain0) {
// --debug
// if (ngsq < rpeakgain0) // cerr << "*";
rpeakgain0 = ngsq /* * 0.50 + rpeakgain0 * 0.50 */;
rpeaklimitdelay = peaklimitdelay;
} else if (rpeaklimitdelay == 0) {
if (nrgain > 1.0)
tnrgain = 1.0;
else
tnrgain = nrgain;
rpeakgain0 = tnrgain * rpeakgainfilter +
(1.0 - rpeakgainfilter) * rpeakgain0;
}
if (rpeakgain0 <= rpeakgain1) {
rpeakgain1 = rpeakgain0;
rpeaklimitdelay = peaklimitdelay;
} else if (rpeaklimitdelay == 0) {
rpeakgain1 = rpeakgainfilter * rpeakgain0 +
(1.0 - rpeakgainfilter) * rpeakgain1;
} else {
--rpeaklimitdelay;
}
sqrtrpeakgain = sqrt(rpeakgain1);
totalgain = npeakgain * sqrtrpeakgain;
right = newright * sqrtrpeakgain;
*iter.current(1) = right;
//insample->put_left(left / 32767.0);
// *righta = hardlimit(right, 32200, 32767);
// cerr << "5.l:" << newleft << "\n";
left = newleft * sqrtrpeakgain;
*iter.current(0) = left;
// insample->put_left(left / 32767.0);
// cerr << "6.l:" << left << "\n";
//
// *lefta = hardlimit(left, 32200, 32767);
// if (right != *righta || left != *lefta) {
// fprintf(stderr,"!");
// }
if (totalgain > maxgain)
maxgain = totalgain;
if (totalgain < mingain)
mingain = totalgain;
if (right > extra_maxlevel)
extra_maxlevel = right;
if (left > extra_maxlevel)
extra_maxlevel = left;
iter.next();
}
// cerr << "post:" << insample->get_left() << "\n";
}