You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OpenAudio_ArduinoLibrary/AudioEffectCompressor2_F32.cpp

149 lines
5.6 KiB

/* AudioEffectCompressor2_F32.cpp
*
* Bob Larkin W7PUA 22 January 2021
* See AudioEffectCompressor2_F32.h for details
*
* MIT License. Use at your own risk.
*/
#include "AudioEffectCompressor2_F32.h"
/* See https://github.com/Tympan/Tympan_Library/blob/master/src/AudioCalcGainWDRC_F32.h
* Dr Paul Beckmann
* https://community.arm.com/tools/f/discussions/4292/cmsis-dsp-new-functionality-proposal/22621#22621
* Fast approximation to the log2() function. It uses a two step
* process. First, it decomposes the floating-point number into
* a fractional component F and an exponent E. The fraction component
* is used in a polynomial approximation and then the exponent added
* to the result. A 3rd order polynomial is used and the result
* when computing db20() is accurate to 7.984884e-003 dB. Y is log2(X)
*/
float v2DB_Approx(float volts) {
float Y, F;
int E;
// This is the approximation to log2()
F = frexpf(volts, &E); // first separate power of 2;
// Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E;
Y = 1.23149591; //C[0]
Y *= F;
Y += -4.11852516f; //C[1]
Y *= F;
Y += 6.02197014f; //C[2]
Y *= F;
Y += -3.13396450f; //C[3]
Y += E;
// Convert to dB = 20 Log10(volts)
return 6.020599f * Y; // (20.0f/log2(10.0))*Y;
}
// Accelerate the powf(10.0,x) function (from Chip's single slope compressor)
float pow10f(float x) {
//return powf(10.0f,x) //standard, but slower
return expf(2.30258509f*x); //faster: exp(log(10.0f)*x)
}
// begin()
void AudioEffectCompressor2_F32::begin(void) {
for(int kk =0; kk<5; kk++) // Keeps division out of update()
slope[kk] = 1.0f/curve0.compressionRatio[kk];
outKneeDB[0] = curve0.marginDB; // Start at top
for(int kk=1; kk<5; kk++) {
outKneeDB[kk] = outKneeDB[kk-1] - (curve0.kneeDB[kk-1] -
curve0.kneeDB[kk])/curve0.compressionRatio[kk-1];
}
firstIndex = 4; // Start at bottom
for(int kk=4; kk>0; kk--) {
if(curve0.kneeDB[kk] >= -500.0) {
firstIndex = kk;
break;
}
}
/* for(int kk=0; kk<5; kk++) {
Serial.print(kk);
Serial.print(" <--k outKneeDB[k]--> ");
Serial.println(outKneeDB[kk]);
}
Serial.print("firstIndex--> ");
Serial.println(firstIndex);
*/
}
// update()
void AudioEffectCompressor2_F32::update(void) {
float vAbs, vPeak;
float vInDB = 0.0f;
float vOutDB = 0.0f;
float targetGain;
// Receive the input audio data
audio_block_f32_t *block = AudioStream_F32::receiveWritable_f32();
if (!block) return;
// Allocate memory for the output
audio_block_f32_t *out_block = AudioStream_F32::allocate_f32();
if (!out_block) {
release(block);
return;
}
// Find the smoothed envelope, target gain and compressed output
vPeak = vPeakSave;
for (int k=0; k<block->length; k++) {
vAbs = (block->data[k] >= 0.0f) ? block->data[k] : -block->data[k];
if (vAbs >= vPeak) { // Attack (rising level)
vPeak = alpha * vPeak + (oneMinusAlpha) * vAbs;
} else { // Release (decay for falling level)
vPeak = beta * vPeak;
}
// Convert to dB
// At all levels and quite frequency flat, this under estimates by about 1.05 dB
vInDB = v2DB_Approx(vPeak) + 1.05f;
if(vInDB > vInMaxDB) vInMaxDB = vInDB; // For reporting back
// Find gain point. Don't look below first segment firstIndex.
for(int kk=firstIndex; kk>=0; kk--) {
if( vInDB<=curve0.kneeDB[kk] || kk==0 ) {
vOutDB = outKneeDB[kk] + slope[kk]*(vInDB - curve0.kneeDB[kk]);
break;
}
}
// Convert the needed gain back to a voltage ratio 10^(db/20)
targetGain = pow10f(0.05f*(vOutDB - vInDB));
// And apply target gain to signal stream from the delayed data. The
// delay buffer is circular because of delayBufferMask and length 2^m m<=8.
out_block->data[k] = targetGain * delayData[(k + in_index) & delayBufferMask];
if(printIO) {
Serial.print(block->data[k],6);
Serial.print("," );
Serial.print(delayData[(k + in_index) & delayBufferMask],6);
Serial.print("," );
Serial.println(targetGain);
}
// Put the new data into the delay line, delaySize positions ahead.
// If delaySize==256, this will be the same location as we just got data from.
delayData[(k + in_index + delaySize) & delayBufferMask] = block->data[k];
}
vPeakSave = vPeak; // save last vPeak for next time
sampleInputDB = vInDB; // Last values for get...() functions
sampleGainDB = vOutDB - vInDB;
// transmit the block and release memory
AudioStream_F32::release(block);
AudioStream_F32::transmit(out_block); // send the FIR output
AudioStream_F32::release(out_block);
// Update pointer in_index to delay line for next 128 update
in_index = (in_index + block->length) & delayBufferMask;
} // End update()
// Sets a new compression curve by transferring structure
void AudioEffectCompressor2_F32::setCompressionCurve(struct compressionCurve *_cCurve) {
curve0.marginDB = _cCurve->marginDB;
curve0.offsetDB = _cCurve->offsetDB;
for(int kk=0; kk<5; kk++) {
// Also, adjust the input levels for offsetDB value
curve0.kneeDB[kk] = _cCurve->kneeDB[kk] - curve0.offsetDB;
curve0.compressionRatio[kk] = _cCurve->compressionRatio[kk];
}
}