So, I had a go using your code and the problem i run into is right about here:
Because i used IIRFilter, not dsp::IIR::Filter, i tried writing my own version of getMagnitudeForFrequency() using Juce’s as a guide. And i think that’s where my math skills failed me again. I was getting values for magnitudes, but they weren’t quite right, and it’s been so long since i did math using complex numbers that i didn’t know where my version of the method went wrong. Otherwise, i could follow the rest of your code and see it looked like it was working as properly as it could with my incorrect magnitudes.
void FilterVisual::drawLowPass2P()
{
setLowpassCoefficients(sampleRate, cutoffFreq, resonance);
// for each increment of the width of the visual space
for (int i=0; i<getWidth(); i++)
{
// Convert the X position to a frequency, normalized 0-1
float xFreq = positionToFrequency( (float)i / (float)getWidth() );
int order = 4;//(5 - 1) / 2; // juce: (coefficients.size()) - 1) / 2;
// Get magnitude for that frequency.
float mag = getMagAtFreq(xFreq, sampleRate, order); // = filtCoeff.getMagnitudeForFrequency( (double)xFreq, (double)sampleRate ); // *** MAGNITUDE FUNCTION GOES HERE SOMEHOW ***
DBG(mag);
// Filtering to fit nicely in the display
float dbScale = 12.0f;
float dBs = Decibels::gainToDecibels( mag );
DBG(dBs);
dBs = jlimit( -dbScale, dbScale, dBs );
// Create Y position
float yPos = jmap( dBs, -dbScale, dbScale, 1.0f, 0.0f );
// Create path
if (i==0)
{
filterShape.startNewSubPath( i, yPos * getHeight() );
//DBG(yPos * getHeight());
}
else
{
filterShape.lineTo( i, yPos * getHeight() );
//DBG(yPos * getHeight());
}
}
DBG("");
}
void FilterVisual::setLowpassCoefficients(float SR, float CO, float Q)
{
float n = 1.0f / std::tan(MathConstants<float>::pi * CO / SR );
float nSquared = n * n;
float invQ = 1.0f / Q;
float c1 = 1.0f / (1.0f + invQ * n + nSquared);
a0 = c1;
a1 = c1 * 2.0f;
a2 = c1;
b0 = 1.0f;
b1 = c1 * 2.0f * (1 - nSquared);
b2 = c1 * (1.0f - invQ * n + nSquared);
}
float FilterVisual::getMagAtFreq(float freq, float SR, int order_)
{
constexpr std::complex<float> j (0.0f, 1.0f);
int order = order_;
float coefs[] = { a0, a1, a2, b0, b1, b2 };
std::complex<float> numerator = 0.0f, denominator = 0.0f, factor = 1.0f;
std::complex<float> jw = std::exp( -MathConstants<float>::twoPi * freq * j / sampleRate );
for (int n = 0; n <= order; ++n)
{
numerator += static_cast<float>(coefs[n]) * factor;
factor *= jw;
}
denominator = 1.0f;
factor = jw;
for (int n = order + 1; n <= 2 * order; ++n)
{
denominator += static_cast<float>(coefs[n]) * factor;
factor *= jw;
}
return std::abs(numerator / denominator);
}
So, i resigned myself to just drawing the filter curves as “ideal forms” using the cutoff and resonance without the actual filter magnitudes. Honestly, that’s all that’s necessary.
But thanks for the input, nonetheless. 