Guest

Register | Lost password?

— Forum Scope —

— Match —

— Forum Options —

Minimum search word length is 3 characters - maximum search word length is 84 characters

Chapter 6 Parametric EQ
October 25, 2015
5:03 am
Corms30
New Member
Members
Forum Posts: 2
Member Since:
October 24, 2015
Offline

Hi Will

Firstly, thank you for such a valuable book - I am having a blast working through it and getting great results.

I am wondering if you could advise me as to a problem I am having with the parametric/peaking filter (pg192).
I am calculating the coefficients as follows into the modified BiQuad function:

makeSecondOrderParametric(const double sampleRate, const double cornerFrequency, const double Q, const float gainFactor)
{
const double thetaC = 2 * pi * cornerFrequency/ sampleRate;
double thetaCOver2Q = thetaC/2*Q;
CLAMP(thetaCOver2Q,0.0,pi/2);
const double mu = pow(10.0, gainFactor/20.0);
const double zeta = 4 / (1 + mu);
const double ZetaTan = zeta *tan(thetaCOver2Q);
const double beta = 0.5 *((1 - ZetaTan)/(1 + ZetaTan));
const double gamma = (0.5 + beta)*cos(thetaC);
const double a0 = 0.5 - beta;

return IIRCoefficients( a0, // a0
0.0, // a1
-a0, // a2
-2*gamma, // b1
2*beta, // b2
mu - 1.0, // c0
1.0); // d0
}

I have modified your CBiQuad function to accommodate the c0 and d0 variables as follows:

float doBiQuad(const float in)
{
// Second order difference equation: y(n) = a0x(n) + a1x(n-1) + a2x(n-2) - b1y(n-1) - b2y(n-2)
const float wet = c0 * (a0*in + a1*Xz_1 + a2*Xz_2 - b1*Yz_1 - b2*Yz_2);
const float dry = d0 * in;
float yn = wet + dry;

// underflow check
if(yn > 0.0 && yn < FLT_MIN_PLUS) yn = 0;
if(yn FLT_MIN_MINUS) yn = 0;

// shuffle delays
// Y delays
Yz_2 = Yz_1;
Yz_1 = yn;

// X delays
Xz_2 = Xz_1;
Xz_1 = in;

return yn;
}

I am getting erroneous results and the filters are blowing up. Have I implemented the modified BiQuad function correctly? I have been banging my head against the screen on this one
Any help would be greatly appreciated.

Cheers
Anthony

October 25, 2015
8:36 pm
W Pirkle
Forum Posts: 249
Member Since:
January 29, 2017
Offline

Hi Anthony

There are a couple of issues here:

First, here is my code for calculating the coefficients - I think yours looks OK but I did not scrutinize it:

float theta_c = 2.0*pi*fCutoffFreq/(float)m_nSampleRate;

// --- clamp to 0.95 pi/2 (you can experiment with this)
float tanArg = theta_c/(2.0 * fQ);
if(tanArg >= pi/2.0)
tanArg = 0.95*pi/2.0;
// intermediate values
float mu = pow(10.0, m_fBoostCut/20.0);
float zeta = 4.0/(1.0 + mu);

float fBetaNumerator = 1.0 - zeta*tan(tanArg);
float fBetaDenominator = 1.0 + zeta*tan(tanArg);

// --- write to status window to monitor tan argument
char *f = floatToString(tanArg, 2);
char* p = addStrings("Para tanArg = ", f);
this->sendStatusWndText(p);

delete [] p;
delete [] f;

// beta
float fBeta = 0.5*(fBetaNumerator/fBetaDenominator);

// gamma
float fGamma = (0.5 + fBeta)*(cos(theta_c));

// alpha
float fAlpha = (0.5 - fBeta);

// left channel
m_LeftLPF.m_f_a0 = fAlpha;
m_LeftLPF.m_f_a1 = 0.0;
m_LeftLPF.m_f_a2 = -fAlpha;
m_LeftLPF.m_f_b1 = -2.0*fGamma;
m_LeftLPF.m_f_b2 = 2.0*fBeta;

