SyntaxBomb - Indie Coders

General Category => Showcase => Topic started by: GW on July 30, 2019, 02:16:24

Title: DSP filter playground
Post by: GW on July 30, 2019, 02:16:24
I wrote this over the period of a few days to test different methods of building digital filters. 

Custom oscillators and filters can programmed in Lua and are automatically picked up by the program on startup. 
The program will display the filters frequency response and the resulting fft of audio passed through the filter.  I've already converted many of the filters presented on http://www.musicdsp.org (http://www.musicdsp.org)

Have fun with the program and let me know if you encounter any bugs.
Download (win64) (http://www.kerneltrick.com)

(https://www.syntaxbomb.com/proxy.php?request=http%3A%2F%2Fwww.kerneltrick.com%2Fdownload%2Ffilterplayground.png&hash=ead326d243dc0ceed32c2dcba54527e8f62ad57a)
Title: Re: DSP filter playground
Post by: Brucey on July 30, 2019, 17:31:59
Looks interesting :)
I had a go, but sadly it EAVs on startup. It opens a window, but that's about as far as it gets before it falls over :(

Happy to do any further testing.
Title: Re: DSP filter playground
Post by: GW on July 30, 2019, 18:31:14
Thanks for testing it.
I had a few local paths referenced on startup.  :(   I've fixed and re-uploaded.  It should work now.
Title: Re: DSP filter playground
Post by: Midimaster on December 14, 2021, 15:14:11
As I now try to code also filters f.e. VCF's I would like to know more about your app. On the homepage I can seee a filter desription in LUA, but I do not understand how to use it. There are also no comments what the parameters mean.

Do you plan to open the whole code to everyone?

this is the VCF I found:
--filtername=Moog VCF
--extra=www.musicdsp.org/en/latest/Filters/
pi=3.14159265359
srate=44100.0

_cutoff=0.0
_res=0.0
fs = srate
Y1=0.0
y2=0.0
y3=0.0
y4=0.0
oldx=0.0
oldy1=0.0
oldy2=0.0
oldy3=0.0
_x=0.0
r=0.0
p=0.0
k=0.0


function init(cutoff,resonance)
_cutoff = cutoff * (srate/2)
_res = resonance
local f = (_cutoff + _cutoff) / fs
p = f * (1.8 - 0.8 * f)
k = p + p - 1.0

local t = (1.0 - p) * 1.386249;
local t2 = 12.0 + t * t;
r = _res * (t2 + 6.0 * t) / (t2 - 6.0 * t)
end

function process(x)
_x = x - r * y4
Y1 = _x * p + oldx * p - k * Y1
y2 = Y1 * p + oldy1 * p - k * y2
y3 = y2 * p + oldy2 * p - k * y3
y4 = y3 * p + oldy3 * p - k * y4
y4=y4-(y4 * y4 * y4) / 6.0
oldx = _x; oldy1 = Y1; oldy2 = y2; oldy3 = y3;
return y4;
end
Title: Re: DSP filter playground
Post by: GW on December 14, 2021, 17:44:00
I've made some improvements to the program since when this was first posted.  I can post the new version download soon.
This program is for testing out filter and oscillator ideas and test how they behave with different audio types.
The filters are written in Lua.  The "init" function is called any time there is a parameter change in the app and the slider values for frequency and cutoff of the filter are passed as arguments (0..1 for both).   
The "process" function takes 1 sample of the current playing audio and returns whatever you do with it.  All variables defined above the "init" function are global.

I also wrote a similar version in c# that works with scripts written in c# instead of Lua, but it's a little less complete than the Blitzmax version.  I can post a download to that version if it's easier to write filters in C#.

I'll update the top post when I update the download link with the newer version.

Are you having some trouble running the program?  Are you looking for an explanation of the Moog voltage controlled filter?  What kinds are problems are you having?
 
Title: Re: DSP filter playground
Post by: Midimaster on December 14, 2021, 18:01:37
A the moment I'm working on a update of my old (2012) FFT-BlitzMax-Algo. I will publish it here in some days (hours?) as free Public Domain.

But I'm searching for new features for it related to Time-Stretching, Bias-filters, etc... VCF with adjustable resonance (cutoff) and I want to learn how to inverse the FFT to be able to manipulate a FFT-result of a signal and inverse it back to real audio.

So I'm not interested in your App, but only in the source codes. At first I thought the LUA-part is not all, but now you write, that the two functions are all I need?  I'm suprised that the LUA-algos will work with only one single sample value at the same time? (one 32bit float?). I will have a second look on it.... How can Your function "know" whether this value if part of the resonance frequency? I thought it would be necessary to do first a conversation from pure singal into frequency spectrum...

Title: Re: DSP filter playground
Post by: GW on December 14, 2021, 21:20:35
All of the filters in the program are IIR filters. They don't require a history of the input beyond the little internal memory in the filter.   
the lua script init function takes 2 values, the 'cutoff frequency' and the 'resonance'.  From these, the filter sets itself up to process audio.  At each sample, raw audio goes in with the 'x' parameter and filtered 'x' comes out, based on the setup of the filter and it's internal state. 
Conversion to the frequency domain is never needed for filtering. This is why IIR filters are used everywhere. They are simple,stable,fast and don't screw up the phase of your signal in unknown ways. 

Here is a very simple LP filter, Step through the code and get an idea of how it works to help understand.

SuperStrict
Framework brl.basic
Import brl.glmax2d
Graphics 600,600
SetBlend alphablend
SetAlpha 0.5

Function LP#(x#)
Global a#=0.6,b#=1-a,z#
z=x*b+z*a
Return z
End Function

Local s#=1
For Local i% = 0 Until 600
If i Mod 40 = 0 Then s=-s
SetColor 255,255,255
DrawRect(i,300-280*s,3,3)
SetColor 255,0,255
DrawRect(i,300-280*LP(s),3,3)
'Print s + " " + LP(s)
Next
Flip
WaitKey



The moog vcf filter included in the download is not very good. I wouldn't recommend ever using it. you would be better off using a simpler Cookbook LP included here

--filtername=Cookbook LP
--extra=??
pi=3.14159265359
samplerate=44100.0

t0=0
t1=0
t2=0

a0=0
a1=0
a2=0
b1=0
b2=0

xold1=0
xold2=0
yold1=0
yold2=0
y=0

function init(cutoff,resonence)
t0=math.cos(pi*(0.99*cutoff+0.01))
t1=math.sin(pi*(0.99*cutoff+0.01))/(2.6*(resonence+0.001))
t2=1+t1

a0=(1-t0)/(2*t2)
a1=(1-t0)/t2
a2=(1-t0)/(2*t2)
b1=-2*t0/t2
b2=((1-t1)/t2)
end

function process(x)
yold2=yold1
yold1=y
y=a2*xold2+a1*xold1+a0*x-b2*yold2-b1*yold1
xold2=xold1
xold1=x
return y
end


Here is an example of using fft as a brick-wall lowpass filter.  It works here, but only because the phase is normalized, but it's a bad idea to do filtering this way.
The version you have of DSPplayground, i think, doesn't have the routines to run this script. But you can see how it's done though.

local ffi = require("ffi")
--oscname=fftTest
--extra=use fft as LP filter on Saw
--phase is -pi..pi freq is 20..22050 output is -1..1
pi=3.14159265359
srate=44100.0
N = 2048
oFreq = 0
ou1 = 0
local fftbuffptr = ffi.new("float*")
-----------------------------------------------------------------------------------------------
function init()
end
-----------------------------------------------------------------------------------------------
function process(phase,freq,user1,user2,user3,user4)
local i=0
if (oFreq ~= freq) or (user1 ~= ou1) then -- if the frequency changes or u1 slider changes
oFreq=freq
ou1=user1
luaosc.debug(jit.version)

fftbuffptr = ffi.cast("float*", ##FFTPTR) --the pointer to fft array is set at runtime replacing the tag
   
for i=0,N-1,1 do -- fill the array with a waveform
fftbuffptr[i] = ((i/N)*2.0)-1.0   --(math.random()*2)-1 --iif(i<1024,1,-1) -- iif(math.random()>0.8, math.random(-1,1),0)
fftbuffptr[i+1]=0
end
luaosc.FFT2048() --the fft happens in place the rusult is in the form of re,im,re,im...
for i=2+(user1*512.0),N-1 do      --or frequency = binNumber * sampleRate / numBins
fftbuffptr[i]= fftbuffptr[i] * 0.1--0.0 -- set freq beyond the cutoff 0
end;
luaosc.invFFT2048() -- place back into wavform shape
end

local idx = ((phase/pi)+1)/2.0
idx=math.min((idx*N),N-2)
return fftbuffptr[idx]
--return cosi(fftbuffptr[idx],fftbuffptr[idx+1], idx-math.floor(idx)) * (1)  --interpolate the result based on phase
end
-----------------------------------------------------------------------------------------------
function cosi(y1,y2,mu)
local mu2
mu2 = (1-math.cos(mu*pi))/2
return (y1*(1-mu2)+y2*mu2)
end

function iif(c,t,f)
if c then return t else return f end
end
Title: Re: DSP filter playground
Post by: Midimaster on December 17, 2021, 14:44:49
Ok, I tested your first example: LowPass example. I cannot believe it... but it worked!

I need to read more about IIR to understand what happens.

If you like, I would ask more questions....


Are there "reproducible" reason, why you use these values?

Global a#=0.6
Global b#=1-a

..or are this results of experiments? Where is the correlation between this and a "regular way" to descripe a LowPass-Filter, f.e.

LowPass-Parameters:
Cutoff frequency =500Hz
Slope = -12dB


In your third example you write about "DSP-Playground". Is this your APP? Or a third party project? The code is nothing what I can use with BlitzMax...


Now I have a look on your second example: CookBook LP. Again a stupid question: What is "CookBook"?

I tried to translate the code into BlitzMax:
Code (BlitzMax) Select
SuperStrict
Graphics 600,600
Local s#=1

VCF.Init(0.1, 111)

For Local i% = 0 Until 600
If i Mod 40 = 0 Then s=-s
SetColor 255,255,255
DrawRect(i,100-8*s,3,3)
SetColor 255,0,255
DrawRect i,500-8*VCF.Process(s),3,3
Next
Flip
WaitKey

Type VCF
Global a0:Double, a1:Double, a2:Double
Global            b1:Double, b2:Double

Function Init(CutOff:Double, Resonance:Double)
Local t0:Double = Cos(180*(0.99*CutOff+0.01))
Local t1:Double = Sin(180*(0.99*CutOff+0.01))/(2.6*(Resonance+0.001))
Local t2:Double = 1+t1

a0 = (1-t0) / (2*t2)
a1 = (1-t0) / t2
a2 = (1-t0) / (2*t2)
b1 = -2*t0  / t2
b2 = (1-t1) / t2
End Function


Function process:Double(x:Double)
Global xold1:Double, xold2:Double, yold1:Double, y:Double

Local yold2:Double = yold1
yold1 = y
y = a2*xold2 + a1*xold1 + a0*x - b2*yold2 - b1*yold1
xold2 = xold1
xold1 = x
Return y
End Function
End Type



I think I need to translate:
x=math.sin(pi*n)
' or here:
t0=math.sin(pi*(0.99*cutoff+0.01))/(2.6*(resonence+0.001))

into BlitzMax:
x = sin(180*n)
' or here:
t0 = Sin(180*(0.99*CutOff+0.01))/(2.6*(Resonance+0.001))


I have no idea, what parameters would be senseful for the init call?
function init(cutoff,resonence)


Title: Re: DSP filter playground
Post by: GW on December 17, 2021, 19:29:34

QuoteOk, I tested your first example: LowPass example. I cannot believe it... but it worked!
yes. the important thing is to step through the code and see how it works. This is about the simplest lowpass filter you can make.

QuoteAre there "reproducible" reason, why you use these values?
You can think of 'a' the (normalized) cutoff frequency related to the sampling rate of the audio.  This number is not linear though. 
A value 0.6 would roughly be about 3500 hertz with a sampling rate of 44100.
So below 3500h the audio is not attenuated, and at 3500h the audio drops 3db.  Because this coefficient number is not linear, you can use: a = exp(-2.0*pi*cutoff_frequency/samplerate) to get proper value for a.  Different filters use different methods to set this up.

If you want a steeper roll-off like 12db. you'll need a filter with more coefficients. The sample filter I posted before is a 1-pole filter. with a 6db cut.

QuoteIn your third example you write about "DSP-Playground". Is this your APP? Or a third party project? The code is nothing what I can use with BlitzMax...
I wrote the app.  Read the top post for explanation. The program is was made for the propose you want. To experiment with writing filters and effects and to see how they effect the audio in real time. 

QuoteNow I have a look on your second example: CookBook LP. Again a stupid question: What is "CookBook"?
Cookbook LP refers to the Lowpass filter given in the 'Audio EQ Cookbook'  https://github.com/WebAudio/Audio-EQ-Cookbook/blob/main/Audio-EQ-Cookbook.txt
It explains biquad IIR filters and how to design them.  I don't know if this filter has a proper name so I call it 'CookBook LP'.

QuoteI have no idea, what parameters would be senseful for the init call?
Both values are in the range of 0..1 . In the DSP playground program, these are the sliders 'F' and 'Q'  F == Cutoff frequency and Q == resonance.