| 
									
										
										
										
											2019-07-16 14:56:23 +02:00
										 |  |  | ///////////////////////////////////////////////////////////////////////////////////
 | 
					
						
							| 
									
										
										
										
											2023-11-19 06:43:20 +01:00
										 |  |  | // Copyright (C) 2019-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com>          //
 | 
					
						
							| 
									
										
										
										
											2019-07-16 14:56:23 +02:00
										 |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // 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 <http://www.gnu.org/licenses/>.          //
 | 
					
						
							|  |  |  | ///////////////////////////////////////////////////////////////////////////////////
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <algorithm>
 | 
					
						
							|  |  |  | #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) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-07-18 01:48:07 +02:00
										 |  |  |     float compressedSample; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 14:56:23 +02:00
										 |  |  |     if (m_sampleIndex >= AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         sf_compressor_process(&m_compressorState, AUDIOCOMPRESSORSND_SF_COMPRESSOR_CHUNKSIZE, m_storageBuffer, m_processedBuffer); | 
					
						
							|  |  |  |         m_sampleIndex = 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 01:48:07 +02:00
										 |  |  |     compressedSample = m_processedBuffer[m_sampleIndex]; | 
					
						
							|  |  |  |     m_storageBuffer[m_sampleIndex] = sample; | 
					
						
							|  |  |  |     m_sampleIndex++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return compressedSample; | 
					
						
							| 
									
										
										
										
											2019-07-16 14:56:23 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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 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`
 | 
					
						
							| 
									
										
										
										
											2024-07-10 22:59:13 +02:00
										 |  |  | 	// intersecting points (0, y1), (1, y2), (2, y3), (3, y4)
 | 
					
						
							| 
									
										
										
										
											2019-07-16 14:56:23 +02:00
										 |  |  | 	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; | 
					
						
							|  |  |  | } |