How do EQ plugins manage to draw the frequency response?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Especially with a couple of bands it seems to be very computationally intensive. Are there any tricks on increasing efficiency? Or is everyone just using giant lookup tables?

Thanks in advance for any hints.

Post

It's only a graphical display, so you don't need to calculate the curve at audio rate. Even 120 Hz refresh rate (probably overkill for an eq curve) is drastically lower than audio rate of 44100 samples per second for example. Plus these calculations don't need to be computed on the audio thread also saving further CPU time and it only needs to happen for plugins with the GUI open and only after control adjustments. To optimise further you could use approximations of trigonometric functions and SIMD operations for example.

Edit: as I'm sure will be pointed out it's best to profile your code before optimising, so as to make the biggest gains vs time spent optimising.

Post

Thanks for your answer.
I am still under the impression that it requires a fair bit of computational power and if the CPU is already under load due to some other processing going on, my implementation becomes very laggy.

Post

How are you calculating and drawing the curve?

Post

I'm using this formula (the one with the sin^2 term towards the end of their answer) to get the magnitude response of the individual biquads and am calculating between 230 and 850 values for the peak filter (depending on the q factor) spread logarithmically across the frequency spectrum.

I'm developing a Rack Extension (so Reason, not a VST) and am using the provided draw function to draw lines.

Post

OK, I've no idea how Rack Extensions handles threading, timers or drawing. I probably can't be much more help, sorry.

Post

If you're doing 230 to 850 values per frame at 60hz I'd guess it's the actual drawing that's the bottle neck, not the computation of where the points are.

Post

I'm retired from programming. Does reason only call your draw process when it knows your screen area needs redraw or does it just repeatedly call the draw function so long as your window is visible?

Unless you have some kind of animation overlay like an audio fft display in addition to the freq response plot then you only need to draw if part of your window has been clobbered and needs redraw, or if the user (or automation) has changed the eq settings and the freq plot has changed shape.

If reason doesn't keep up with settings changes then your code could probably set some global flag "parms changed so redraw the curve" when you detect settings changes.

If reason will let you create and draw/blit an offscreen graphics buffer--

Every time your draw routine gets called and the "redraw the curve" flag is set, you could erase and redraw the new eq curve into the offscreen buffer.

Every time your draw routine gets called you would blit the offscreen buffer into your visible window. Just if the "redraw the curve" flag is not set, then you know you can just blit that old picture you already offscreen rendered in the past, because you know the old picture is still valid and you don't have to draw the same picture all over again just to get the same result you already have in yer buffer from last time.

Graphics update customs keep changing but sometimes it is not very time-wasting just to always blit an image into a window even if the image doesn't need refresh, because the computer's graphic system will know that none of the relevant regions have been invalidated, and it will skip over copying bits it knows it doesn't have to update. Those OS details change so frequently that it is hard to keep up with it and know the exact bestest thang to do. :)

Maybe reason won't let you use offscreen buffer or blit between buffers, which would be a shame.

Anyway that kind of strategy would minimize the number of line draw loops, and line draw loops can sometimes be slow.

Post

Thanks for the input, guys. Unfortunately, I'm not really allowed to say anything due to the NDA I've signed, but the optimizations like only redrawing parts of the display that need updating I do have implemented, so that's not an issue. I was just wondering, if the actual calculation of the magnitude response could be sped up.

Post

If you're having slow down with just a couple of bands then you have some issues somewhere. If you can't discuss details of the implementation here then perhaps you should check with the Rack Extension developer forum. Otherwise everyone here is playing a guessing game of what might be up.

Post

Just calculate the freq response but do not actually display it, then you'll know if it's the calculation or the drawing routine that's your bottleneck.

I find it hard to imagine that it is the calculation tbh; it's not very intensive.

Post

matt42 wrote: Thu Jun 06, 2019 10:03 am If you're having slow down with just a couple of bands then you have some issues somewhere. If you can't discuss details of the implementation here then perhaps you should check with the Rack Extension developer forum. Otherwise everyone here is playing a guessing game of what might be up.
Definitely. I've discussed drawing behaviour with the other devs and think I've optimized everything I can there. I was just wandering whether I was missing something obvious to speed up the process.

Post

For FIR filters, it's pretty simple if you calculate it using complex numbers:

Code: Select all

  z = cos(hertz * 2*pi/sampleRate) + i * sin(hertz * 2*pi/sampleRate)
  gainAndPhase = 1*coef[0] + z*coef[1] + z^2*coef[2] + z^3*coef[3] + z^4*coef[4]....
For IIR filters, you can split IIR filters into a FIR filter and a IIR-poles-only filter in series. The IIR-poles-only filter is the inverse of a FIR filter, so you can calculate its gain as 1/inverseFirGain.

For instance, the classic 2 pole filter:

Code: Select all

  out = in*b0 + inDl1*b1 + inDl2*b2 + outDl1*a1 + outDl2*a2;
  inDl2 = inDl1; inDl1 = in; outDl2 = outDl1; outDl1 = out;
has the following gain at a given hertz (calculated in complex numbers):

Code: Select all

  z = cos(hertz * 2*pi/sampleRate) + i * sin(hertz * 2*pi/sampleRate)
  gainAndPhaseFIRPart = 1*b0 + z*b1 + z^2*b2;
  gainAndPhaseIIRPart = 1 / (1 - z*a1 - z^2*a2);
  finalGainAndPhase = gainAndPhaseFIRPart * gainAndPhaseIIRPart;
The cos()/sin() can be optimized out, so the only slow part is the division and the sqrt() in calculating the magnitude of the gain.
Last edited by MadBrain on Thu Jun 06, 2019 6:29 pm, edited 2 times in total.

Post

(double posted by mistake)

Post

Thanks, I'll look into this!

Post Reply

Return to “DSP and Plugin Development”