Remove audio artifacts when automating the Equliser parameters.
The Equliser pluging uses biquad filters, These do not like having there parameters updating during processing, and are know to produce clicks and DC biasing. A twin filter system has been employed with a cross fade, to interpolate between parameters. This has removed for the use of sample exactness, as the filter is only updated once per frame, with interpolation provided by the crossfade. The same filters are used as pervious, ensuring unautomated filtering remains unchanged.
This commit is contained in:
@@ -70,6 +70,8 @@ EqEffect::~EqEffect()
|
||||
|
||||
bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames )
|
||||
{
|
||||
const int sampleRate = Engine::mixer()->processingSampleRate();
|
||||
|
||||
//wet/dry controls
|
||||
const float dry = dryLevel();
|
||||
const float wet = wetLevel();
|
||||
@@ -93,59 +95,6 @@ bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames )
|
||||
float highShelfFreq = m_eqControls.m_highShelfFreqModel.value();
|
||||
float lpFreq = m_eqControls.m_lpFreqModel.value();
|
||||
|
||||
ValueBuffer *hpResBuffer = m_eqControls.m_hpResModel.valueBuffer();
|
||||
ValueBuffer *lowShelfResBuffer = m_eqControls.m_lowShelfResModel.valueBuffer();
|
||||
ValueBuffer *para1BwBuffer = m_eqControls.m_para1BwModel.valueBuffer();
|
||||
ValueBuffer *para2BwBuffer = m_eqControls.m_para2BwModel.valueBuffer();
|
||||
ValueBuffer *para3BwBuffer = m_eqControls.m_para3BwModel.valueBuffer();
|
||||
ValueBuffer *para4BwBuffer = m_eqControls.m_para4BwModel.valueBuffer();
|
||||
ValueBuffer *highShelfResBuffer = m_eqControls.m_highShelfResModel.valueBuffer();
|
||||
ValueBuffer *lpResBuffer = m_eqControls.m_lpResModel.valueBuffer();
|
||||
|
||||
ValueBuffer *hpFreqBuffer = m_eqControls.m_hpFeqModel.valueBuffer();
|
||||
ValueBuffer *lowShelfFreqBuffer = m_eqControls.m_lowShelfFreqModel.valueBuffer();
|
||||
ValueBuffer *para1FreqBuffer = m_eqControls.m_para1FreqModel.valueBuffer();
|
||||
ValueBuffer *para2FreqBuffer = m_eqControls.m_para2FreqModel.valueBuffer();
|
||||
ValueBuffer *para3FreqBuffer = m_eqControls.m_para3FreqModel.valueBuffer();
|
||||
ValueBuffer *para4FreqBuffer = m_eqControls.m_para4FreqModel.valueBuffer();
|
||||
ValueBuffer *highShelfFreqBuffer = m_eqControls.m_highShelfFreqModel.valueBuffer();
|
||||
ValueBuffer *lpFreqBuffer = m_eqControls.m_lpFreqModel.valueBuffer();
|
||||
|
||||
int hpResInc = hpResBuffer ? 1 : 0;
|
||||
int lowShelfResInc = lowShelfResBuffer ? 1 : 0;
|
||||
int para1BwInc = para1BwBuffer ? 1 : 0;
|
||||
int para2BwInc = para2BwBuffer ? 1 : 0;
|
||||
int para3BwInc = para3BwBuffer ? 1 : 0;
|
||||
int para4BwInc = para4BwBuffer ? 1 : 0;
|
||||
int highShelfResInc = highShelfResBuffer ? 1 : 0;
|
||||
int lpResInc = lpResBuffer ? 1 : 0;
|
||||
|
||||
int hpFreqInc = hpFreqBuffer ? 1 : 0;
|
||||
int lowShelfFreqInc = lowShelfFreqBuffer ? 1 : 0;
|
||||
int para1FreqInc = para1FreqBuffer ? 1 : 0;
|
||||
int para2FreqInc = para2FreqBuffer ? 1 : 0;
|
||||
int para3FreqInc = para3FreqBuffer ? 1 : 0;
|
||||
int para4FreqInc = para4FreqBuffer ? 1 : 0;
|
||||
int highShelfFreqInc = highShelfFreqBuffer ? 1 : 0;
|
||||
int lpFreqInc = lpFreqBuffer ? 1 : 0;
|
||||
|
||||
float *hpResPtr = hpResBuffer ? &( hpResBuffer->values()[ 0 ] ) : &hpRes;
|
||||
float *lowShelfResPtr = lowShelfResBuffer ? &( lowShelfResBuffer->values()[ 0 ] ) : &lowShelfRes;
|
||||
float *para1BwPtr = para1BwBuffer ? &( para1BwBuffer->values()[ 0 ] ) : ¶1Bw;
|
||||
float *para2BwPtr = para2BwBuffer ? &( para2BwBuffer->values()[ 0 ] ) : ¶2Bw;
|
||||
float *para3BwPtr = para3BwBuffer ? &( para3BwBuffer->values()[ 0 ] ) : ¶3Bw;
|
||||
float *para4BwPtr = para4BwBuffer ? &( para4BwBuffer->values()[ 0 ] ) : ¶4Bw;
|
||||
float *highShelfResPtr = highShelfResBuffer ? &( highShelfResBuffer->values()[ 0 ] ) : &highShelfRes;
|
||||
float *lpResPtr = lpResBuffer ? &( lpResBuffer->values()[ 0 ] ) : &lpRes;
|
||||
|
||||
float *hpFreqPtr = hpFreqBuffer ? &( hpFreqBuffer->values()[ 0 ] ) : &hpFreq;
|
||||
float *lowShelfFreqPtr = lowShelfFreqBuffer ? &( lowShelfFreqBuffer->values()[ 0 ] ) : &lowShelfFreq;
|
||||
float *para1FreqPtr = para1FreqBuffer ? &(para1FreqBuffer->values()[ 0 ] ) : ¶1Freq;
|
||||
float *para2FreqPtr = para2FreqBuffer ? &(para2FreqBuffer->values()[ 0 ] ) : ¶2Freq;
|
||||
float *para3FreqPtr = para3FreqBuffer ? &(para3FreqBuffer->values()[ 0 ] ) : ¶3Freq;
|
||||
float *para4FreqPtr = para4FreqBuffer ? &(para4FreqBuffer->values()[ 0 ] ) : ¶4Freq;
|
||||
float *hightShelfFreqPtr = highShelfFreqBuffer ? &(highShelfFreqBuffer->values()[ 0 ] ) : &highShelfFreq;
|
||||
float *lpFreqPtr = lpFreqBuffer ? &(lpFreqBuffer ->values()[ 0 ] ) : &lpFreq;
|
||||
|
||||
bool hpActive = m_eqControls.m_hpActiveModel.value();
|
||||
bool hp24Active = m_eqControls.m_hp24Model.value();
|
||||
@@ -167,6 +116,29 @@ bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames )
|
||||
float para4Gain = m_eqControls.m_para4GainModel.value();
|
||||
float highShelfGain = m_eqControls.m_highShelfGainModel.value();
|
||||
|
||||
//set all filter parameters once per frame, EqFilter handles
|
||||
//smooth xfading, reducing pops clicks and dc bias offsets
|
||||
|
||||
m_hp12.setParameters( sampleRate, hpFreq, hpRes, 1 );
|
||||
m_hp24.setParameters( sampleRate, hpFreq, hpRes, 1 );
|
||||
m_hp480.setParameters( sampleRate, hpFreq, hpRes, 1 );
|
||||
m_lp480.setParameters( sampleRate, lpFreq, lpRes, 1 );
|
||||
m_hp481.setParameters( sampleRate, hpFreq, hpRes, 1 );
|
||||
m_lp481.setParameters( sampleRate, hpFreq, hpRes, 1 );
|
||||
m_lowShelf.setParameters( sampleRate, lowShelfFreq, lowShelfRes, lowShelfGain );
|
||||
m_para1.setParameters( sampleRate, para1Freq, para1Bw, para1Gain );
|
||||
m_para2.setParameters( sampleRate, para2Freq, para2Bw, para2Gain );
|
||||
m_para3.setParameters( sampleRate, para3Freq, para3Bw, para3Gain );
|
||||
m_para4.setParameters( sampleRate, para4Freq, para4Bw, para4Gain );
|
||||
m_highShelf.setParameters( sampleRate, highShelfFreq, highShelfRes, highShelfGain );
|
||||
m_lp12.setParameters( sampleRate, lpFreq, lpRes, 1 );
|
||||
m_lp24.setParameters( sampleRate, lpFreq, lpRes, 1 );
|
||||
m_lp480.setParameters( sampleRate, lpFreq, lpRes, 1 );
|
||||
m_lp480.setParameters( sampleRate, lpFreq, lpRes, 1 );
|
||||
|
||||
|
||||
|
||||
|
||||
if( !isEnabled() || !isRunning () )
|
||||
{
|
||||
return( false );
|
||||
@@ -191,7 +163,6 @@ bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames )
|
||||
}
|
||||
|
||||
const float outGain = m_outGain;
|
||||
const int sampleRate = Engine::mixer()->processingSampleRate();
|
||||
sampleFrame m_inPeak = { 0, 0 };
|
||||
|
||||
if(m_eqControls.m_analyseInModel.value( true ) && outSum > 0 )
|
||||
@@ -207,99 +178,87 @@ bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames )
|
||||
m_eqControls.m_inPeakL = m_eqControls.m_inPeakL < m_inPeak[0] ? m_inPeak[0] : m_eqControls.m_inPeakL;
|
||||
m_eqControls.m_inPeakR = m_eqControls.m_inPeakR < m_inPeak[1] ? m_inPeak[1] : m_eqControls.m_inPeakR;
|
||||
|
||||
for( fpp_t f = 0; f < frames; f++)
|
||||
float periodProgress = 0.0f; // percentage of period processed
|
||||
for( fpp_t f = 0; f < frames; ++f)
|
||||
{
|
||||
periodProgress = (float)f / (float)(frames-1);
|
||||
//wet dry buffer
|
||||
dryS[0] = buf[f][0];
|
||||
dryS[1] = buf[f][1];
|
||||
if( hpActive )
|
||||
{
|
||||
m_hp12.setParameters( sampleRate, *hpFreqPtr, *hpResPtr, 1 );
|
||||
buf[f][0] = m_hp12.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_hp12.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_hp12.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_hp12.update( buf[f][1], 1, periodProgress );
|
||||
|
||||
if( hp24Active || hp48Active )
|
||||
{
|
||||
m_hp24.setParameters( sampleRate, *hpFreqPtr, *hpResPtr, 1 );
|
||||
buf[f][0] = m_hp24.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_hp24.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_hp24.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_hp24.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
|
||||
if( hp48Active )
|
||||
{
|
||||
m_hp480.setParameters( sampleRate, *hpFreqPtr, *hpResPtr, 1 );
|
||||
buf[f][0] = m_hp480.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_hp480.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_hp480.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_hp480.update( buf[f][1], 1, periodProgress );
|
||||
|
||||
m_hp481.setParameters( sampleRate, *hpFreqPtr, *hpResPtr, 1 );
|
||||
buf[f][0] = m_hp481.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_hp481.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_hp481.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_hp481.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
}
|
||||
|
||||
if( lowShelfActive )
|
||||
{
|
||||
m_lowShelf.setParameters( sampleRate, *lowShelfFreqPtr, *lowShelfResPtr, lowShelfGain );
|
||||
buf[f][0] = m_lowShelf.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_lowShelf.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_lowShelf.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_lowShelf.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
|
||||
if( para1Active )
|
||||
{
|
||||
m_para1.setParameters( sampleRate, *para1FreqPtr, *para1BwPtr, para1Gain );
|
||||
buf[f][0] = m_para1.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_para1.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_para1.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_para1.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
|
||||
if( para2Active )
|
||||
{
|
||||
m_para2.setParameters( sampleRate, *para2FreqPtr, *para2BwPtr, para2Gain );
|
||||
buf[f][0] = m_para2.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_para2.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_para2.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_para2.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
|
||||
if( para3Active )
|
||||
{
|
||||
m_para3.setParameters( sampleRate, *para3FreqPtr, *para3BwPtr, para3Gain );
|
||||
buf[f][0] = m_para3.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_para3.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_para3.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_para3.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
|
||||
if( para4Active )
|
||||
{
|
||||
m_para4.setParameters( sampleRate, *para4FreqPtr, *para4BwPtr, para4Gain );
|
||||
buf[f][0] = m_para4.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_para4.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_para4.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_para4.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
|
||||
if( highShelfActive )
|
||||
{
|
||||
m_highShelf.setParameters( sampleRate, *hightShelfFreqPtr, *highShelfResPtr, highShelfGain );
|
||||
buf[f][0] = m_highShelf.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_highShelf.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_highShelf.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_highShelf.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
|
||||
if( lpActive ){
|
||||
m_lp12.setParameters( sampleRate, *lpFreqPtr, *lpResPtr, 1 );
|
||||
buf[f][0] = m_lp12.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_lp12.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_lp12.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_lp12.update( buf[f][1], 1, periodProgress );
|
||||
|
||||
if( lp24Active || lp48Active )
|
||||
{
|
||||
m_lp24.setParameters( sampleRate, *lpFreqPtr, *lpResPtr, 1 );
|
||||
buf[f][0] = m_lp24.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_lp24.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_lp24.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_lp24.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
|
||||
if( lp48Active )
|
||||
{
|
||||
m_lp480.setParameters( sampleRate, *lpFreqPtr, *lpResPtr, 1 );
|
||||
buf[f][0] = m_lp480.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_lp480.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_lp480.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_lp480.update( buf[f][1], 1, periodProgress );
|
||||
|
||||
m_lp481.setParameters( sampleRate, *lpFreqPtr, *lpResPtr, 1 );
|
||||
buf[f][0] = m_lp481.update( buf[f][0], 0 );
|
||||
buf[f][1] = m_lp481.update( buf[f][1], 1 );
|
||||
buf[f][0] = m_lp481.update( buf[f][0], 0, periodProgress );
|
||||
buf[f][1] = m_lp481.update( buf[f][1], 1, periodProgress );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,24 +266,7 @@ bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames )
|
||||
buf[f][1] = ( dry * dryS[1] ) + ( wet * buf[f][1] );
|
||||
buf[f][0] = ( dry * dryS[0] ) + ( wet * buf[f][0] );
|
||||
|
||||
//increment pointers if needed
|
||||
hpResPtr += hpResInc;
|
||||
lowShelfResPtr += lowShelfResInc;
|
||||
para1BwPtr += para1BwInc;
|
||||
para2BwPtr += para2BwInc;
|
||||
para3BwPtr += para3BwInc;
|
||||
para4BwPtr += para4BwInc;
|
||||
highShelfResPtr += highShelfResInc;
|
||||
lpResPtr += lpResInc;
|
||||
|
||||
hpFreqPtr += hpFreqInc;
|
||||
lowShelfFreqPtr += lowShelfFreqInc;
|
||||
para1FreqPtr += para1FreqInc;
|
||||
para2FreqPtr += para2FreqInc;
|
||||
para3FreqPtr += para3FreqInc;
|
||||
para4FreqPtr += para4FreqInc;
|
||||
hightShelfFreqPtr += highShelfFreqInc;
|
||||
lpFreqPtr += lpFreqInc;
|
||||
}
|
||||
|
||||
sampleFrame outPeak = { 0, 0 };
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
///
|
||||
/// \brief The EqFilter class.
|
||||
/// A wrapper for the StereoBiQuad class, giving it freq, res, and gain controls.
|
||||
/// It is designed to process periods in one pass, with recalculation of coefficents
|
||||
/// Used on a per channel per frame basis with recalculation of coefficents
|
||||
/// upon parameter changes. The intention is to use this as a bass class, children override
|
||||
/// the calcCoefficents() function, providing the coefficents a1, a2, b0, b1, b2.
|
||||
///
|
||||
class EqFilter : public StereoBiQuad
|
||||
class EqFilter
|
||||
{
|
||||
public:
|
||||
EqFilter() :
|
||||
@@ -114,7 +114,28 @@ public:
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// \brief update
|
||||
/// filters using two BiQuads, then crossfades,
|
||||
/// depending on on percentage of period processes
|
||||
/// \param in
|
||||
/// \param ch
|
||||
/// \param frameProgress percentage of frame processed
|
||||
/// \return
|
||||
///
|
||||
inline float update( float in, ch_cnt_t ch, float frameProgress)
|
||||
{
|
||||
float initailF = m_biQuadFrameInitial.update( in, ch );
|
||||
float targetF = m_biQuadFrameTarget.update( in, ch );
|
||||
|
||||
if(frameProgress > 0.99999 )
|
||||
{
|
||||
m_biQuadFrameInitial= m_biQuadFrameTarget;
|
||||
}
|
||||
|
||||
return (1.0f-frameProgress) * initailF + frameProgress * targetF;
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
@@ -127,11 +148,23 @@ protected:
|
||||
|
||||
}
|
||||
|
||||
inline void setCoeffs( float a1, float a2, float b0, float b1, float b2 )
|
||||
{
|
||||
m_biQuadFrameTarget.setCoeffs( a1, a2, b0, b1, b2 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
float m_sampleRate;
|
||||
float m_freq;
|
||||
float m_res;
|
||||
float m_gain;
|
||||
float m_bw;
|
||||
StereoBiQuad m_biQuadFrameInitial;
|
||||
StereoBiQuad m_biQuadFrameTarget;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user