SDRAngel  4.11.5
Developer docs for <a href="https://github.com/f4exb/sdrangel">SDRangel<\a>, an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware.
audiocompressorsnd.cpp
Go to the documentation of this file.
1 // Copyright (C) 2019 F4EXB //
3 // written by Edouard Griffiths //
4 // //
5 // Audio compressor based on sndfilter by Sean Connelly (@voidqk) //
6 // https://github.com/voidqk/sndfilter //
7 // //
8 // Sample by sample interface to facilitate integration in SDRangel modulators. //
9 // Uses mono samples (just floats) //
10 // //
11 // This program is free software; you can redistribute it and/or modify //
12 // it under the terms of the GNU General Public License as published by //
13 // the Free Software Foundation as version 3 of the License, or //
14 // (at your option) any later version. //
15 // //
16 // This program is distributed in the hope that it will be useful, //
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
19 // GNU General Public License V3 for more details. //
20 // //
21 // You should have received a copy of the GNU General Public License //
22 // along with this program. If not, see <http://www.gnu.org/licenses/>. //
24 
25 #include <algorithm>
26 #include "audiocompressorsnd.h"
27 
28 
30 {
31  m_sampleIndex = 0;
33 }
34 
36 {}
37 
39 {
41  m_rate,
42  m_pregain,
44  m_knee,
45  m_ratio,
46  m_attack,
47  m_release,
48  m_predelay,
53  m_postgain,
54  m_wet
55  );
56 }
57 
58 float AudioCompressorSnd::compress(float sample)
59 {
60  float compressedSample;
61 
63  {
65  m_sampleIndex = 0;
66  }
67 
68  compressedSample = m_processedBuffer[m_sampleIndex];
70  m_sampleIndex++;
71 
72  return compressedSample;
73 }
74 
75 // populate the compressor state with advanced parameters
77  // these parameters are the same as the simple version above:
78  int rate, float pregain, float threshold, float knee, float ratio, float attack, float release,
79  // these are the advanced parameters:
80  float predelay, // seconds, length of the predelay buffer [0 to 1]
81  float releasezone1, // release zones should be increasing between 0 and 1, and are a fraction
82  float releasezone2, // of the release time depending on the input dB -- these parameters define
83  float releasezone3, // the adaptive release curve, which is discussed in further detail in the
84  float releasezone4, // demo: adaptive-release-curve.html
85  float postgain, // dB, amount of gain to apply after compression [0 to 100]
86  float wet) // amount to apply the effect [0 completely dry to 1 completely wet]
87 {
88  // setup the predelay buffer
89  int delaybufsize = rate * predelay;
90 
91  if (delaybufsize < 1)
92  {
93  delaybufsize = 1;
94  }
95  else if (delaybufsize > AUDIOCOMPRESSORSND_SF_COMPRESSOR_MAXDELAY)
96  {
98  std::fill(delaybuf, delaybuf+delaybufsize, 0.0f);
99  }
100 
101  // useful values
102  float linearpregain = db2lin(pregain);
103  float linearthreshold = db2lin(threshold);
104  float slope = 1.0f / ratio;
105  float attacksamples = rate * attack;
106  float attacksamplesinv = 1.0f / attacksamples;
107  float releasesamples = rate * release;
108  float satrelease = 0.0025f; // seconds
109  float satreleasesamplesinv = 1.0f / ((float)rate * satrelease);
110  float dry = 1.0f - wet;
111 
112  // metering values (not used in core algorithm, but used to output a meter if desired)
113  float metergain = 1.0f; // gets overwritten immediately because gain will always be negative
114  float meterfalloff = 0.325f; // seconds
115  float meterrelease = 1.0f - expf(-1.0f / ((float)rate * meterfalloff));
116 
117  // calculate knee curve parameters
118  float k = 5.0f; // initial guess
119  float kneedboffset = 0.0f;
120  float linearthresholdknee = 0.0f;
121 
122  if (knee > 0.0f) // if a knee exists, search for a good k value
123  {
124  float xknee = db2lin(threshold + knee);
125  float mink = 0.1f;
126  float maxk = 10000.0f;
127 
128  // search by comparing the knee slope at the current k guess, to the ideal slope
129  for (int i = 0; i < 15; i++)
130  {
131  if (kneeslope(xknee, k, linearthreshold) < slope) {
132  maxk = k;
133  } else {
134  mink = k;
135  }
136 
137  k = sqrtf(mink * maxk);
138  }
139 
140  kneedboffset = lin2db(kneecurve(xknee, k, linearthreshold));
141  linearthresholdknee = db2lin(threshold + knee);
142  }
143 
144  // calculate a master gain based on what sounds good
145  float fulllevel = compcurve(1.0f, k, slope, linearthreshold, linearthresholdknee, threshold, knee, kneedboffset);
146  float mastergain = db2lin(postgain) * powf(1.0f / fulllevel, 0.6f);
147 
148  // calculate the adaptive release curve parameters
149  // solve a,b,c,d in `y = a*x^3 + b*x^2 + c*x + d`
150  // interescting points (0, y1), (1, y2), (2, y3), (3, y4)
151  float y1 = releasesamples * releasezone1;
152  float y2 = releasesamples * releasezone2;
153  float y3 = releasesamples * releasezone3;
154  float y4 = releasesamples * releasezone4;
155  float a = (-y1 + 3.0f * y2 - 3.0f * y3 + y4) / 6.0f;
156  float b = y1 - 2.5f * y2 + 2.0f * y3 - 0.5f * y4;
157  float c = (-11.0f * y1 + 18.0f * y2 - 9.0f * y3 + 2.0f * y4) / 6.0f;
158  float d = y1;
159 
160  // save everything
161  this->metergain = 1.0f; // large value overwritten immediately since it's always < 0
162  this->meterrelease = meterrelease;
163  this->threshold = threshold;
164  this->knee = knee;
165  this->wet = wet;
166  this->linearpregain = linearpregain;
167  this->linearthreshold = linearthreshold;
168  this->slope = slope;
169  this->attacksamplesinv = attacksamplesinv;
170  this->satreleasesamplesinv = satreleasesamplesinv;
171  this->dry = dry;
172  this->k = k;
173  this->kneedboffset = kneedboffset;
174  this->linearthresholdknee = linearthresholdknee;
175  this->mastergain = mastergain;
176  this->a = a;
177  this->b = b;
178  this->c = c;
179  this->d = d;
180  this->detectoravg = 0.0f;
181  this->compgain = 1.0f;
182  this->maxcompdiffdb = -1.0f;
183  this->delaybufsize = delaybufsize;
184  this->delaywritepos = 0;
185  this->delayreadpos = delaybufsize > 1 ? 1 : 0;
186 }
187 
188 void AudioCompressorSnd::sf_compressor_process(AudioCompressorSnd::CompressorState *state, int size, float *input, float *output)
189 {
190  // pull out the state into local variables
191  float metergain = state->metergain;
192  float meterrelease = state->meterrelease;
193  float threshold = state->threshold;
194  float knee = state->knee;
195  float linearpregain = state->linearpregain;
196  float linearthreshold = state->linearthreshold;
197  float slope = state->slope;
198  float attacksamplesinv = state->attacksamplesinv;
199  float satreleasesamplesinv = state->satreleasesamplesinv;
200  float wet = state->wet;
201  float dry = state->dry;
202  float k = state->k;
203  float kneedboffset = state->kneedboffset;
204  float linearthresholdknee = state->linearthresholdknee;
205  float mastergain = state->mastergain;
206  float a = state->a;
207  float b = state->b;
208  float c = state->c;
209  float d = state->d;
210  float detectoravg = state->detectoravg;
211  float compgain = state->compgain;
212  float maxcompdiffdb = state->maxcompdiffdb;
213  int delaybufsize = state->delaybufsize;
214  int delaywritepos = state->delaywritepos;
215  int delayreadpos = state->delayreadpos;
216  float *delaybuf = state->delaybuf;
217 
218  int samplesperchunk = AUDIOCOMPRESSORSND_SF_COMPRESSOR_SPU;
219  int chunks = size / samplesperchunk;
220  float ang90 = (float)M_PI * 0.5f;
221  float ang90inv = 2.0f / (float)M_PI;
222  int samplepos = 0;
224 
225  for (int ch = 0; ch < chunks; ch++)
226  {
227  detectoravg = fixf(detectoravg, 1.0f);
228  float desiredgain = detectoravg;
229  float scaleddesiredgain = asinf(desiredgain) * ang90inv;
230  float compdiffdb = lin2db(compgain / scaleddesiredgain);
231 
232  // calculate envelope rate based on whether we're attacking or releasing
233  float enveloperate;
234  if (compdiffdb < 0.0f)
235  { // compgain < scaleddesiredgain, so we're releasing
236  compdiffdb = fixf(compdiffdb, -1.0f);
237  maxcompdiffdb = -1; // reset for a future attack mode
238  // apply the adaptive release curve
239  // scale compdiffdb between 0-3
240  float x = (clampf(compdiffdb, -12.0f, 0.0f) + 12.0f) * 0.25f;
241  float releasesamples = adaptivereleasecurve(x, a, b, c, d);
242  enveloperate = db2lin(spacingdb / releasesamples);
243  }
244  else
245  { // compresorgain > scaleddesiredgain, so we're attacking
246  compdiffdb = fixf(compdiffdb, 1.0f);
247  if (maxcompdiffdb == -1 || maxcompdiffdb < compdiffdb)
248  maxcompdiffdb = compdiffdb;
249  float attenuate = maxcompdiffdb;
250  if (attenuate < 0.5f)
251  attenuate = 0.5f;
252  enveloperate = 1.0f - powf(0.25f / attenuate, attacksamplesinv);
253  }
254 
255  // process the chunk
256  for (int chi = 0; chi < samplesperchunk; chi++, samplepos++,
257  delayreadpos = (delayreadpos + 1) % delaybufsize,
258  delaywritepos = (delaywritepos + 1) % delaybufsize)
259  {
260 
261  float inputL = input[samplepos] * linearpregain;
262  delaybuf[delaywritepos] = inputL;
263 
264  inputL = absf(inputL);
265  float inputmax = inputL;
266 
267  float attenuation;
268  if (inputmax < 0.0001f)
269  attenuation = 1.0f;
270  else
271  {
272  float inputcomp = compcurve(inputmax, k, slope, linearthreshold,
273  linearthresholdknee, threshold, knee, kneedboffset);
274  attenuation = inputcomp / inputmax;
275  }
276 
277  float rate;
278  if (attenuation > detectoravg)
279  { // if releasing
280  float attenuationdb = -lin2db(attenuation);
281  if (attenuationdb < 2.0f)
282  attenuationdb = 2.0f;
283  float dbpersample = attenuationdb * satreleasesamplesinv;
284  rate = db2lin(dbpersample) - 1.0f;
285  }
286  else
287  rate = 1.0f;
288 
289  detectoravg += (attenuation - detectoravg) * rate;
290  if (detectoravg > 1.0f)
291  detectoravg = 1.0f;
292  detectoravg = fixf(detectoravg, 1.0f);
293 
294  if (enveloperate < 1) // attack, reduce gain
295  compgain += (scaleddesiredgain - compgain) * enveloperate;
296  else
297  { // release, increase gain
298  compgain *= enveloperate;
299  if (compgain > 1.0f)
300  compgain = 1.0f;
301  }
302 
303  // the final gain value!
304  float premixgain = sinf(ang90 * compgain);
305  float gain = dry + wet * mastergain * premixgain;
306 
307  // calculate metering (not used in core algo, but used to output a meter if desired)
308  float premixgaindb = lin2db(premixgain);
309  if (premixgaindb < metergain)
310  metergain = premixgaindb; // spike immediately
311  else
312  metergain += (premixgaindb - metergain) * meterrelease; // fall slowly
313 
314  // apply the gain
315  output[samplepos] = delaybuf[delayreadpos] * gain;
316  }
317  }
318 
319  state->metergain = metergain;
320  state->detectoravg = detectoravg;
321  state->compgain = compgain;
322  state->maxcompdiffdb = maxcompdiffdb;
323  state->delaywritepos = delaywritepos;
324  state->delayreadpos = delayreadpos;
325 }
#define AUDIOCOMPRESSORSND_SF_COMPRESSOR_MAXDELAY
#define AUDIOCOMPRESSORSND_SF_COMPRESSOR_SPACINGDB
static float kneecurve(float x, float k, float linearthreshold)
#define AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE
CompressorState m_compressorState
static float absf(float v)
static float fixf(float v, float def)
void sf_advancecomp(int rate, float pregain, float threshold, float knee, float ratio, float attack, float release, float predelay, float releasezone1, float releasezone2, float releasezone3, float releasezone4, float postgain, float wet)
static float adaptivereleasecurve(float x, float a, float b, float c, float d)
#define M_PI
Definition: rdsdemod.cpp:27
static float compcurve(float x, float k, float slope, float linearthreshold, float linearthresholdknee, float threshold, float knee, float kneedboffset)
int32_t i
Definition: decimators.h:244
float m_storageBuffer[AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE]
static float lin2db(float lin)
float delaybuf[AUDIOCOMPRESSORSND_SF_COMPRESSOR_MAXDELAY]
static float kneeslope(float x, float k, float linearthreshold)
static void sf_compressor_process(CompressorState *state, int size, float *input, float *output)
static float clampf(float v, float min, float max)
float m_processedBuffer[AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE]
static float db2lin(float db)
float compress(float sample)
#define AUDIOCOMPRESSORSND_SF_COMPRESSOR_SPU