diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt
index fc6736ef3..925e1c481 100644
--- a/sdrbase/CMakeLists.txt
+++ b/sdrbase/CMakeLists.txt
@@ -51,6 +51,7 @@ endif(LIBSERIALDV_FOUND)
set(sdrbase_SOURCES
${sdrbase_SOURCES}
audio/audiocompressor.cpp
+ audio/audiocompressorsnd.cpp
audio/audiodevicemanager.cpp
audio/audiofifo.cpp
audio/audiofilter.cpp
@@ -152,6 +153,7 @@ set(sdrbase_SOURCES
set(sdrbase_HEADERS
${sdrbase_HEADERS}
audio/audiocompressor.h
+ audio/audiocompressorsnd.h
audio/audiodevicemanager.h
audio/audiofifo.h
audio/audiofilter.h
diff --git a/sdrbase/audio/audiocompressorsnd.cpp b/sdrbase/audio/audiocompressorsnd.cpp
new file mode 100644
index 000000000..1f98a2a08
--- /dev/null
+++ b/sdrbase/audio/audiocompressorsnd.cpp
@@ -0,0 +1,324 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2019 F4EXB //
+// written by Edouard Griffiths //
+// //
+// Audio compressor based on sndfilter by Sean Connelly (@voidqk) //
+// https://github.com/voidqk/sndfilter //
+// //
+// Sample by sample interface to facilitate integration in SDRangel modulators. //
+// Uses mono samples (just floats) //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include "audiocompressorsnd.h"
+
+
+AudioCompressorSnd::AudioCompressorSnd()
+{
+ m_sampleIndex = 0;
+ std::fill(m_processedBuffer, m_processedBuffer+AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE, 0.0f);
+}
+
+AudioCompressorSnd::~AudioCompressorSnd()
+{}
+
+void AudioCompressorSnd::initState()
+{
+ m_compressorState.sf_advancecomp(
+ m_rate,
+ m_pregain,
+ m_threshold,
+ m_knee,
+ m_ratio,
+ m_attack,
+ m_release,
+ m_predelay,
+ m_releasezone1,
+ m_releasezone2,
+ m_releasezone3,
+ m_releasezone4,
+ m_postgain,
+ m_wet
+ );
+}
+
+float AudioCompressorSnd::compress(float sample)
+{
+ if (m_sampleIndex >= AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE)
+ {
+ sf_compressor_process(&m_compressorState, AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE, m_storageBuffer, m_processedBuffer);
+ m_sampleIndex = 0;
+ }
+ else
+ {
+ m_storageBuffer[m_sampleIndex] = sample;
+ m_sampleIndex++;
+ }
+
+ return m_processedBuffer[m_sampleIndex];
+}
+
+// populate the compressor state with advanced parameters
+void AudioCompressorSnd::CompressorState::sf_advancecomp(
+ // these parameters are the same as the simple version above:
+ int rate, float pregain, float threshold, float knee, float ratio, float attack, float release,
+ // these are the advanced parameters:
+ float predelay, // seconds, length of the predelay buffer [0 to 1]
+ float releasezone1, // release zones should be increasing between 0 and 1, and are a fraction
+ float releasezone2, // of the release time depending on the input dB -- these parameters define
+ float releasezone3, // the adaptive release curve, which is discussed in further detail in the
+ float releasezone4, // demo: adaptive-release-curve.html
+ float postgain, // dB, amount of gain to apply after compression [0 to 100]
+ float wet) // amount to apply the effect [0 completely dry to 1 completely wet]
+{
+ // setup the predelay buffer
+ int delaybufsize = rate * predelay;
+
+ if (delaybufsize < 1)
+ {
+ delaybufsize = 1;
+ }
+ else if (delaybufsize > AUDIOCOMPRESSORSND_SF_COMPRESSOR_MAXDELAY)
+ {
+ delaybufsize = AUDIOCOMPRESSORSND_SF_COMPRESSOR_MAXDELAY;
+ std::fill(delaybuf, delaybuf+delaybufsize, 0.0f);
+ }
+
+ // useful values
+ float linearpregain = db2lin(pregain);
+ float linearthreshold = db2lin(threshold);
+ float slope = 1.0f / ratio;
+ float attacksamples = rate * attack;
+ float attacksamplesinv = 1.0f / attacksamples;
+ float releasesamples = rate * release;
+ float satrelease = 0.0025f; // seconds
+ float satreleasesamplesinv = 1.0f / ((float)rate * satrelease);
+ float dry = 1.0f - wet;
+
+ // metering values (not used in core algorithm, but used to output a meter if desired)
+ float metergain = 1.0f; // gets overwritten immediately because gain will always be negative
+ float meterfalloff = 0.325f; // seconds
+ float meterrelease = 1.0f - expf(-1.0f / ((float)rate * meterfalloff));
+
+ // calculate knee curve parameters
+ float k = 5.0f; // initial guess
+ float kneedboffset = 0.0f;
+ float linearthresholdknee = 0.0f;
+
+ if (knee > 0.0f) // if a knee exists, search for a good k value
+ {
+ float xknee = db2lin(threshold + knee);
+ float mink = 0.1f;
+ float maxk = 10000.0f;
+
+ // search by comparing the knee slope at the current k guess, to the ideal slope
+ for (int i = 0; i < 15; i++)
+ {
+ if (kneeslope(xknee, k, linearthreshold) < slope) {
+ maxk = k;
+ } else {
+ mink = k;
+ }
+
+ k = sqrtf(mink * maxk);
+ }
+
+ kneedboffset = lin2db(kneecurve(xknee, k, linearthreshold));
+ linearthresholdknee = db2lin(threshold + knee);
+ }
+
+ // calculate a master gain based on what sounds good
+ float fulllevel = compcurve(1.0f, k, slope, linearthreshold, linearthresholdknee, threshold, knee, kneedboffset);
+ float mastergain = db2lin(postgain) * powf(1.0f / fulllevel, 0.6f);
+
+ // calculate the adaptive release curve parameters
+ // solve a,b,c,d in `y = a*x^3 + b*x^2 + c*x + d`
+ // interescting points (0, y1), (1, y2), (2, y3), (3, y4)
+ float y1 = releasesamples * releasezone1;
+ float y2 = releasesamples * releasezone2;
+ float y3 = releasesamples * releasezone3;
+ float y4 = releasesamples * releasezone4;
+ float a = (-y1 + 3.0f * y2 - 3.0f * y3 + y4) / 6.0f;
+ float b = y1 - 2.5f * y2 + 2.0f * y3 - 0.5f * y4;
+ float c = (-11.0f * y1 + 18.0f * y2 - 9.0f * y3 + 2.0f * y4) / 6.0f;
+ float d = y1;
+
+ // save everything
+ this->metergain = 1.0f; // large value overwritten immediately since it's always < 0
+ this->meterrelease = meterrelease;
+ this->threshold = threshold;
+ this->knee = knee;
+ this->wet = wet;
+ this->linearpregain = linearpregain;
+ this->linearthreshold = linearthreshold;
+ this->slope = slope;
+ this->attacksamplesinv = attacksamplesinv;
+ this->satreleasesamplesinv = satreleasesamplesinv;
+ this->dry = dry;
+ this->k = k;
+ this->kneedboffset = kneedboffset;
+ this->linearthresholdknee = linearthresholdknee;
+ this->mastergain = mastergain;
+ this->a = a;
+ this->b = b;
+ this->c = c;
+ this->d = d;
+ this->detectoravg = 0.0f;
+ this->compgain = 1.0f;
+ this->maxcompdiffdb = -1.0f;
+ this->delaybufsize = delaybufsize;
+ this->delaywritepos = 0;
+ this->delayreadpos = delaybufsize > 1 ? 1 : 0;
+}
+
+void AudioCompressorSnd::sf_compressor_process(AudioCompressorSnd::CompressorState *state, int size, float *input, float *output)
+{
+ // pull out the state into local variables
+ float metergain = state->metergain;
+ float meterrelease = state->meterrelease;
+ float threshold = state->threshold;
+ float knee = state->knee;
+ float linearpregain = state->linearpregain;
+ float linearthreshold = state->linearthreshold;
+ float slope = state->slope;
+ float attacksamplesinv = state->attacksamplesinv;
+ float satreleasesamplesinv = state->satreleasesamplesinv;
+ float wet = state->wet;
+ float dry = state->dry;
+ float k = state->k;
+ float kneedboffset = state->kneedboffset;
+ float linearthresholdknee = state->linearthresholdknee;
+ float mastergain = state->mastergain;
+ float a = state->a;
+ float b = state->b;
+ float c = state->c;
+ float d = state->d;
+ float detectoravg = state->detectoravg;
+ float compgain = state->compgain;
+ float maxcompdiffdb = state->maxcompdiffdb;
+ int delaybufsize = state->delaybufsize;
+ int delaywritepos = state->delaywritepos;
+ int delayreadpos = state->delayreadpos;
+ float *delaybuf = state->delaybuf;
+
+ int samplesperchunk = AUDIOCOMPRESSORSND_SF_COMPRESSOR_SPU;
+ int chunks = size / samplesperchunk;
+ float ang90 = (float)M_PI * 0.5f;
+ float ang90inv = 2.0f / (float)M_PI;
+ int samplepos = 0;
+ float spacingdb = AUDIOCOMPRESSORSND_SF_COMPRESSOR_SPACINGDB;
+
+ for (int ch = 0; ch < chunks; ch++)
+ {
+ detectoravg = fixf(detectoravg, 1.0f);
+ float desiredgain = detectoravg;
+ float scaleddesiredgain = asinf(desiredgain) * ang90inv;
+ float compdiffdb = lin2db(compgain / scaleddesiredgain);
+
+ // calculate envelope rate based on whether we're attacking or releasing
+ float enveloperate;
+ if (compdiffdb < 0.0f)
+ { // compgain < scaleddesiredgain, so we're releasing
+ compdiffdb = fixf(compdiffdb, -1.0f);
+ maxcompdiffdb = -1; // reset for a future attack mode
+ // apply the adaptive release curve
+ // scale compdiffdb between 0-3
+ float x = (clampf(compdiffdb, -12.0f, 0.0f) + 12.0f) * 0.25f;
+ float releasesamples = adaptivereleasecurve(x, a, b, c, d);
+ enveloperate = db2lin(spacingdb / releasesamples);
+ }
+ else
+ { // compresorgain > scaleddesiredgain, so we're attacking
+ compdiffdb = fixf(compdiffdb, 1.0f);
+ if (maxcompdiffdb == -1 || maxcompdiffdb < compdiffdb)
+ maxcompdiffdb = compdiffdb;
+ float attenuate = maxcompdiffdb;
+ if (attenuate < 0.5f)
+ attenuate = 0.5f;
+ enveloperate = 1.0f - powf(0.25f / attenuate, attacksamplesinv);
+ }
+
+ // process the chunk
+ for (int chi = 0; chi < samplesperchunk; chi++, samplepos++,
+ delayreadpos = (delayreadpos + 1) % delaybufsize,
+ delaywritepos = (delaywritepos + 1) % delaybufsize)
+ {
+
+ float inputL = input[samplepos] * linearpregain;
+ delaybuf[delaywritepos] = inputL;
+
+ inputL = absf(inputL);
+ float inputmax = inputL;
+
+ float attenuation;
+ if (inputmax < 0.0001f)
+ attenuation = 1.0f;
+ else
+ {
+ float inputcomp = compcurve(inputmax, k, slope, linearthreshold,
+ linearthresholdknee, threshold, knee, kneedboffset);
+ attenuation = inputcomp / inputmax;
+ }
+
+ float rate;
+ if (attenuation > detectoravg)
+ { // if releasing
+ float attenuationdb = -lin2db(attenuation);
+ if (attenuationdb < 2.0f)
+ attenuationdb = 2.0f;
+ float dbpersample = attenuationdb * satreleasesamplesinv;
+ rate = db2lin(dbpersample) - 1.0f;
+ }
+ else
+ rate = 1.0f;
+
+ detectoravg += (attenuation - detectoravg) * rate;
+ if (detectoravg > 1.0f)
+ detectoravg = 1.0f;
+ detectoravg = fixf(detectoravg, 1.0f);
+
+ if (enveloperate < 1) // attack, reduce gain
+ compgain += (scaleddesiredgain - compgain) * enveloperate;
+ else
+ { // release, increase gain
+ compgain *= enveloperate;
+ if (compgain > 1.0f)
+ compgain = 1.0f;
+ }
+
+ // the final gain value!
+ float premixgain = sinf(ang90 * compgain);
+ float gain = dry + wet * mastergain * premixgain;
+
+ // calculate metering (not used in core algo, but used to output a meter if desired)
+ float premixgaindb = lin2db(premixgain);
+ if (premixgaindb < metergain)
+ metergain = premixgaindb; // spike immediately
+ else
+ metergain += (premixgaindb - metergain) * meterrelease; // fall slowly
+
+ // apply the gain
+ output[samplepos] = delaybuf[delayreadpos] * gain;
+ }
+ }
+
+ state->metergain = metergain;
+ state->detectoravg = detectoravg;
+ state->compgain = compgain;
+ state->maxcompdiffdb = maxcompdiffdb;
+ state->delaywritepos = delaywritepos;
+ state->delayreadpos = delayreadpos;
+}
diff --git a/sdrbase/audio/audiocompressorsnd.h b/sdrbase/audio/audiocompressorsnd.h
new file mode 100644
index 000000000..c2bd55322
--- /dev/null
+++ b/sdrbase/audio/audiocompressorsnd.h
@@ -0,0 +1,229 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2019 F4EXB //
+// written by Edouard Griffiths //
+// //
+// Audio compressor based on sndfilter by Sean Connelly (@voidqk) //
+// https://github.com/voidqk/sndfilter //
+// //
+// Sample by sample interface to facilitate integration in SDRangel modulators. //
+// Uses mono samples (just floats) //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SDRBASE_AUDIO_AUDIOCOMPRESSORSND_H_
+#define SDRBASE_AUDIO_AUDIOCOMPRESSORSND_H_
+
+#include
+
+// maximum number of samples in the delay buffer
+#define AUDIOCOMPRESSORSND_SF_COMPRESSOR_MAXDELAY 1024
+
+// samples per update; the compressor works by dividing the input chunks into even smaller sizes,
+// and performs heavier calculations after each mini-chunk to adjust the final envelope
+#define AUDIOCOMPRESSORSND_SF_COMPRESSOR_SPU 32
+
+// not sure what this does exactly, but it is part of the release curve
+#define AUDIOCOMPRESSORSND_SF_COMPRESSOR_SPACINGDB 5.0f
+
+// the "chunk" size as defined in original sndfilter library
+#define AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE 128
+
+#include "export.h"
+
+class SDRBASE_API AudioCompressorSnd
+{
+public:
+ AudioCompressorSnd();
+ ~AudioCompressorSnd();
+
+ void initDefault(int rate)
+ {
+ m_rate = rate;
+ m_pregain = 0.000f;
+ m_threshold = -24.000f;
+ m_knee = 30.000f;
+ m_ratio = 12.000f;
+ m_attack = 0.003f;
+ m_release = 0.250f;
+ m_predelay = 0.006f;
+ m_releasezone1 = 0.090f;
+ m_releasezone2 = 0.160f;
+ m_releasezone3 = 0.420f;
+ m_releasezone4 = 0.980f;
+ m_postgain = 0.000f;
+ m_wet = 1.000f;
+ initState();
+ }
+
+ void initSimple(
+ int rate, // input sample rate (samples per second)
+ float pregain, // dB, amount to boost the signal before applying compression [0 to 100]
+ float threshold, // dB, level where compression kicks in [-100 to 0]
+ float knee, // dB, width of the knee [0 to 40]
+ float ratio, // unitless, amount to inversely scale the output when applying comp [1 to 20]
+ float attack, // seconds, length of the attack phase [0 to 1]
+ float release // seconds, length of the release phase [0 to 1]
+ )
+ {
+ m_rate = rate;
+ m_pregain = pregain;
+ m_threshold = threshold;
+ m_knee = knee;
+ m_ratio = ratio;
+ m_attack = attack;
+ m_release = release;
+ m_predelay = 0.006f;
+ m_releasezone1 = 0.090f;
+ m_releasezone2 = 0.160f;
+ m_releasezone3 = 0.420f;
+ m_releasezone4 = 0.980f;
+ m_postgain = 0.000f;
+ m_wet = 1.000f;
+ initState();
+ }
+
+ void initState();
+ float compress(float sample);
+
+ float m_rate;
+ float m_pregain;
+ float m_threshold;
+ float m_knee;
+ float m_ratio;
+ float m_attack;
+ float m_release;
+ float m_predelay;
+ float m_releasezone1;
+ float m_releasezone2;
+ float m_releasezone3;
+ float m_releasezone4;
+ float m_postgain;
+ float m_wet;
+
+private:
+ static inline float db2lin(float db){ // dB to linear
+ return powf(10.0f, 0.05f * db);
+ }
+
+ static inline float lin2db(float lin){ // linear to dB
+ return 20.0f * log10f(lin);
+ }
+
+ // for more information on the knee curve, check out the compressor-curve.html demo + source code
+ // included in this repo
+ static inline float kneecurve(float x, float k, float linearthreshold){
+ return linearthreshold + (1.0f - expf(-k * (x - linearthreshold))) / k;
+ }
+
+ static inline float kneeslope(float x, float k, float linearthreshold){
+ return k * x / ((k * linearthreshold + 1.0f) * expf(k * (x - linearthreshold)) - 1);
+ }
+
+ static inline float compcurve(float x, float k, float slope, float linearthreshold,
+ float linearthresholdknee, float threshold, float knee, float kneedboffset){
+ if (x < linearthreshold)
+ return x;
+ if (knee <= 0.0f) // no knee in curve
+ return db2lin(threshold + slope * (lin2db(x) - threshold));
+ if (x < linearthresholdknee)
+ return kneecurve(x, k, linearthreshold);
+ return db2lin(kneedboffset + slope * (lin2db(x) - threshold - knee));
+ }
+
+ // for more information on the adaptive release curve, check out adaptive-release-curve.html demo +
+ // source code included in this repo
+ static inline float adaptivereleasecurve(float x, float a, float b, float c, float d){
+ // a*x^3 + b*x^2 + c*x + d
+ float x2 = x * x;
+ return a * x2 * x + b * x2 + c * x + d;
+ }
+
+ static inline float clampf(float v, float min, float max){
+ return v < min ? min : (v > max ? max : v);
+ }
+
+ static inline float absf(float v){
+ return v < 0.0f ? -v : v;
+ }
+
+ static inline float fixf(float v, float def){
+ // fix NaN and infinity values that sneak in... not sure why this is needed, but it is
+ if (isnan(v) || isinf(v))
+ return def;
+ return v;
+ }
+
+ struct CompressorState
+ { // sf_compressor_state_st
+ // user can read the metergain state variable after processing a chunk to see how much dB the
+ // compressor would have liked to compress the sample; the meter values aren't used to shape the
+ // sound in any way, only used for output if desired
+ float metergain;
+
+ // everything else shouldn't really be mucked with unless you read the algorithm and feel
+ // comfortable
+ float meterrelease;
+ float threshold;
+ float knee;
+ float linearpregain;
+ float linearthreshold;
+ float slope;
+ float attacksamplesinv;
+ float satreleasesamplesinv;
+ float wet;
+ float dry;
+ float k;
+ float kneedboffset;
+ float linearthresholdknee;
+ float mastergain;
+ float a; // adaptive release polynomial coefficients
+ float b;
+ float c;
+ float d;
+ float detectoravg;
+ float compgain;
+ float maxcompdiffdb;
+ int delaybufsize;
+ int delaywritepos;
+ int delayreadpos;
+ float delaybuf[AUDIOCOMPRESSORSND_SF_COMPRESSOR_MAXDELAY]; // predelay buffer
+
+ // populate the compressor state with advanced parameters
+ void sf_advancecomp(
+ // these parameters are the same as the simple version above:
+ int rate, float pregain, float threshold, float knee, float ratio, float attack, float release,
+ // these are the advanced parameters:
+ float predelay, // seconds, length of the predelay buffer [0 to 1]
+ float releasezone1, // release zones should be increasing between 0 and 1, and are a fraction
+ float releasezone2, // of the release time depending on the input dB -- these parameters define
+ float releasezone3, // the adaptive release curve, which is discussed in further detail in the
+ float releasezone4, // demo: adaptive-release-curve.html
+ float postgain, // dB, amount of gain to apply after compression [0 to 100]
+ float wet // amount to apply the effect [0 completely dry to 1 completely wet]
+ );
+ };
+
+ static void sf_compressor_process(CompressorState *state, int size, float *input, float *output);
+
+ CompressorState m_compressorState;
+ float m_storageBuffer[AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE];
+ float m_processedBuffer[AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE];
+ int m_sampleIndex;
+};
+
+
+
+
+#endif // SDRBASE_AUDIO_AUDIOCOMPRESSORSND_H_
\ No newline at end of file