Page 1 of 3

Freely Configurable Filter

PostPosted: Sun Sep 02, 2018 8:14 pm
by martinvicanek
This is one of my current projects, a freely configurable filter. The user specifies a transfer curve in the frequency domain by placing control points, which are interpolated to give the desired transfer function. The application calculates FIR filter coefficients and provides a ready-to-use filter. The actual filter characteristic may deviate to some extent from the specification because the filter length is 1024 taps; longer filters have higher latency and use more CPU.

DISCLAIMER: The Ruby code violates all style guides and recommendations. In particular, I am guilty of using for-loops - a lot of them. :oops: Read at your own risk! Minors stay away!

ACKNOWLEDGEMNET: My special thanks to Spogg, :ugeek: who solved a Preset Manager problem that had given me a serious headache. I am also grateful for some expert advice from Tulamide :ugeek: concerning graphics optimization.

Please download, use, and give some feedback!

Re: Freely Configurable Filter

PostPosted: Sun Sep 02, 2018 9:53 pm
by tulamide
1) The idea itself
Brilliant! Has this been done ever before? A paint-your-own-filter? I don't think it can become any more versatile, or do I miss something?

2) The real deal (DSP)
Once again top quality work. I love how the Impulse Response module works. Certainly gets the most out of green, speedwise. That I don't understand anything of it, is another sign of quality :lol:

3) Ruby
Well done. Yes, you ignore a lot, but to make such a complex Ruby code work really is an achievement. However, no code that couldn't be optimized. So here are a few things.

Starting with the smallest, you seem to be unaware of some range features.
Code: Select all
1..12 ## range from 1 to 12
1...12 ## range from 1 to 11

## more precisely, three dots define a range excluding the last value.
##no need for this:
1..n-1
##replace with
1...n


I highly recommend to replace the for loops. It isn't difficult, but saves CPU load.
Code: Select all
for k in 0..@xLabel.size-1 do
   rect = [@x0 - lwh + @w*@xLabelPos[k], @y0 + @h + 0.5, lw, lh]
   v.drawString @xLabel[k], font, sf, rect, lBrush
end

Code: Select all
@xLabel.each_with_index do |label, i|
   rect = [@x0 - lwh + @w*@xLabelPos[i], @y0 + @h + 0.5, lw, lh]
   v.drawString label, font, sf, rect, lBrush
end


Move all creations of class instances for Pen, Brush, Color and Font to the init method, and use their class methods to change colors or sizes (for example, @myPen.setWidth or @myBrush.setColor. This way you reduce the work the GC has to do, which results in faster execution overall. This is only important in settings, where you draw repeatedly over time, like in your module.

Last, splines. You could have saved a lot of cpu by using my Spline class. I explicitly designed it for use in realtime environments, using
- faster calculations (no expensive polynomial math)
- adaptive (switchable) adjustment of drawing point clouds (= more segments for longer curves, less for shorter)
- anything from linear to cubic through one class
- nodes support mirroring of control points
- methods for mouse control included (detects mouse on node and on control points)
- optional output of interpolation points, independend on drawing settings (!)
- comes with an extensive pictured manual, that describes each method in detail, including usage

With your current skills in Ruby, it would have been a breeze to use the Spline class. Give it a try: http://flowstone.guru/downloads/ruby-for-flowstone-expansion-spline-class/

Re: Freely Configurable Filter

PostPosted: Sun Sep 02, 2018 10:37 pm
by martinvicanek
Thanks, Tula, some stuff for me to consider in more detail. I know your spline classes for sure, but frankly I just couldn't get anything to work, not even a very basic "hello world" type of thing, sorry. If you say there is a manual I will give it a second try.

I am also aware of TheOm's work, however I figured it was easier to write my own code than to modify his to my needs. In my opinion cubic splines are useless for drawing curves because they always overshoot. How often have I cursed at Photoshop for implementing cubic splines? That's why I have included the smart (monotone) interpolation mode. I know you can do Bezier - that*s OK for parametric curves [x(t),y(t)] but not so much for functions y(x).

Now tell me, why are those "each"-iterators preferrable over for-loops? To me the notation is awkward, when I see |x|
I always associate abs(x) (or x.abs for that matter). I need a hard and fast advantage to switch. And then, how can I control excecution order?

Again, thanks for your hints, much appreciated!

Re: Freely Configurable Filter

PostPosted: Mon Sep 03, 2018 12:02 am
by tulamide
I know what you mean. Splines can be difficult for Y(x) functions. TheOms editor is perfect for those. However, you can get splines over fixed x-values with the Spline class (for example by switching off auto-resolution and instead set a resultion of n, which will evenly divide the spline distance on the x-axis). But I admit it was more about speed (I used the Splines once to visualize frequencies, so I can assure you that class is fast), than difficulties in translating the values, that drived me to mention the class.

The each iterators are a fixed component of Ruby's core (enumerator class). They are as fast as C ececution is. 'For loops' can't take advantage of internal optimizations and so are just as fast as Ruby is (interpreted). The same goes for the range functionality I pointed out. Using the three dots saves a few cycles per iteration (n-1 is calculated per iteration, not once). By drawing like 20 times per second that is already a lot.

And what do you mean by "control execution order"? It is the same as with your for loops. You should read more about iteration in Ruby, as it can be crucial to optimise speed.
General enumerator info: https://ruby-doc.org/core-1.9.3/Enumerator.html
Arrays implement enumerators and have their own methods built upon enumerator, for example ::map https://ruby-doc.org/core-1.9.3/Array.html#method-i-map

Re: Freely Configurable Filter

PostPosted: Mon Sep 03, 2018 5:00 am
by RJHollins
Man ... this is so cool, MartinV.

8-) 8-) 8-)