// right channel
m_RightLPF.m_f_a0 = fAlpha;
m_RightLPF.m_f_a1 = 0.0;
m_RightLPF.m_f_a2 = -fAlpha;
m_RightLPF.m_f_b1 = -2.0*fGamma;
m_RightLPF.m_f_b2 = 2.0*fBeta;

c0 = mu - 1.0;
d0 = 1.0;

Note how I clamp the tangent argument to 0.95*pi/2 -- if you clamp it right at pi/2, it will blow up for very high fc and very low Q. You can experiment with the 0.95 value; I have gone to 0.99 successfully.

Also, notice how I use the Status Window to print the value of the tan argument so I can watch it change with the filter (you can use this trick to monitor any variables in your plugin, just don't use it in your process( ) method).

Most importantly: do not modify the CBiQuad code you find in pluginconstants.h and pluginobjects.cpp -- there are several objects in those files (BiQuad, EnvelopeDetector, WaveData, etc...) that are easily extendible. If you want to modify the behavior of any of these objects, you should subclass them instead of editing them directly. The reason is that these files are constantly in flux as I add new objects, constant declarations, #includes, etc... When you open an older project with a new version of RackAFX, it may replace these files with the latest updates, thus overwriting your edits. So, always subclass these objects!

In my implementation (which is what I used to generate the figures on page 192) I declared the c0 and d0 variables directly in the project's .h file like this:

// Add your code here: ----------------------------------------------------------- //

float c0;
float d0;

Then, in processAudioFrame() I use the normal biquad output and add the c0/d0 part:

pOutputBuffer[0] = c0*m_LeftLPF.doBiQuad(pInputBuffer[0]) + d0*pInputBuffer[0];
pOutputBuffer[1] = c0*m_RightLPF.doBiQuad(pInputBuffer[1]) + d0*pInputBuffer[1];

My guess is that your clamping code needs to be modified, but you should also clean up the BiQuad object issue by subclassing, e.g. CBiQuadEx or the like...

All the best,

Will

October 29, 2015
10:52 am
Corms30
New Member
Members
Forum Posts: 2
Member Since:
October 24, 2015
Offline

Hi Will

Thank you for getting back to me with a detailed explanation.
I will try your suggestions and subclass the BiQuad object. It's a great tip about writing the tan values to the window - I was wondering how to do that!
I'll be sure to let you know how I go.

Best wishes
Anthony

October 29, 2015
7:01 pm
W Pirkle
Forum Posts: 249
Member Since:
January 29, 2017
Offline

Hi Anthony

Comparing your code and mine should flush out the differences.

Meantime, yes do the subclassing on CBiQuad. For the new v6.6.1.5 (beta) I had to alter the pluginconstants.h file to accommodate changes made in the VST3.6.5 SDK so that file will be overwritten.

- Will

October 7, 2017
8:42 pm
burningTr33
Member
Members
Forum Posts: 8
Member Since:
December 25, 2014
Offline

Hi Will,

First off thank you for this book. It has been a tremendous help for breaking into the world of DSP.

So I took your low shelf, high shelf (6.41) and peak filter design equation (6.42) and tried to implement your design in my own project (not in RackAFX). I was successful at implementing both the shelving filters, but I am having trouble with the peak filter blowing up in the positive direction.  I stepped through my code and after about 15 to 20 samples the values start to reach infinity. I saw your code above for prepping the coefficients and copied the code verbatim to see if i made any small mistakes, but the results are the same. Currently I am using the transposed direct form 2 design of the biquad listed on this site: http://www.earlevel.com/main/2.....8/biquads/

So I had 2 questions:

1. Since I was able to successfully implement the shelving filters using my biquad implementation, does that mean i can get rid of my biquad as a possible source of bug and continue to look elsewhere?

2. Do you have any pointers on how to debug Peak Filters? I know this is an open ended question, but I just need a different perspective. Currently how i tested the shelving filters was to pass 1 second samples worth of white noise and plot out the spectrum using audacity.

Thank you,

Moses

October 9, 2017
10:10 am
W Pirkle
Forum Posts: 249
Member Since:
January 29, 2017
Offline

Hi Moses:

I've been using both of the parametric filters (const Q and non-const Q) without issues for years now, so I am confident in the equations.

1. the shelving filters are first order and the parametrics are 2nd order, so you may still have an issue with your biquad implementation (BTW - I've implemented all four "standard" biquad types and done a lot of testing regarding modulated filters, including parametrics; hope to publish something soon)

2. I guess you aren't using RackAFX. If you were you could use the built-in analyzer to take the actual impulse, phase, frequency and step responses of your plugin/filter. The Frequency Response will show you exactly what your filter is doing and you can even click on the curves and slide the mouse around to read off actual values in dB from the response. Another option is to use the built-in oscillator in RackAFX and then sweep it, watching the 0.707 reference lines on the built-in oscilloscope to see the -3dB frequency.

White noise is not the way to test a filter of any kind (other than being able to see a ball-park curvature). The correct process is either take a digital impulse response, then FFT (as RackAFX does in its Analyzer) or use an oscillator and dB meter to send sinusoids in and measure what comes out.

- Will

October 11, 2017
9:59 am
W Pirkle
Forum Posts: 249
Member Since:
January 29, 2017
Offline

You can find free o-scope/spectrum analyzer plugins here:

https://bedroomproducersblog.com/2011/07/05/bpb-freeware-studio-best-free-oscilloscope-vstau-plugins/

I used this one:

http://bram.smartelectronix.co.....s.php?id=4

for verifying waveforms in AU/VST when I was working on the synth book.

- Will

October 23, 2017
7:03 pm
burningTr33
Member
Members
Forum Posts: 8
Member Since:
December 25, 2014
Offline

Hey Will thank you for your reply. I was definitely debugging my biquad and found some issues as to the order of combining some of the feedback signals.

1. the shelving filters are first order and the parametrics are 2nd order, so you may still have an issue with your biquad implementation (BTW - I've implemented all four "standard" biquad types and done a lot of testing regarding modulated filters, including parametrics; hope to publish something soon)

Please keep us posted (or at least me) I would love to see your implementations of the biquad filter.

2. I guess you aren't using RackAFX. If you were you could use the built-in analyzer to take the actual impulse, phase, frequency and step responses of your plugin/filter. The Frequency Response will show you exactly what your filter is doing and you can even click on the curves and slide the mouse around to read off actual values in dB from the response. Another option is to use the built-in oscillator in RackAFX and then sweep it, watching the 0.707 reference lines on the built-in oscilloscope to see the -3dB frequency.

Yea my project is in CoreAudio. The examples in your book are pretty clear and straight forward so i was able to port a lot of it over to my project without too much trouble. Your explanations on filters help me get through another book (DAFX by Udo Zölzer).

White noise is not the way to test a filter of any kind (other than being able to see a ball-park curvature). The correct process is either take a digital impulse response, then FFT (as RackAFX does in its Analyzer) or use an oscillator and dB meter to send sinusoids in and measure what comes out. I was using the white noise more of what you were describing.

Yes that was basically what I was doing. When you talk about a dgitial impulse response can it be a quick sine wave or something that lasts for a few milliseconds? Does it have to be a specific signal?

October 24, 2017
8:16 am
W Pirkle
Forum Posts: 249
Member Since:
January 29, 2017
Offline

Wow, that's cool that you are using CoreAudio - did you see the Forum topic about doing all the FX book projects in AU? The AU API is actually closer to the RAFX API than VST or AAX, thought that was not intended.

A digital impulse is just that: a sequence (from the FX book) h(n) = {1.0, 0.0, 0.0, ... , 0.0} and the sequence is the same length as the FFT that will transform it into a frequency response. The magnitude of the FFT is the frequency response. The argument of the FFT is the phase response. When your device (plugin or digital FX processor) has digital I/O, you can do this to get the frequency and phase response. Because this is a static impulse response measurement, you don't need to worry about overlap or windowing as you do with a constantly moving input stream that is FFT'd repeatedly.

For linear analog or digital systems, you can also use an oscillator and dB meter to stimulate and test the filter. You can do that with a plugin, but you'll need a very accurate sinusoid generator and a capture device that can detect the envelope and give you an accurate reading in RMS; from that you can convert to dB.

For non-linear analog or digital systems, it's a much larger problem. In this case the current preferred method is using a swept sinusoid followed by deconvolution to extract the impulse response. Several of my grad students have done their theses in this area and it really works well, though it is not so easy to setup the system to perform the operation.

http://pcfarina.eng.unipr.it/P.....nd2007.pdf

- Will

October 28, 2017
5:54 pm
burningTr33
Member
Members
Forum Posts: 8
Member Since:
December 25, 2014
Offline

Wow, that's cool that you are using CoreAudio - did you see the Forum topic about doing all the FX book projects in AU? The AU API is actually closer to the RAFX API than VST or AAX, thought that was not intended.

No I have not seen it. Ill have to look through it more closely when I get the chance. The thing with AU is that a lot of the useful templates have now disappeared with newer versions of xcode, and trying to set up older examples can be a bit of pain. I figured going through and learning about all the plumbing for CoreAudio was a better route. I got Learning CoreAudio by Kevin Avila and Chris Adamson and worked through their examples.

A digital impulse is just that: a sequence (from the FX book) h(n) = {1.0, 0.0, 0.0, ... , 0.0} and the sequence is the same length as the FFT that will transform it into a frequency response. The magnitude of the FFT is the frequency response. The argument of the FFT is the phase response. When your device (plugin or digital FX processor) has digital I/O, you can do this to get the frequency and phase response. Because this is a static impulse response measurement, you don't need to worry about overlap or windowing as you do with a constantly moving input stream that is FFT'd repeatedly.

For linear analog or digital systems, you can also use an oscillator and dB meter to stimulate and test the filter. You can do that with a plugin, but you'll need a very accurate sinusoid generator and a capture device that can detect the envelope and give you an accurate reading in RMS; from that you can convert to dB.

For non-linear analog or digital systems, it's a much larger problem. In this case the current preferred method is using a swept sinusoid followed by deconvolution to extract the impulse response. Several of my grad students have done their theses in this area and it really works well, though it is not so easy to setup the system to perform the operation.

http://pcfarina.eng.unipr.it/P.....nd2007.pdf

Thanks for your explanation. I'll look into this as well.

On a semi-related note, in a biquad filter, how would you suggest storing the delayed samples? Would you use a class member or a delay line with a 1 sample delay? I would think just for memory sakes and efficiency sake it would be best to just store it in a class member variable.

October 29, 2017
4:29 pm
W Pirkle
Forum Posts: 249
Member Since:
January 29, 2017
Offline

For the biquad, see the CBiQuad object in pluginconstants.h -- it uses member variables to store the delayed values. However, I have other versions that are array-oriented, with the delayed values and the coefficients packed into arrays.

- Will

Forum Timezone: America/New_York

Most Users Ever Online: 36

Currently Online:
3 Guest(s)

1 Guest(s)

Top Posters:

Skyler: 48

Derek: 46

Peter: 41

Frodson: 40

clau_ste: 39

Gwen: 32

JimmyM: 30

TheSmile: 27

EZB: 24

lppier: 23

Member Stats:

Guest Posters: 1

Members: 523

Moderators: 1

Forum Stats:

Groups: 11

Forums: 31

Topics: 546

Posts: 2138