-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathAudioEffectCompressor2_F32.cpp
148 lines (136 loc) · 5.55 KB
/
AudioEffectCompressor2_F32.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/* 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];
}
}