-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathAudioEffectCompressor2_F32.h
225 lines (209 loc) · 11 KB
/
AudioEffectCompressor2_F32.h
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/*
* AudioEffectCompressor2_F32.h
*
* Bob Larkin W7PUA 11 December 2020
*
* This is a general purpose audio compressor block (C++ class). It works by determining
* the average input of the input signal, and based on a pre-determined curve,
* changes the gain going through the block.
* A good discussion is the Wikipedia page:
* https://en.wikipedia.org/wiki/Dynamic_range_compression
* This compressor includes up to 5 dB/dB line segments allowing for most of the
* features listed. These include
* Multi segment compression curves, up to 5
* Limiting
* Approximation to "soft knees"
* Expansion for suppressing low-level artifacts
* Anticipation
* Scale offset for use such as hearing-aid audiology
* This is derived from the WDRC compressor. Chip Audette (OpenAudio) Feb 2017
* Which was derived From: WDRC_circuit from CHAPRO from BTNRC: https://github.com/BTNRH/chapro
* As of Feb 2017, CHAPRO license is listed as "Creative Commons?"
*
* MIT License. Use at your own risk.
*/
/* Compressor #2. Amplifies input signals by varying amoounts depending
* on the signal level. This is controlled by up to 5 line segments specified
* by a point at the highest input level for the line along with a compression ratio
* (1/slope) for the line segment. This can provide limiting at the highest inputs
* by setting compressionRatio[0]=1000.0 (i.e., some big value). Expansion at the
* lw levels provides a squelch action, using a very small compression
* ratio like 0.01.
*
* A special case is the [0] segment, that continues at the same slope for inputs up
* to any level. For this the kneeDB[0] can be the expected 0.0 or other values
* on that line. The output level for kneeDB[0] is the input variable, marginDB.
* This allows gain control near clipping but below. marginDB is typically 1 or 2 dB.
*
Vout dB
|
|
0.0 + kneeDB[0]
marg + kneeDB[1] @********************
| @************ 1:compressionRatio[0]
| ****
| ***
| ***
| *** 1:compressionRatio[1]
| kneeDB[2] ***
| @***
| *
| *
| *
| *
| * 1:compressionRatio[2]
| *
| * === Vout in dB vs. Vin in dB ===
|* Three segment example
* Knees (breakpoints) are shown with '@'
*| compressionRatio[] are ratio of: input change (in dB):output change in dB
* |
* |________|___________________|____________________________|________ vIn dB
k1 k2 0.0
* The graph shows the changes in gain on a log or dB scale. A compressionRatio
* of 1 represents a constant gain with level. When the compressionRatio is greater
* than 1, say 2.0, the voltage gain is decreasing as the input level increases.
*
* vInDB refers to the time averaged envelope voltage.
* The zero reference is the full ADC range output. This is ±1.0
* peak or 0.707 RMS in F32 terminology.
*
* The curve is for gainOffsetDB = 0.0. This parameter raises and lowers the
* input scale for the kneeDB[] parameter.
*
* Timing: For 44.1 kHz sample rate and 256 samples per update, the update( ) time
* runs 240 to 270 icroseconds using Teensy 3.6.
*/
#ifndef _AUDIO_EFFECT_COMPRESSOR2_F32_H
#define _AUDIO_EFFECT_COMPRESSOR2_F32_H
#include <Arduino.h>
#include <AudioStream_F32.h>
// The following 3 defines are simplified implementations for common uses.
// They replace the begin() function that is otherwise required.
// See testCompressor2.ino example for how to use these defines.
// None of these support offsetting the input scale as done in Tympan.
/* limiterBegin(pointerObject, float marDB, float linearInDB) has 2 segments.
* It is linear up to an input of linearInDB (typically -15.0f) and
* then virtually limits for higher input levels. The output level at
* this point is marDB, the margin to prevent clipping, like -2 dB.
* This is not a clipper with waveform distortion, but rather decreases
* the gain, dB for dB, as the input increases in the limiter region.
* pobject is a pointer to the INO AudioEffectCompressor2_F32 object.
* This function replaces begin() for the AudioEffectCompressor2_F32 object.
*/
#define limiterBegin(pobject, marDB, linearInDB) struct compressionCurve _curv={marDB,0.0f,{0.0,linearInDB,-1000.0f,-1000.0f,-1000.0f},{100.0,1.0f,1.0f,1.0f,1.0f}}; pobject->setCompressionCurve(&_curv); pobject->begin();
/* basicCompressorBegin has a 3 segments. It is linear up to an input linearInDB
* and then decreases gain according to compressionRatioDB up to an input -10 dB where it
* is almost limited, with an increase of output level of 1 dB for a 10 dB increase
* in input level. The output level at full input is 1 dB below full output.
* This function replaces begin() for the AudioEffectCompressor2_F32 object.
*/
#define basicCompressorBegin(pobject, linearInDB, compressionRatio) struct compressionCurve _curv={-1.0,0.0f,{0.0,-10.0f,linearInDB,-1000.0f,-1000.0f},{10.0f,compressionRatio,1.0f,1.0f,1.0f}}; pobject->setCompressionCurve(&_curv); pobject->begin();
/* squelchCompressorBegin is similar to basicCompression above, except that there is
* an expansion region for low levels. So, the call defines the four regions in
* terms of the input levels. squelchInDB sets the lowest input level
* before the squelching effect starts. */
#define squelchCompressorBegin(pobject, squelchInDB, linearInDB, compressionInDB, compressionRatio) struct compressionCurve _curv={-1.0,0.0f,{0.0,compressionInDB,linearInDB,squelchInDB,-1000.0f},{10.0,compressionRatio,1.0f,0.1f,1.0f}}; pobject->setCompressionCurve(&_curv); pobject->begin();
// Basic definition of compression curve:
struct compressionCurve {
float marginDB;
float offsetDB;
float kneeDB[5];
float compressionRatio[5];
};
// ---------------------------------------------------------------------------
class AudioEffectCompressor2_F32 : public AudioStream_F32
{
//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName: Compressor2
public:
AudioEffectCompressor2_F32(void): AudioStream_F32(1, inputQueueArray) {
setAttackReleaseSec(0.005f, 0.100f);
}
AudioEffectCompressor2_F32(const AudioSettings_F32 &settings): AudioStream_F32(1, inputQueueArray) {
//setSampleRate_Hz(settings.sample_rate_Hz);
setAttackReleaseSec(0.005f, 0.100f);
}
virtual void update(void);
void begin(void);
void setCompressionCurve(struct compressionCurve*);
// A delay of 256 samples is 256/44100 = 0.0058 sec = 5.8 mSec
void setDelayBufferSize(int16_t _delaySize) { // Any power of 2, i.e., 256, 128, 64, etc.
delaySize = _delaySize;
delayBufferMask = _delaySize - 1;
in_index = 0;
}
void printOn(bool _printIO) { printIO = _printIO; } // Diagnostics ONLY. Not for general INO
float getCurrentInputDB(void) { return sampleInputDB; }
float getCurrentGainDB(void) { return sampleGainDB; }
float getvInMaxDB(void) {
float vRet = vInMaxDB;
vInMaxDB = -1000.0f; // Reset for next max measure
return vRet;
}
//convert time constants from seconds to unitless parameters, from CHAPRO, agc_prepare.c
void setAttackReleaseSec(const float atk_sec, const float rel_sec) {
// convert ANSI attack & release times to filter time constants
float ansi_atk = atk_sec * sample_rate_Hz / 2.425f;
float ansi_rel = rel_sec * sample_rate_Hz / 1.782f;
alpha = (float) (ansi_atk / (1.0f + ansi_atk));
oneMinusAlpha = 1.0f - alpha;
beta = (float) (ansi_rel / (1.0f + ansi_rel));
}
private:
audio_block_f32_t *inputQueueArray[1];
float delayData[256]; // The circular delay line for the signal
uint16_t in_index = 0; // Pointer to next block update entry
// And a mask to make the circular buffer limit to a power of 2
uint16_t delayBufferMask = 0X00FF;
uint16_t delaySize = 256;
float sample_rate_Hz = 44100;
float attackSec = 0.005f; // Q: Can this be reduced with the delay line added to the signal path??
float releaseSec = 0.100f;
// This alpha, beta for 5 ms attack, 100ms release, about 0.07 dB max ripple at 1000 Hz
float alpha = 0.98912216f;
float oneMinusAlpha = 0.01087784f;
float beta = 0.9995961f;
/* Definition of the compression curve.
* Input and output are normally referenced to the full scale range (-1.0 to 1.0 in float).
* The full scale point is called 0 dB.
* There are 5 slopes, specified by the top input level for each line segment (kneeDB[])
* and the Compression Ratio (1/slope) of the segment (compressionRatio[]).
* These are numbered from the highest to the lowest allowing
* the number of segments to be adjusted, i.e., there is always a segment 0
* but there may not be a segment 3 and 4, for instance. This becomes very flexible,
* as there can be, say, compression for top levels and expansion for very low levels.
*
* A special case is segment 0. On a plot of output dB vs input db, an input
* level of kneeDB[0] produces an output of marginDB. A typical marginDB might
* be -1.0 or -2.0. This margin allows controlling the gain without clipping in the DAC.
*
* This structure can have multiple curve structures like cCurve that can be used by
* cmpr1.setCompressionCurve(&cCurve);
* cmpr1.begin();
*
* Unused segments can have knees like kneeDB[4] at, say -1000.0f.
* Values below -500 will slightly speed up the update() function. if the bottom
* two segments are not used, kneeDB[3] would also be set to -1000.0f amd so forth.
*
* Finally, there is a variable offsetDB that allows for a shift in the input scale.
* It simply shifts the definition input levels, in dB.
* This allows converting from DSP scales that have 0dB at full DSP scale
* to auditory scales such as are used in the Tympan library. An offsetDB=119.0
* would allow all inputs to be in "SPL" units with a maximum input value of 119.
*/
struct compressionCurve curve0 = { -2.0f, 0.0f, // margin, offset
{0.0f, -10.0f, -20.0f, -30.0f, -1000.0f}, // kneeDB[]
{ 100.0f, 2.0f, 1.5f, 1.0f, 1.0f} }; // compressionRatio
// VoutDB at each knee, needed to find gain; determined at begin():
float outKneeDB[5]={-2.0f, -3.0f, -8.0f -978.0f, -978.0f};
// slopes are 1/compressionRatio determined at begin() to save update{} time
float slope[5] = {0.01f, 0.5f, 0.666667f, 1.0f, 1.0f};
// Save time in update() by ignoring unused (low level) segments:
uint16_t firstIndex = 3;
float vPeakSave = 0.0f;
float vInMaxDB = -1000.0f; // Only for reporting
bool printIO = false; // Diagnostics Only
float sampleInputDB, sampleGainDB;
};
#endif