[DEV] cotinue update
This commit is contained in:
parent
e83c228ca1
commit
e912b34365
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
audio::algo::chunkware::Compressor::Compressor() :
|
audio::algo::chunkware::Compressor::Compressor() :
|
||||||
AttRelEnvelope(10.0, 100.0),
|
AttRelEnvelope(10.0, 100.0),
|
||||||
|
m_isConfigured(false),
|
||||||
m_threshdB(0.0),
|
m_threshdB(0.0),
|
||||||
m_ratio(1.0),
|
m_ratio(1.0),
|
||||||
m_overThresholdEnvelopeDB(DC_OFFSET) {
|
m_overThresholdEnvelopeDB(DC_OFFSET) {
|
||||||
@ -42,12 +43,24 @@ void audio::algo::chunkware::Compressor::setRatio(double _ratio) {
|
|||||||
m_ratio = _ratio;
|
m_ratio = _ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::algo::chunkware::Compressor::initRuntime() {
|
void audio::algo::chunkware::Compressor::init() {
|
||||||
m_overThresholdEnvelopeDB = DC_OFFSET;
|
m_overThresholdEnvelopeDB = DC_OFFSET;
|
||||||
|
m_isConfigured = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<enum audio::format> audio::algo::chunkware::Compressor::getSupportedFormat() {
|
||||||
|
std::vector<enum audio::format> out = getNativeSupportedFormat();
|
||||||
|
out.push_back(audio::format_int16);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
void audio::algo::chunkware::Compressor::process(audio::format _format, void* _output, const void* _input, size_t _nbChunk, int8_t _nbChannel) {
|
std::vector<enum audio::format> audio::algo::chunkware::Compressor::getNativeSupportedFormat() {
|
||||||
|
std::vector<enum audio::format> out;
|
||||||
|
out.push_back(audio::format_double);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::algo::chunkware::Compressor::process(void* _output, const void* _input, size_t _nbChunk, int8_t _nbChannel, enum audio::format _format) {
|
||||||
// TODO : Check init ...
|
// TODO : Check init ...
|
||||||
if (_nbChannel != 1) {
|
if (_nbChannel != 1) {
|
||||||
AA_CHUNK_ERROR("Can not compress with Other than single channel: " << _nbChannel);
|
AA_CHUNK_ERROR("Can not compress with Other than single channel: " << _nbChannel);
|
||||||
@ -57,12 +70,16 @@ void audio::algo::chunkware::Compressor::process(audio::format _format, void* _o
|
|||||||
{
|
{
|
||||||
const int16_t* input = reinterpret_cast<const int16_t*>(_input);
|
const int16_t* input = reinterpret_cast<const int16_t*>(_input);
|
||||||
int16_t* output = reinterpret_cast<int16_t*>(_output);
|
int16_t* output = reinterpret_cast<int16_t*>(_output);
|
||||||
|
double vals[_nbChannel];
|
||||||
for (size_t iii=0; iii<_nbChunk ; ++iii) {
|
for (size_t iii=0; iii<_nbChunk ; ++iii) {
|
||||||
double val = input[iii];
|
for (int8_t kkk=0; kkk<_nbChannel ; ++kkk) {
|
||||||
val /= 32768.0;
|
vals[kkk] = double(input[iii*_nbChannel+kkk]) / 32768.0;
|
||||||
processMono(val);
|
}
|
||||||
val *= 32768.0;
|
processDouble(vals, vals, _nbChannel);
|
||||||
output[iii] = int16_t(std::avg(-32768.0, val*32768.0, 32767.0));
|
for (int8_t kkk=0; kkk<_nbChannel ; ++kkk) {
|
||||||
|
vals[kkk] *= 32768.0;
|
||||||
|
output[iii*_nbChannel+kkk] = int16_t(std::avg(-32768.0, vals[kkk], 32767.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -71,8 +88,7 @@ void audio::algo::chunkware::Compressor::process(audio::format _format, void* _o
|
|||||||
const double* input = reinterpret_cast<const double*>(_input);
|
const double* input = reinterpret_cast<const double*>(_input);
|
||||||
double* output = reinterpret_cast<double*>(_output);
|
double* output = reinterpret_cast<double*>(_output);
|
||||||
for (size_t iii=0; iii<_nbChunk ; ++iii) {
|
for (size_t iii=0; iii<_nbChunk ; ++iii) {
|
||||||
output[iii] = input[iii];
|
processDouble(&output[iii*_nbChannel], &input[iii*_nbChannel], _nbChannel);
|
||||||
processMono(output[iii]);
|
|
||||||
//AA_CHUNK_INFO(" in=" << input[iii] << " => " << output[iii]);
|
//AA_CHUNK_INFO(" in=" << input[iii] << " => " << output[iii]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,45 +100,17 @@ void audio::algo::chunkware::Compressor::process(audio::format _format, void* _o
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::algo::chunkware::Compressor::processMono(double& _in) {
|
void audio::algo::chunkware::Compressor::processDouble(double* _out, const double* _in, int8_t _nbChannel) {
|
||||||
double rect1 = std::abs(_in); // rectify input
|
double keyLink = 0;
|
||||||
rect1 += DC_OFFSET;
|
// get greater value;
|
||||||
double keydB = lin2dB(rect1);
|
for (int8_t iii=0; iii<_nbChannel; ++iii) {
|
||||||
// threshold
|
double absValue = std::abs(_in[iii]);
|
||||||
double overdB = keydB - m_threshdB; //delta over threshold
|
keyLink = std::max(keyLink, absValue);
|
||||||
if ( overdB < 0.0 ) {
|
|
||||||
overdB = 0.0;
|
|
||||||
}
|
}
|
||||||
// attack/release
|
processDouble(_out, _in, _nbChannel, keyLink);
|
||||||
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) {
|
void audio::algo::chunkware::Compressor::processDouble(double* _out, const double* _in, int8_t _nbChannel, double _keyLinked) {
|
||||||
// create sidechain
|
|
||||||
double rect1 = std::abs(_in1); // rectify input
|
|
||||||
double rect2 = std::abs(_in2);
|
|
||||||
/* if desired, one could use another EnvelopeDetector to smooth
|
|
||||||
* the rectified signal.
|
|
||||||
*/
|
|
||||||
double link = std::max(rect1, rect2); // link channels with greater of 2
|
|
||||||
process(_in1, _in2, link); // rest of process
|
|
||||||
}
|
|
||||||
|
|
||||||
void audio::algo::chunkware::Compressor::process(double& _in1, double& _in2, double _keyLinked) {
|
|
||||||
_keyLinked = std::abs(_keyLinked); // rectify (just in case)
|
|
||||||
// convert key to dB
|
// convert key to dB
|
||||||
_keyLinked += DC_OFFSET; // add DC offset to avoid log(0)
|
_keyLinked += DC_OFFSET; // add DC offset to avoid log(0)
|
||||||
double keydB = lin2dB(_keyLinked); // convert linear -> dB
|
double keydB = lin2dB(_keyLinked); // convert linear -> dB
|
||||||
@ -145,7 +133,8 @@ void audio::algo::chunkware::Compressor::process(double& _in1, double& _in2, dou
|
|||||||
double gr = overdB * (m_ratio - 1.0); // gain reduction (dB)
|
double gr = overdB * (m_ratio - 1.0); // gain reduction (dB)
|
||||||
gr = dB2lin(gr); // convert dB -> linear
|
gr = dB2lin(gr); // convert dB -> linear
|
||||||
// output gain
|
// output gain
|
||||||
_in1 *= gr; // apply gain reduction to input
|
for (int8_t iii=0; iii<_nbChannel; ++iii) {
|
||||||
_in2 *= gr;
|
_out[iii] = _in[iii] * gr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,9 +36,46 @@ namespace audio {
|
|||||||
namespace algo {
|
namespace algo {
|
||||||
namespace chunkware {
|
namespace chunkware {
|
||||||
class Compressor : public audio::algo::chunkware::AttRelEnvelope {
|
class Compressor : public audio::algo::chunkware::AttRelEnvelope {
|
||||||
|
protected:
|
||||||
|
bool m_isConfigured;
|
||||||
public:
|
public:
|
||||||
Compressor();
|
Compressor();
|
||||||
virtual ~Compressor() {}
|
virtual ~Compressor() {}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initialize the Algorithm
|
||||||
|
*/
|
||||||
|
virtual void init();
|
||||||
|
/**
|
||||||
|
* @brief Get list of format suported in input.
|
||||||
|
* @return list of supported format
|
||||||
|
*/
|
||||||
|
std::vector<enum audio::format> getSupportedFormat();
|
||||||
|
/**
|
||||||
|
* @brief Get list of algorithm format suported. No format convertion.
|
||||||
|
* @return list of supported format
|
||||||
|
*/
|
||||||
|
std::vector<enum audio::format> getNativeSupportedFormat();
|
||||||
|
/**
|
||||||
|
* @brief Main input algo process.
|
||||||
|
* @param[in,out] _output Output data.
|
||||||
|
* @param[in] _input Input data.
|
||||||
|
* @param[in] _nbChunk Number of chunk in the input buffer.
|
||||||
|
* @param[in] _nbChannel Number of channel in the stream.
|
||||||
|
* @param[in] _format Input data format.
|
||||||
|
*/
|
||||||
|
void process(void* _output, const void* _input, size_t _nbChunk, int8_t _nbChannel = 2, enum audio::format _format = audio::format_double);
|
||||||
|
protected:
|
||||||
|
virtual void processDouble(double* _out, const double* _in, int8_t _nbChannel);
|
||||||
|
void processDouble(double* _out, const double* _in, int8_t _nbChannel, double _value);
|
||||||
|
/*
|
||||||
|
void process(float* _out, const float* _in, int8_t _nbChannel);
|
||||||
|
void process(int16_16_t* _out, const int16_16_t* _in, int8_t _nbChannel);
|
||||||
|
void process(int16_32_t* _out, const int16_32_t* _in, int8_t _nbChannel);
|
||||||
|
void process(int24_32_t* _out, const int24_32_t* _in, int8_t _nbChannel);
|
||||||
|
void process(int32_32_t* _out, const int32_32_t* _in, int8_t _nbChannel);
|
||||||
|
void process(int32_64_t* _out, const int32_64_t* _in, int8_t _nbChannel);
|
||||||
|
*/
|
||||||
protected:
|
protected:
|
||||||
double m_threshdB;//!< threshold (dB)
|
double m_threshdB;//!< threshold (dB)
|
||||||
public:
|
public:
|
||||||
@ -53,18 +90,7 @@ namespace audio {
|
|||||||
virtual double getRatio() const {
|
virtual double getRatio() const {
|
||||||
return m_ratio;
|
return m_ratio;
|
||||||
}
|
}
|
||||||
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:
|
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);
|
|
||||||
// runtime variables
|
// runtime variables
|
||||||
double m_overThresholdEnvelopeDB; //!< over-threshold envelope (dB)
|
double m_overThresholdEnvelopeDB; //!< over-threshold envelope (dB)
|
||||||
};
|
};
|
||||||
|
@ -47,11 +47,12 @@ void audio::algo::chunkware::CompressorRms::initRuntime() {
|
|||||||
m_averageSuares = DC_OFFSET;
|
m_averageSuares = DC_OFFSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::algo::chunkware::CompressorRms::process(double& _in1, double& _in2) {
|
void audio::algo::chunkware::CompressorRms::processDouble(double* _out, const double* _in, int8_t _nbChannel) {
|
||||||
|
double sum = 0.0;
|
||||||
// create sidechain
|
// create sidechain
|
||||||
double inSq1 = _in1 * _in1; // square input
|
for (int8_t iii=0; iii<_nbChannel; ++iii) {
|
||||||
double inSq2 = _in2 * _in2;
|
sum += _in[iii] * _in[iii]; // square input
|
||||||
double sum = inSq1 + inSq2; // power summing
|
}
|
||||||
sum += DC_OFFSET; // DC offset, to prevent denormal
|
sum += DC_OFFSET; // DC offset, to prevent denormal
|
||||||
m_averager.run(sum, m_averageSuares); // average of squares
|
m_averager.run(sum, m_averageSuares); // average of squares
|
||||||
double rms = sqrt(m_averageSuares); // rms (sort of ...)
|
double rms = sqrt(m_averageSuares); // rms (sort of ...)
|
||||||
@ -62,5 +63,5 @@ void audio::algo::chunkware::CompressorRms::process(double& _in1, double& _in2)
|
|||||||
* giving comparable results.
|
* giving comparable results.
|
||||||
*/
|
*/
|
||||||
// rest of process
|
// rest of process
|
||||||
audio::algo::chunkware::Compressor::process(_in1, _in2, rms);
|
processDouble(_out, _in, _nbChannel, rms);
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,9 @@ namespace audio {
|
|||||||
virtual double getWindow() const {
|
virtual double getWindow() const {
|
||||||
return m_averager.getTc();
|
return m_averager.getTc();
|
||||||
}
|
}
|
||||||
// runtime process
|
public:
|
||||||
virtual void initRuntime(); // call before runtime (in resume())
|
virtual void init();
|
||||||
void process(double& _in1, double& _in2); // compressor runtime process
|
void processDouble(double* _out, const double* _in, int8_t _nbChannel);
|
||||||
protected:
|
protected:
|
||||||
audio::algo::chunkware::EnvelopeDetector m_averager; //!< averager
|
audio::algo::chunkware::EnvelopeDetector m_averager; //!< averager
|
||||||
double m_averageSuares; //!< average of squares
|
double m_averageSuares; //!< average of squares
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include <audio/algo/chunkware/debug.h>
|
#include <audio/algo/chunkware/debug.h>
|
||||||
|
|
||||||
audio::algo::chunkware::Limiter::Limiter() :
|
audio::algo::chunkware::Limiter::Limiter() :
|
||||||
|
m_isConfigured(false),
|
||||||
m_threshdB(0.0),
|
m_threshdB(0.0),
|
||||||
m_threshold(1.0),
|
m_threshold(1.0),
|
||||||
m_peakHold(0),
|
m_peakHold(0),
|
||||||
@ -39,8 +40,8 @@ audio::algo::chunkware::Limiter::Limiter() :
|
|||||||
m_bufferMask(BUFFER_SIZE-1),
|
m_bufferMask(BUFFER_SIZE-1),
|
||||||
m_cursor(0) {
|
m_cursor(0) {
|
||||||
setAttack(1.0);
|
setAttack(1.0);
|
||||||
|
m_outputBuffer.resize(1);
|
||||||
m_outputBuffer[0].resize(BUFFER_SIZE, 0.0);
|
m_outputBuffer[0].resize(BUFFER_SIZE, 0.0);
|
||||||
m_outputBuffer[ 1 ].resize(BUFFER_SIZE, 0.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::algo::chunkware::Limiter::setThreshold(double _dB) {
|
void audio::algo::chunkware::Limiter::setThreshold(double _dB) {
|
||||||
@ -64,13 +65,17 @@ void audio::algo::chunkware::Limiter::setSampleRate(double _sampleRate) {
|
|||||||
m_release.setSampleRate(_sampleRate);
|
m_release.setSampleRate(_sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::algo::chunkware::Limiter::initRuntime() {
|
void audio::algo::chunkware::Limiter::init(int8_t _nbChannel) {
|
||||||
m_peakTimer = 0;
|
m_peakTimer = 0;
|
||||||
m_maxPeak = m_threshold;
|
m_maxPeak = m_threshold;
|
||||||
m_overThresholdEnvelope = m_threshold;
|
m_overThresholdEnvelope = m_threshold;
|
||||||
m_cursor = 0;
|
m_cursor = 0;
|
||||||
m_outputBuffer[ 0 ].assign(BUFFER_SIZE, 0.0);
|
m_outputBuffer.resize(_nbChannel);
|
||||||
m_outputBuffer[ 1 ].assign(BUFFER_SIZE, 0.0);
|
for (int8_t iii=0; iii<_nbChannel; ++iii) {
|
||||||
|
m_outputBuffer[iii].resize(BUFFER_SIZE, 0.0);
|
||||||
|
m_outputBuffer[iii].assign(BUFFER_SIZE, 0.0);
|
||||||
|
}
|
||||||
|
m_isConfigured = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::algo::chunkware::FastEnvelope::setCoef() {
|
void audio::algo::chunkware::FastEnvelope::setCoef() {
|
||||||
@ -78,22 +83,41 @@ void audio::algo::chunkware::FastEnvelope::setCoef() {
|
|||||||
m_coefficient = std::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) {
|
std::vector<enum audio::format> audio::algo::chunkware::Limiter::getSupportedFormat() {
|
||||||
|
std::vector<enum audio::format> out = getNativeSupportedFormat();
|
||||||
|
out.push_back(audio::format_int16);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<enum audio::format> audio::algo::chunkware::Limiter::getNativeSupportedFormat() {
|
||||||
|
std::vector<enum audio::format> out;
|
||||||
|
out.push_back(audio::format_double);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::algo::chunkware::Limiter::process(void* _output, const void* _input, size_t _nbChunk, int8_t _nbChannel, enum audio::format _format) {
|
||||||
// TODO : Check init ...
|
// TODO : Check init ...
|
||||||
if (_nbChannel != 1) {
|
if (_nbChannel != m_outputBuffer.size()) {
|
||||||
AA_CHUNK_ERROR("Can not compress with Other than single channel: " << _nbChannel);
|
AA_CHUNK_ERROR("Can not compress with Other than nb channel configured ... channel: " << _nbChannel << " != " << m_outputBuffer.size());
|
||||||
|
}
|
||||||
|
if (m_isConfigured == false) {
|
||||||
|
AA_CHUNK_ERROR("Algo is not initialized...");
|
||||||
}
|
}
|
||||||
switch (_format) {
|
switch (_format) {
|
||||||
case audio::format_int16:
|
case audio::format_int16:
|
||||||
{
|
{
|
||||||
const int16_t* input = reinterpret_cast<const int16_t*>(_input);
|
const int16_t* input = reinterpret_cast<const int16_t*>(_input);
|
||||||
int16_t* output = reinterpret_cast<int16_t*>(_output);
|
int16_t* output = reinterpret_cast<int16_t*>(_output);
|
||||||
|
double vals[_nbChannel];
|
||||||
for (size_t iii=0; iii<_nbChunk ; ++iii) {
|
for (size_t iii=0; iii<_nbChunk ; ++iii) {
|
||||||
double val = input[iii];
|
for (int8_t kkk=0; kkk<_nbChannel ; ++kkk) {
|
||||||
val /= 32768.0;
|
vals[kkk] = double(input[iii*_nbChannel+kkk]) / 32768.0;
|
||||||
processMono(val);
|
}
|
||||||
val *= 32768.0;
|
processDouble(vals, vals, _nbChannel);
|
||||||
output[iii] = int16_t(std::avg(-32768.0, val*32768.0, 32767.0));
|
for (int8_t kkk=0; kkk<_nbChannel ; ++kkk) {
|
||||||
|
vals[kkk] *= 32768.0;
|
||||||
|
output[iii*_nbChannel+kkk] = int16_t(std::avg(-32768.0, vals[kkk], 32767.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -102,114 +126,24 @@ void audio::algo::chunkware::Limiter::process(audio::format _format, void* _outp
|
|||||||
const double* input = reinterpret_cast<const double*>(_input);
|
const double* input = reinterpret_cast<const double*>(_input);
|
||||||
double* output = reinterpret_cast<double*>(_output);
|
double* output = reinterpret_cast<double*>(_output);
|
||||||
for (size_t iii=0; iii<_nbChunk ; ++iii) {
|
for (size_t iii=0; iii<_nbChunk ; ++iii) {
|
||||||
output[iii] = input[iii];
|
processDouble(&output[iii*_nbChannel], &input[iii*_nbChannel], _nbChannel);
|
||||||
processMono(output[iii]);
|
|
||||||
//AA_CHUNK_INFO(" in=" << input[iii] << " => " << output[iii]);
|
//AA_CHUNK_INFO(" in=" << input[iii] << " => " << output[iii]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
AA_CHUNK_ERROR("Can not compress with unsupported format : " << _format);
|
AA_CHUNK_ERROR("Can not Limit with unsupported format : " << _format);
|
||||||
return;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void audio::algo::chunkware::Limiter::processDouble(double* _out, const double* _in, int8_t _nbChannel) {
|
||||||
|
double keyLink = 0;
|
||||||
|
// get greater value;
|
||||||
|
for (int8_t iii=0; iii<_nbChannel; ++iii) {
|
||||||
|
double absValue = std::abs(_in[iii]);
|
||||||
|
keyLink = std::max(keyLink, absValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
double rect1 = fabs(_in1); // rectify input
|
|
||||||
double rect2 = fabs(_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
|
// we always want to feed the sidechain AT LEATS the threshold value
|
||||||
if (keyLink < m_threshold) {
|
if (keyLink < m_threshold) {
|
||||||
keyLink = m_threshold;
|
keyLink = m_threshold;
|
||||||
@ -262,16 +196,19 @@ void audio::algo::chunkware::Limiter::process(double& _in1, double& _in2) {
|
|||||||
// (m_cursor - delay) & m_bufferMask gets sample from [delay] samples ago
|
// (m_cursor - delay) & m_bufferMask gets sample from [delay] samples ago
|
||||||
// m_bufferMask variable wraps index
|
// m_bufferMask variable wraps index
|
||||||
unsigned int delayIndex = (m_cursor - m_peakHold) & m_bufferMask;
|
unsigned int delayIndex = (m_cursor - m_peakHold) & m_bufferMask;
|
||||||
double delay1 = m_outputBuffer[0][delayIndex];
|
double delay[_nbChannel];
|
||||||
double delay2 = m_outputBuffer[1][delayIndex];
|
for (int8_t iii=0; iii<_nbChannel; ++iii) {
|
||||||
|
delay[iii] = m_outputBuffer[iii][delayIndex];
|
||||||
// load current buffer index and advance current index
|
// load current buffer index and advance current index
|
||||||
// m_bufferMask wraps m_cursor index
|
// m_bufferMask wraps m_cursor index
|
||||||
m_outputBuffer[0][m_cursor] = _in1;
|
m_outputBuffer[iii][m_cursor] = _in[iii];
|
||||||
m_outputBuffer[1][m_cursor] = _in2;
|
}
|
||||||
++m_cursor &= m_bufferMask;
|
++m_cursor &= m_bufferMask;
|
||||||
// output gain
|
// output gain
|
||||||
_in1 = delay1 * gR; // apply gain reduction to input
|
for (int8_t iii=0; iii<_nbChannel; ++iii) {
|
||||||
_in2 = delay2 * gR;
|
// apply gain reduction to input
|
||||||
|
_out[iii] = delay[iii] * gR;
|
||||||
|
}
|
||||||
/* REGARDING THE GAIN REDUCTION: Due to the logarithmic nature
|
/* REGARDING THE GAIN REDUCTION: Due to the logarithmic nature
|
||||||
* of the attack phase, the sidechain will never achieve "full"
|
* of the attack phase, the sidechain will never achieve "full"
|
||||||
* attack. (Actually, it is only guaranteed to achieve 99% of
|
* attack. (Actually, it is only guaranteed to achieve 99% of
|
||||||
|
@ -40,7 +40,7 @@ namespace audio {
|
|||||||
// class for faster attack/release
|
// class for faster attack/release
|
||||||
class FastEnvelope : public audio::algo::chunkware::EnvelopeDetector {
|
class FastEnvelope : public audio::algo::chunkware::EnvelopeDetector {
|
||||||
public:
|
public:
|
||||||
FastEnvelope(double _ms = 1.0, double _sampleRate = 44100.0) :
|
FastEnvelope(double _ms = 1.0, double _sampleRate = 48000.0) :
|
||||||
EnvelopeDetector(_ms, _sampleRate) {
|
EnvelopeDetector(_ms, _sampleRate) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -56,7 +56,41 @@ namespace audio {
|
|||||||
public:
|
public:
|
||||||
Limiter();
|
Limiter();
|
||||||
virtual ~Limiter() {}
|
virtual ~Limiter() {}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initialize the Algorithm
|
||||||
|
* @param[in] _nbChannel Number of channel in the stream.
|
||||||
|
*/
|
||||||
|
virtual void init(int8_t _nbChannel);
|
||||||
|
/**
|
||||||
|
* @brief Get list of format suported in input.
|
||||||
|
* @return list of supported format
|
||||||
|
*/
|
||||||
|
std::vector<enum audio::format> getSupportedFormat();
|
||||||
|
/**
|
||||||
|
* @brief Get list of algorithm format suported. No format convertion.
|
||||||
|
* @return list of supported format
|
||||||
|
*/
|
||||||
|
std::vector<enum audio::format> getNativeSupportedFormat();
|
||||||
|
/**
|
||||||
|
* @brief Main input algo process.
|
||||||
|
* @param[in,out] _output Output data.
|
||||||
|
* @param[in] _input Input data.
|
||||||
|
* @param[in] _nbChunk Number of chunk in the input buffer.
|
||||||
|
* @param[in] _nbChannel Number of channel in the stream.
|
||||||
|
* @param[in] _format Input data format.
|
||||||
|
*/
|
||||||
|
void process(void* _output, const void* _input, size_t _nbChunk, int8_t _nbChannel = 2, enum audio::format _format = audio::format_double);
|
||||||
|
protected:
|
||||||
|
void processDouble(double* _out, const double* _in, int8_t _nbChannel);
|
||||||
|
/*
|
||||||
|
void process(float* _out, const float* _in, int8_t _nbChannel);
|
||||||
|
void process(int16_16_t* _out, const int16_16_t* _in, int8_t _nbChannel);
|
||||||
|
void process(int16_32_t* _out, const int16_32_t* _in, int8_t _nbChannel);
|
||||||
|
void process(int24_32_t* _out, const int24_32_t* _in, int8_t _nbChannel);
|
||||||
|
void process(int32_32_t* _out, const int32_32_t* _in, int8_t _nbChannel);
|
||||||
|
void process(int32_64_t* _out, const int32_64_t* _in, int8_t _nbChannel);
|
||||||
|
*/
|
||||||
protected:
|
protected:
|
||||||
double m_threshdB; //!< threshold (dB)
|
double m_threshdB; //!< threshold (dB)
|
||||||
public:
|
public:
|
||||||
@ -91,49 +125,35 @@ namespace audio {
|
|||||||
protected:
|
protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// sample rate dependencies
|
/**
|
||||||
|
* @brief Set sample rate.
|
||||||
|
* @param[in] _sampleRate New sample rate value.
|
||||||
|
*/
|
||||||
virtual void setSampleRate(double _sampleRate);
|
virtual void setSampleRate(double _sampleRate);
|
||||||
|
/**
|
||||||
|
* @brief Get current sample rate.
|
||||||
|
* @return Vlue of the sample rate.
|
||||||
|
*/
|
||||||
virtual double getSampleRate() {
|
virtual double getSampleRate() {
|
||||||
return m_attack.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:
|
|
||||||
/*
|
|
||||||
void process(double* _out, const double* _in, int8_t _nbChannel);
|
|
||||||
void process(float* _out, const float* _in, int8_t _nbChannel);
|
|
||||||
void process(int16_16_t* _out, const int16_16_t* _in, int8_t _nbChannel);
|
|
||||||
void process(int16_32_t* _out, const int16_32_t* _in, int8_t _nbChannel);
|
|
||||||
void process(int24_32_t* _out, const int24_32_t* _in, int8_t _nbChannel);
|
|
||||||
void process(int32_32_t* _out, const int32_32_t* _in, int8_t _nbChannel);
|
|
||||||
void process(int32_64_t* _out, const int32_64_t* _in, int8_t _nbChannel);
|
|
||||||
*/
|
|
||||||
// limiter runtime process
|
|
||||||
void process(double& _in1, double& _in2);
|
|
||||||
void processMono(double& _in);
|
|
||||||
private:
|
private:
|
||||||
// transfer function
|
// transfer function
|
||||||
|
|
||||||
double m_threshold; //!< threshold (linear)
|
double m_threshold; //!< threshold (linear)
|
||||||
// max peak
|
// max peak
|
||||||
|
uint32_t m_peakTimer; //!< peak hold timer
|
||||||
unsigned int m_peakTimer; //!< peak hold timer
|
|
||||||
double m_maxPeak; //!< max peak
|
double m_maxPeak; //!< max peak
|
||||||
// attack/release envelope
|
// attack/release envelope
|
||||||
audio::algo::chunkware::FastEnvelope m_attack; //!< attack
|
audio::algo::chunkware::FastEnvelope m_attack; //!< attack
|
||||||
audio::algo::chunkware::FastEnvelope m_release; //!< release
|
audio::algo::chunkware::FastEnvelope m_release; //!< release
|
||||||
|
|
||||||
|
|
||||||
double m_overThresholdEnvelope; //!< over-threshold envelope (linear)
|
double m_overThresholdEnvelope; //!< over-threshold envelope (linear)
|
||||||
// buffer
|
// buffer
|
||||||
// BUFFER_SIZE default can handle up to ~10ms at 96kHz
|
// BUFFER_SIZE default can handle up to ~10ms at 96kHz
|
||||||
// change this if you require more
|
// change this if you require more
|
||||||
static const int BUFFER_SIZE = 1024; //!< buffer size (always a power of 2!)
|
static const int BUFFER_SIZE = 1024; //!< buffer size (always a power of 2!)
|
||||||
unsigned int m_bufferMask; //!< buffer mask
|
uint32_t m_bufferMask; //!< buffer mask
|
||||||
unsigned int m_cursor; //!< cursor
|
uint32_t m_cursor; //!< cursor
|
||||||
std::vector< double > m_outputBuffer[2]; //!< output buffer
|
std::vector<std::vector<double> > m_outputBuffer; //!< output buffer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ int main(int _argc, const char** _argv) {
|
|||||||
algo.setThreshold(0);
|
algo.setThreshold(0);
|
||||||
algo.setAttack(0.1);
|
algo.setAttack(0.1);
|
||||||
algo.setRelease(2);
|
algo.setRelease(2);
|
||||||
algo.initRuntime();
|
algo.init(1);
|
||||||
int32_t lastPourcent = -1;
|
int32_t lastPourcent = -1;
|
||||||
for (int32_t iii=0; iii<output.size()/blockSize; ++iii) {
|
for (int32_t iii=0; iii<output.size()/blockSize; ++iii) {
|
||||||
if (lastPourcent != 100*iii / (output.size()/blockSize)) {
|
if (lastPourcent != 100*iii / (output.size()/blockSize)) {
|
||||||
@ -120,7 +120,7 @@ int main(int _argc, const char** _argv) {
|
|||||||
APPL_VERBOSE("Process : " << iii*blockSize << "/" << int32_t(output.size()/blockSize)*blockSize);
|
APPL_VERBOSE("Process : " << iii*blockSize << "/" << int32_t(output.size()/blockSize)*blockSize);
|
||||||
}
|
}
|
||||||
std11::chrono::steady_clock::time_point timeStart = std11::chrono::steady_clock::now();
|
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);
|
algo.process(&output[iii*blockSize], &inputData[iii*blockSize], blockSize, 1, audio::format_double);
|
||||||
if (perf == true) {
|
if (perf == true) {
|
||||||
std11::chrono::steady_clock::time_point timeEnd = std11::chrono::steady_clock::now();
|
std11::chrono::steady_clock::time_point timeEnd = std11::chrono::steady_clock::now();
|
||||||
std11::chrono::nanoseconds time = timeEnd - timeStart;
|
std11::chrono::nanoseconds time = timeEnd - timeStart;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user