[DEV] Continue rework

This commit is contained in:
Edouard DUPIN 2015-04-07 22:17:55 +02:00
parent 72a2776a6f
commit 1d1b5e9393
8 changed files with 338 additions and 79 deletions

View File

@ -26,7 +26,7 @@
#include <audio/algo/chunkware/Compressor.h>
#include <audio/algo/chunkware/debug.h>
audio::algo::chunkware::Compresssor::Compresssor() :
audio::algo::chunkware::Compressor::Compressor() :
AttRelEnvelope(10.0, 100.0),
m_threshdB(0.0),
m_ratio(1.0),
@ -34,23 +34,86 @@ audio::algo::chunkware::Compresssor::Compresssor() :
}
void audio::algo::chunkware::Compresssor::setThresh(double _dB) {
void audio::algo::chunkware::Compressor::setThreshold(double _dB) {
m_threshdB = _dB;
}
void audio::algo::chunkware::Compresssor::setRatio(double _ratio) {
AA_CHUNK_ASSERT(_ratio > 0.0, "input function error");
void audio::algo::chunkware::Compressor::setRatio(double _ratio) {
m_ratio = _ratio;
}
void audio::algo::chunkware::Compresssor::initRuntime() {
void audio::algo::chunkware::Compressor::initRuntime() {
m_overThresholdEnvelopeDB = DC_OFFSET;
}
void audio::algo::chunkware::Compresssor::process(double& _in1, double& _in2) {
void audio::algo::chunkware::Compressor::process(audio::format _format, void* _output, const void* _input, size_t _nbChunk, int8_t _nbChannel) {
// TODO : Check init ...
if (_nbChannel != 1) {
AA_CHUNK_ERROR("Can not compress with Other than single channel: " << _nbChannel);
}
switch (_format) {
case audio::format_int16:
{
const int16_t* input = reinterpret_cast<const int16_t*>(_input);
int16_t* output = reinterpret_cast<int16_t*>(_output);
for (size_t iii=0; iii<_nbChunk ; ++iii) {
double val = input[iii];
val /= 32768.0;
processMono(val);
val *= 32768.0;
output[iii] = int16_t(std::avg(-32768.0, val*32768.0, 32767.0));
}
}
break;
case audio::format_double:
{
const double* input = reinterpret_cast<const double*>(_input);
double* output = reinterpret_cast<double*>(_output);
for (size_t iii=0; iii<_nbChunk ; ++iii) {
output[iii] = input[iii];
processMono(output[iii]);
//AA_CHUNK_INFO(" in=" << input[iii] << " => " << output[iii]);
}
}
break;
default:
AA_CHUNK_ERROR("Can not compress with unsupported format : " << _format);
return;
}
}
void audio::algo::chunkware::Compressor::processMono(double& _in) {
double rect1 = std::abs(_in); // rectify input
rect1 += DC_OFFSET;
double keydB = lin2dB(rect1);
// threshold
double overdB = keydB - m_threshdB; //delta over threshold
if ( overdB < 0.0 ) {
overdB = 0.0;
}
// attack/release
overdB += DC_OFFSET; // add DC offset to avoid denormal
AttRelEnvelope::run(overdB, m_overThresholdEnvelopeDB); // run attack/release envelope
overdB = m_overThresholdEnvelopeDB - DC_OFFSET; // subtract DC offset
/* REGARDING THE DC OFFSET: In this case, since the offset is added before
* the attack/release processes, the envelope will never fall below the offset,
* thereby avoiding denormals. However, to prevent the offset from causing
* constant gain reduction, we must subtract it from the envelope, yielding
* a minimum value of 0dB.
*/
// transfer function
double gr = overdB * ( m_ratio - 1.0 ); // gain reduction (dB)
gr = dB2lin(gr); // convert dB -> linear
// output gain
_in *= gr; // apply gain reduction to input
}
void audio::algo::chunkware::Compressor::process(double& _in1, double& _in2) {
// create sidechain
double rect1 = fabs(_in1); // rectify input
double rect2 = fabs(_in2);
double rect1 = std::abs(_in1); // rectify input
double rect2 = std::abs(_in2);
/* if desired, one could use another EnvelopeDetector to smooth
* the rectified signal.
*/
@ -58,15 +121,16 @@ void audio::algo::chunkware::Compresssor::process(double& _in1, double& _in2) {
process(_in1, _in2, link); // rest of process
}
void audio::algo::chunkware::Compresssor::process(double& _in1, double& _in2, double _keyLinked) {
_keyLinked = fabs(_keyLinked); // rectify (just in case)
void audio::algo::chunkware::Compressor::process(double& _in1, double& _in2, double _keyLinked) {
_keyLinked = std::abs(_keyLinked); // rectify (just in case)
// convert key to dB
_keyLinked += DC_OFFSET; // add DC offset to avoid log(0)
double keydB = lin2dB(_keyLinked); // convert linear -> dB
_keyLinked += DC_OFFSET; // add DC offset to avoid log(0)
double keydB = lin2dB(_keyLinked); // convert linear -> dB
// threshold
double overdB = keydB - m_threshdB; // delta over threshold
if (overdB < 0.0)
double overdB = keydB - m_threshdB; // delta over threshold
if (overdB < 0.0) {
overdB = 0.0;
}
// attack/release
overdB += DC_OFFSET; // add DC offset to avoid denormal
audio::algo::chunkware::AttRelEnvelope::run(overdB, m_overThresholdEnvelopeDB); // run attack/release envelope
@ -78,8 +142,8 @@ void audio::algo::chunkware::Compresssor::process(double& _in1, double& _in2, do
* a minimum value of 0dB.
*/
// transfer function
double gr = overdB * (m_ratio - 1.0); // gain reduction (dB)
gr = dB2lin(gr); // convert dB -> linear
double gr = overdB * (m_ratio - 1.0); // gain reduction (dB)
gr = dB2lin(gr); // convert dB -> linear
// output gain
_in1 *= gr; // apply gain reduction to input
_in2 *= gr;

View File

@ -28,36 +28,43 @@
#define __AUDIO_ALGO_CHUNKWARE_COMPRESSOR_H__
#include <etk/types.h>
#include <audio/format.h>
#include <audio/algo/chunkware/AttRelEnvelope.h>
#include <audio/algo/chunkware/Gain.h>
namespace audio {
namespace algo {
namespace chunkware {
class Compresssor : public audio::algo::chunkware::AttRelEnvelope {
class Compressor : public audio::algo::chunkware::AttRelEnvelope {
public:
Compresssor();
virtual ~Compresssor() {}
// parameters
virtual void setThresh(double _dB);
virtual void setRatio(double _dB);
virtual double getThresh() const {
Compressor();
virtual ~Compressor() {}
protected:
double m_threshdB;//!< threshold (dB)
public:
virtual void setThreshold(double _dB);
virtual double getThreshold() const {
return m_threshdB;
}
protected:
double m_ratio; //!< ratio (compression: < 1 ; expansion: > 1)
public:
virtual void setRatio(double _dB);
virtual double getRatio() const {
return m_ratio;
}
// runtime
public:
void process(audio::format _format, void* _output, const void* _input, size_t _nbChunk, int8_t _nbChannel);
// call before runtime (in resume())
virtual void initRuntime();
protected:
// runtime
// compressor runtime process
void processMono(double& _in);
// compressor runtime process
void process(double& _in1, double& _in2);
// with stereo-linked key in
void process(double& _in1, double& _in2, double _keyLinked);
private:
// transfer function
double m_threshdB;//!< threshold (dB)
double m_ratio; //!< ratio (compression: < 1 ; expansion: > 1)
// runtime variables
double m_overThresholdEnvelopeDB; //!< over-threshold envelope (dB)
};

View File

@ -27,27 +27,27 @@
#include <audio/algo/chunkware/CompressorRms.h>
audio::algo::chunkware::CompresssorRms::CompresssorRms() :
audio::algo::chunkware::CompressorRms::CompressorRms() :
m_averager(5.0),
m_averageSuares(DC_OFFSET) {
}
void audio::algo::chunkware::CompresssorRms::setSampleRate(double _sampleRate) {
audio::algo::chunkware::Compresssor::setSampleRate(_sampleRate);
void audio::algo::chunkware::CompressorRms::setSampleRate(double _sampleRate) {
audio::algo::chunkware::Compressor::setSampleRate(_sampleRate);
m_averager.setSampleRate(_sampleRate);
}
void audio::algo::chunkware::CompresssorRms::setWindow(double _ms) {
void audio::algo::chunkware::CompressorRms::setWindow(double _ms) {
m_averager.setTc(_ms);
}
void audio::algo::chunkware::CompresssorRms::initRuntime() {
audio::algo::chunkware::Compresssor::initRuntime();
void audio::algo::chunkware::CompressorRms::initRuntime() {
audio::algo::chunkware::Compressor::initRuntime();
m_averageSuares = DC_OFFSET;
}
void audio::algo::chunkware::CompresssorRms::process(double& _in1, double& _in2) {
void audio::algo::chunkware::CompressorRms::process(double& _in1, double& _in2) {
// create sidechain
double inSq1 = _in1 * _in1; // square input
double inSq2 = _in2 * _in2;
@ -62,5 +62,5 @@ void audio::algo::chunkware::CompresssorRms::process(double& _in1, double& _in2)
* giving comparable results.
*/
// rest of process
audio::algo::chunkware::Compresssor::process(_in1, _in2, rms);
audio::algo::chunkware::Compressor::process(_in1, _in2, rms);
}

View File

@ -32,10 +32,10 @@
namespace audio {
namespace algo {
namespace chunkware {
class CompresssorRms : public audio::algo::chunkware::Compresssor {
class CompressorRms : public audio::algo::chunkware::Compressor {
public:
CompresssorRms();
virtual ~CompresssorRms() {}
CompressorRms();
virtual ~CompressorRms() {}
// sample rate
virtual void setSampleRate(double _sampleRate);
// RMS window

View File

@ -43,7 +43,7 @@ audio::algo::chunkware::Limiter::Limiter() :
m_outputBuffer[ 1 ].resize(BUFFER_SIZE, 0.0);
}
void audio::algo::chunkware::Limiter::setThresh(double _dB) {
void audio::algo::chunkware::Limiter::setThreshold(double _dB) {
m_threshdB = _dB;
m_threshold = dB2lin(_dB);
}
@ -73,11 +73,136 @@ void audio::algo::chunkware::Limiter::initRuntime() {
m_outputBuffer[ 1 ].assign(BUFFER_SIZE, 0.0);
}
void audio::algo::chunkware::Limiter::FastEnvelope::setCoef() {
void audio::algo::chunkware::FastEnvelope::setCoef() {
// rises to 99% of in value over duration of time constant
m_coefficient = pow(0.01, (1000.0 / (m_timeMs * m_sampleRate)));
m_coefficient = std::pow(0.01, (1000.0 / (m_timeMs * m_sampleRate)));
}
void audio::algo::chunkware::Limiter::process(audio::format _format, void* _output, const void* _input, size_t _nbChunk, int8_t _nbChannel) {
// TODO : Check init ...
if (_nbChannel != 1) {
AA_CHUNK_ERROR("Can not compress with Other than single channel: " << _nbChannel);
}
switch (_format) {
case audio::format_int16:
{
const int16_t* input = reinterpret_cast<const int16_t*>(_input);
int16_t* output = reinterpret_cast<int16_t*>(_output);
for (size_t iii=0; iii<_nbChunk ; ++iii) {
double val = input[iii];
val /= 32768.0;
processMono(val);
val *= 32768.0;
output[iii] = int16_t(std::avg(-32768.0, val*32768.0, 32767.0));
}
}
break;
case audio::format_double:
{
const double* input = reinterpret_cast<const double*>(_input);
double* output = reinterpret_cast<double*>(_output);
for (size_t iii=0; iii<_nbChunk ; ++iii) {
output[iii] = input[iii];
processMono(output[iii]);
//AA_CHUNK_INFO(" in=" << input[iii] << " => " << output[iii]);
}
}
break;
default:
AA_CHUNK_ERROR("Can not compress with unsupported format : " << _format);
return;
}
}
void audio::algo::chunkware::Limiter::processMono(double& _in) {
double keyLink = std::abs(_in); // rectify input
// threshold
// we always want to feed the sidechain AT LEATS the threshold value
if (keyLink < m_threshold) {
keyLink = m_threshold;
}
// test:
// a) whether peak timer has "expired"
// b) whether new peak is greater than previous max peak
if ((++m_peakTimer >= m_peakHold) || (keyLink > m_maxPeak)) {
// if either condition is met:
m_peakTimer = 0; // reset peak timer
m_maxPeak = keyLink; // assign new peak to max peak
}
/* REGARDING THE MAX PEAK: This method assumes that the only important
* sample in a look-ahead buffer would be the highest peak. As such,
* instead of storing all samples in a look-ahead buffer, it only stores
* the max peak, and compares all incoming samples to that one.
* The max peak has a hold time equal to what the look-ahead buffer
* would have been, which is tracked by a timer (counter). When this
* timer expires, the sample would have exited from the buffer. Therefore,
* a new sample must be assigned to the max peak. We assume that the next
* highest sample in our theoretical buffer is the current input sample.
* In reality, we know this is probably NOT the case, and that there has
* been another sample, slightly lower than the one before it, that has
* passed the input. If we do not account for this possibility, our gain
* reduction could be insufficient, resulting in an "over" at the output.
* To remedy this, we simply apply a suitably long release stage in the
* envelope follower.
*/
// attack/release
if (m_maxPeak > m_overThresholdEnvelope) {
// run attack phase
m_attack.run(m_maxPeak, m_overThresholdEnvelope);
} else {
// run release phase
m_release.run(m_maxPeak, m_overThresholdEnvelope);
}
/* REGARDING THE ATTACK: This limiter achieves "look-ahead" detection
* by allowing the envelope follower to attack the max peak, which is
* held for the duration of the attack phase -- unless a new, higher
* peak is detected. The output signal is buffered so that the gain
* reduction is applied in advance of the "offending" sample.
*/
/* NOTE: a DC offset is not necessary for the envelope follower,
* as neither the max peak nor envelope should fall below the
* threshold (which is assumed to be around 1.0 linear).
*/
// gain reduction
double gR = m_threshold / m_overThresholdEnvelope;
// unload current buffer index
// (m_cursor - delay) & m_bufferMask gets sample from [delay] samples ago
// m_bufferMask variable wraps index
unsigned int delayIndex = (m_cursor - m_peakHold) & m_bufferMask;
double delay1 = m_outputBuffer[0][delayIndex];
//double delay2 = m_outputBuffer[1][delayIndex];
// load current buffer index and advance current index
// m_bufferMask wraps m_cursor index
m_outputBuffer[0][m_cursor] = _in;
//m_outputBuffer[1][m_cursor] = _in2;
++m_cursor &= m_bufferMask;
// output gain
_in = delay1 * gR; // apply gain reduction to input
//_in2 = delay2 * gR;
/* REGARDING THE GAIN REDUCTION: Due to the logarithmic nature
* of the attack phase, the sidechain will never achieve "full"
* attack. (Actually, it is only guaranteed to achieve 99% of
* the input value over the given time constant.) As such, the
* limiter cannot achieve "brick-wall" limiting. There are 2
* workarounds:
*
* 1) Set the threshold slightly lower than the desired threshold.
* i.e. 0.0dB -> -0.1dB or even -0.5dB
*
* 2) Clip the output at the threshold, as such:
*
* if (in1 > m_threshold) in1 = m_threshold;
* else if (in1 < -m_threshold) in1 = -m_threshold;
*
* if (in2 > m_threshold) in2 = m_threshold;
* else if (in2 < -m_threshold) in2 = -m_threshold;
*
* (... or replace with your favorite branchless clipper ...)
*/
}
void audio::algo::chunkware::Limiter::process(double& _in1, double& _in2) {
// create sidechain
@ -86,8 +211,9 @@ void audio::algo::chunkware::Limiter::process(double& _in1, double& _in2) {
double keyLink = std::max(rect1, rect2); // link channels with greater of 2
// threshold
// we always want to feed the sidechain AT LEATS the threshold value
if (keyLink < m_threshold)
if (keyLink < m_threshold) {
keyLink = m_threshold;
}
// test:
// a) whether peak timer has "expired"
// b) whether new peak is greater than previous max peak

View File

@ -28,68 +28,102 @@
#define __AUDIO_ALGO_CHUNKWARE_LIMITER_H__
#include <etk/types.h>
#include <audio/format.h>
#include <audio/algo/chunkware/AttRelEnvelope.h>
#include <audio/algo/chunkware/Gain.h>
#include <etk/chrono.h>
#include <vector>
namespace audio {
namespace algo {
namespace chunkware {
// class for faster attack/release
class FastEnvelope : public audio::algo::chunkware::EnvelopeDetector {
public:
FastEnvelope(double _ms = 1.0, double _sampleRate = 44100.0) :
EnvelopeDetector(_ms, _sampleRate) {
}
virtual ~FastEnvelope() {}
protected:
// override setCoef() - coefficient calculation
virtual void setCoef();
};
class Limiter {
protected:
bool m_isConfigured;
public:
Limiter();
virtual ~Limiter() {}
// parameters
virtual void setThresh(double _dB);
virtual void setAttack(double _ms);
virtual void setRelease(double _ms);
virtual double getThresh() const {
protected:
double m_threshdB; //!< threshold (dB)
public:
virtual void setThreshold(double _dB);
virtual double getThreshold() const {
return m_threshdB;
}
protected:
std11::chrono::microseconds m_attackTime; //!< attaque time in ms.
public:
virtual void setAttack(double _ms);
virtual double getAttack() const {
return m_attack.getTc();
}
protected:
std11::chrono::microseconds m_releaseTime; //!< attaque time in ms.
public:
virtual void setRelease(double _ms);
virtual double getRelease() const {
return m_release.getTc();
}
// latency
protected:
unsigned int m_peakHold; //!< peak hold (samples)
public:
virtual const unsigned int getLatency() const {
return m_peakHold;
}
protected:
public:
// sample rate dependencies
virtual void setSampleRate(double _sampleRate);
virtual void setSampleRate(double _sampleRate);
virtual double getSampleRate() {
return m_attack.getSampleRate();
}
// runtime
// call before runtime (in resume())
virtual void initRuntime();
void process(audio::format _format, void* _output, const void* _input, size_t _nbChunk, int8_t _nbChannel);
protected:
process(double* _out, const double* _in, int8_t _nbChannel);
process(float* _out, const float* _in, int8_t _nbChannel);
process(int16_16_t* _out, const int16_16_t* _in, int8_t _nbChannel);
process(int16_32_t* _out, const int16_32_t* _in, int8_t _nbChannel);
process(int24_32_t* _out, const int24_32_t* _in, int8_t _nbChannel);
process(int32_32_t* _out, const int32_32_t* _in, int8_t _nbChannel);
process(int32_64_t* _out, const int32_64_t* _in, int8_t _nbChannel);
// limiter runtime process
void process(double& _in1, double& _in2);
protected:
// class for faster attack/release
class FastEnvelope : public audio::algo::chunkware::EnvelopeDetector {
public:
FastEnvelope(double _ms = 1.0, double _sampleRate = 44100.0) :
EnvelopeDetector(_ms, _sampleRate) {
}
virtual ~FastEnvelope() {}
protected:
// override setCoef() - coefficient calculation
virtual void setCoef();
};
void processMono(double& _in);
private:
// transfer function
double m_threshdB; //!< threshold (dB)
double m_threshold; //!< threshold (linear)
// max peak
unsigned int m_peakHold; //!< peak hold (samples)
unsigned int m_peakTimer; //!< peak hold timer
double m_maxPeak; //!< max peak
// attack/release envelope
audio::algo::chunkware::Limiter::FastEnvelope m_attack; //!< attack
audio::algo::chunkware::Limiter::FastEnvelope m_release; //!< release
audio::algo::chunkware::FastEnvelope m_attack; //!< attack
audio::algo::chunkware::FastEnvelope m_release; //!< release
double m_overThresholdEnvelope; //!< over-threshold envelope (linear)
// buffer
// BUFFER_SIZE default can handle up to ~10ms at 96kHz

View File

@ -19,7 +19,7 @@ def create(target):
'audio/algo/chunkware/GateRms.cpp',
'audio/algo/chunkware/Limiter.cpp'
])
myModule.add_module_depend(['etk'])
myModule.add_module_depend(['etk', 'audio'])
myModule.add_export_path(tools.get_current_path(__file__))
# return module
return myModule

View File

@ -6,7 +6,8 @@
#include <test/debug.h>
#include <etk/etk.h>
#include <audio/algo/chunkware/.h>
#include <audio/algo/chunkware/Compressor.h>
#include <audio/algo/chunkware/Limiter.h>
#include <etk/os/FSNode.h>
#include <etk/chrono.h>
@ -21,6 +22,8 @@ static std::vector<double> convert(const std::vector<int16_t>& _data) {
for (size_t iii=0; iii<_data.size(); ++iii) {
out[iii] = _data[iii];
out[iii] /= 32768.0;
out[iii] *= 2.1;
//APPL_INFO(" in=" << _data[iii] << " => " << out[iii]);
}
return out;
}
@ -43,7 +46,7 @@ int main(int _argc, const char** _argv) {
for (int32_t iii=0; iii<_argc ; ++iii) {
std::string data = _argv[iii];
if (etk::start_with(data,"--in=")) {
fbName = &data[5];
inputName = &data[5];
} else if (data == "--perf") {
perf = true;
} else if (etk::start_with(data,"--sample-rate=")) {
@ -77,14 +80,10 @@ int main(int _argc, const char** _argv) {
std11::chrono::nanoseconds maxProcessing(0);
int32_t totalIteration = 0;
audio::algo::aec::Lms algo;
if (filterSize != 0) {
algo.setFilterSize(filterSize);
}
if (mu != 0.0f) {
algo.setMu(mu);
}
/*
audio::algo::chunkware::Compressor algo;
algo.setThreshold(-10);
algo.setRatio(-5);
int32_t lastPourcent = -1;
for (int32_t iii=0; iii<output.size()/blockSize; ++iii) {
if (lastPourcent != 100*iii / (output.size()/blockSize)) {
@ -94,7 +93,7 @@ int main(int _argc, const char** _argv) {
APPL_VERBOSE("Process : " << iii*blockSize << "/" << int32_t(output.size()/blockSize)*blockSize);
}
std11::chrono::steady_clock::time_point timeStart = std11::chrono::steady_clock::now();
algo.process(&output[iii*blockSize], &fbData[iii*blockSize], &micData[iii*blockSize], blockSize);
algo.process(audio::format_double, &output[iii*blockSize], &inputData[iii*blockSize], blockSize, 1);
if (perf == true) {
std11::chrono::steady_clock::time_point timeEnd = std11::chrono::steady_clock::now();
std11::chrono::nanoseconds time = timeEnd - timeStart;
@ -102,9 +101,38 @@ int main(int _argc, const char** _argv) {
maxProcessing = std::max(maxProcessing, time);
totalTimeProcessing += time;
totalIteration++;
usleep(10000);
usleep(1000);
}
}
*/
audio::algo::chunkware::Limiter algo;
algo.setSampleRate(48000);
algo.setThreshold(0);
algo.setAttack(0.1);
algo.setRelease(2);
algo.initRuntime();
int32_t lastPourcent = -1;
for (int32_t iii=0; iii<output.size()/blockSize; ++iii) {
if (lastPourcent != 100*iii / (output.size()/blockSize)) {
lastPourcent = 100*iii / (output.size()/blockSize);
APPL_INFO("Process : " << iii*blockSize << "/" << int32_t(output.size()/blockSize)*blockSize << " " << lastPourcent << "/100");
} else {
APPL_VERBOSE("Process : " << iii*blockSize << "/" << int32_t(output.size()/blockSize)*blockSize);
}
std11::chrono::steady_clock::time_point timeStart = std11::chrono::steady_clock::now();
algo.process(audio::format_double, &output[iii*blockSize], &inputData[iii*blockSize], blockSize, 1);
if (perf == true) {
std11::chrono::steady_clock::time_point timeEnd = std11::chrono::steady_clock::now();
std11::chrono::nanoseconds time = timeEnd - timeStart;
minProcessing = std::min(minProcessing, time);
maxProcessing = std::max(maxProcessing, time);
totalTimeProcessing += time;
totalIteration++;
usleep(1000);
}
}
if (perf == true) {
APPL_INFO("Performance Result: ");
APPL_INFO(" blockSize=" << blockSize << " sample");