Re: Freely Configurable Filter

PostPosted: Mon Sep 03, 2018 10:22 am
by Spogg
Absolutely fantastic Martin!

You just draw the filter characteristics you want, and I totally love it.

I’m wondering if this could be used to accurately match a spectral profile for mastering somehow. Just thinking out loud.

Cheers

Spogg

Re: Freely Configurable Filter

PostPosted: Mon Sep 03, 2018 12:53 pm
by KG_is_back
I'm especially fascinated by the minimum phase response mode. I always knew that it should be possible to calculate minimum phase response for given magnitude curve and I've been hunting for such algorithm for years now... Do you by any chance have some references to what you've done there? I can see some cepstrum log/exp sorcery going on there but I can't quite figure it out...

Re: Freely Configurable Filter

PostPosted: Mon Sep 03, 2018 6:43 pm
by martinvicanek
Spogg wrote:I’m wondering if this could be used to accurately match a spectral profile for mastering somehow. Spogg

Like this?
viewtopic.php?f=2&t=3972&start=50#p22927

Re: Freely Configurable Filter

PostPosted: Mon Sep 03, 2018 6:55 pm
by martinvicanek
KG_is_back wrote:[...] minimum phase response mode.[...] Do you by any chance have some references?

Julius O. Smith is a good source:
https://www.dsprelated.com/freebooks/sa ... esign.html

Here is a concise recipe:

1. Given a (real valued) frequency magnitude response X[k], k = 0..N-1 calculate the cepstrum y[n]

y[n] = IFFT{ log|X[k]| }

2. Flip all negative quefrencies to positive (for causality)

for n = 1 to N/2-1 { y[n] = y[n] + y[N-n] }
for n = N/2+1 to N-1 { y[n] = 0 }

It is a good idea to taper the cepstrum to avoid a step at n = N/2, for instance

for n = 0 to N/2 { y[n] = y[n]*(1 - 4n^2/N^2) }

3. Transform back to frequency domain

Z[k] = FFT{ y[n] }

and undo log by taking the (now complex) exponential:

W[k] = exp( Z[k] ) = exp( Re Z[k] )*( cos( Im Z[k] ) + i*sin( Im Z[k] ) )

4. Convert to time domain to obtain the impulse response IR[k]:

IR[n] = IFFT{ W[k] }

It is a good idea to taper the IR at n = N-1, for instance

IR[n] = IR[n]*(1 + cos(pi*n/N))/2

There you go, ready for convolution.

Re: Freely Configurable Filter

PostPosted: Mon Sep 03, 2018 9:22 pm
by BobF
WoW what more can be said! As always Martin, your projects are truly amazing. Very cool indeed.

Later then, BobF.....