/////////////////////////////////////////////////////////////////////////////////// // 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_ #define _USE_MATH_DEFINES #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_