State Variable Filter Fun

Algorithm development and general DSP issues

Moderator: frank

Post Reply
mdroberts1243
Posts: 18
Joined: Tue Jul 22, 2008 3:54 pm
Location: Ottawa, Canada
Contact:

State Variable Filter Fun

Post by mdroberts1243 »

As a learning aid, I decided to try and implement the state variable filter from Hal Chamberlain's book with variable frequency, Q, and use Pot2 to select one of the four outputs: low pass, band pass, high pass and notch.

Right away I ran into my own ignorance about how to scale the coefficients properly, especially w.r.t. oversampling. Would appreciate anybody's feedback in that area.

Everything seems to basically work (it will oscillate at infinite Q too), but running music through the board gave me the impression that something is lacking in the high end (any reason why it would roll off in the eval board design?).

Looking at a spectrum analyzer with a noise source as the input, the bandpass output and notch outputs don't look 'right', but I am not sure what to expect.

I have attached my code if anybody would like to play with it or point out my errors.

Code: Select all

;
; Test Programme.  Mono out & in on Left Channel.
;
; This implements a state-variable digital filter with frequency and Q controls and lets you select the filter output on the fly
; State-variable filter is called a biquad filter in the Spin Semi knowledge base (see 'peaking filters' in 'effects')
;
; Pot0 is frequency
; Pot1 is Q
;
; Uses Pot2 to select four possible outputs:
;        Low pass
;        Band pass
;        High pass 
;        Notch filter output
;
sqrt2	equ	1.4142135623730
f1scale	equ	0.05482427 * 8	;3 times oversampled?  should this be a factor of four?
;
lp_dly	equ	reg0
bp_dly	equ	reg1
f1	equ	reg2	;raw frequency coeff (needs to be scaled up appropriately)
q1	equ	reg3	;raw Q coeff (will be scaled and negative)
hp	equ	reg4	;high pass
notch	equ	reg5	;notch output
p0fil	equ	reg6
p1fil	equ	reg7
;
;Frequency control
	rdax	pot0,1		;get freq control
	rdfx	p0fil,0.01	;average with filter
	wrax	p0fil,1	
	wrax	f1,0
;Q control
	rdax	pot1,1		;get Q control
	sof	1,-1		;flip control to go from 1 to 0
	absa
	rdfx	p1fil,0.01	;average with filter
	wrax	p1fil,1
	wrax	q1,0

;Oversample (process multiple times) the filter to reach Fs/2
; LP = LP_DLY + F1 * BP_DLY
	ldax	bp_dly
	mulx	f1		; frequency coefficient 	sof	f1scale,0
	rdax	lp_dly, 1.0
	wrax	lp_dly, -1.0	; low-pass delay is not referenced any more, so safe to directly write LP value here
; HP = Input - LP - Q1 * BP_DLY
; ACC has -LP in it from preceeding WRAX
	rdax	adcl,1		; sample the input and add to ACC
	wrax	hp,1		; store (input - lp) temporarily
	ldax	bp_dly
	mulx	q1		; Q coefficent, should be negative and go from -2 to 0 for Q of 0.5 to infinity
	sof	sqrt2,0
	sof	-sqrt2,0		; scale and make negative the Q1
	rdax	hp,1
	wrax	hp,1
; BP = F1 * HP + BP_DLY
; ACC has HP in it already
	mulx	f1
	sof	f1scale,0
	rdax	bp_dly,1
	wrax	bp_dly,0		; store the BP output... bp_dly is o.k. as it is no longer referenced by the equations, clr ACC
; NOTCH = HP + LP
	ldax	lp_dly
	rdax	hp,1
	wrax	notch,0
;Completed pass of filter

;Oversample (process multiple times) the filter to reach Fs/2
; LP = LP_DLY + F1 * BP_DLY
	ldax	bp_dly
	mulx	f1		; frequency coefficient 0.0 to 3.14159
	sof	f1scale,0
	rdax	lp_dly, 1.0
	wrax	lp_dly, -1.0	; low-pass delay is not referenced any more, so safe to directly write LP value here
; HP = Input - LP - Q1 * BP_DLY
; ACC has -LP in it from preceeding WRAX
	rdax	adcl,1		; sample the input and add to ACC
	wrax	hp,1		; store (input - lp) temporarily
	ldax	bp_dly
	mulx	q1		; Q coefficent, should be negative and go from -2 to 0 for Q of 0.5 to infinity
	sof	sqrt2,0
	sof	-sqrt2,0		; scale and make negative the Q1
	rdax	hp,1
	wrax	hp,1
; BP = F1 * HP + BP_DLY
; ACC has HP in it already
	mulx	f1
	sof	f1scale,0
	rdax	bp_dly,1
	wrax	bp_dly,0		; store the BP output... bp_dly is o.k. as it is no longer referenced by the equations, clr ACC
; NOTCH = HP + LP
	ldax	lp_dly
	rdax	hp,1
	wrax	notch,0
;Completed pass of filter
	
;Oversample (process multiple times) the filter to reach Fs/2
; LP = LP_DLY + F1 * BP_DLY
	ldax	bp_dly
	mulx	f1		; frequency coefficient 0.0 to 3.14159
	sof	f1scale,0
	rdax	lp_dly, 1.0
	wrax	lp_dly, -1.0	; low-pass delay is not referenced any more, so safe to directly write LP value here
; HP = Input - LP - Q1 * BP_DLY
; ACC has -LP in it from preceeding WRAX
	rdax	adcl,1		; sample the input and add to ACC
	wrax	hp,1		; store (input - lp) temporarily
	ldax	bp_dly
	mulx	q1		; Q coefficent, should be negative and go from -2 to 0 for Q of 0.5 to infinity
	sof	sqrt2,0
	sof	-sqrt2,0		; scale and make negative the Q1
	rdax	hp,1
	wrax	hp,1
; BP = F1 * HP + BP_DLY
; ACC has HP in it already
	mulx	f1
	sof	f1scale,0
	rdax	bp_dly,1
	wrax	bp_dly,0		; store the BP output... bp_dly is o.k. as it is no longer referenced by the equations, clr ACC
; NOTCH = HP + LP
	ldax	lp_dly
	rdax	hp,1
	wrax	notch,0
;Completed pass of filter

;Oversample (process multiple times) the filter to reach Fs/2
; LP = LP_DLY + F1 * BP_DLY
	ldax	bp_dly
	mulx	f1		; frequency coefficient 0.0 to 3.14159
	sof	f1scale,0
	rdax	lp_dly, 1.0
	wrax	lp_dly, -1.0	; low-pass delay is not referenced any more, so safe to directly write LP value here
; HP = Input - LP - Q1 * BP_DLY
; ACC has -LP in it from preceeding WRAX
	rdax	adcl,1		; sample the input and add to ACC
	wrax	hp,1		; store (input - lp) temporarily
	ldax	bp_dly
	mulx	q1		; Q coefficent, should be negative and go from -2 to 0 for Q of 0.5 to infinity
	sof	sqrt2,0
	sof	-sqrt2,0		; scale and make negative the Q1
	rdax	hp,1
	wrax	hp,1
; BP = F1 * HP + BP_DLY
; ACC has HP in it already
	mulx	f1
	sof	f1scale,0
	rdax	bp_dly,1
	wrax	bp_dly,0		; store the BP output... bp_dly is o.k. as it is no longer referenced by the equations, clr ACC
; NOTCH = HP + LP
	ldax	lp_dly
	rdax	hp,1
	wrax	notch,0
;Completed pass of filter

;Process Pot2 to decide 1 of 4 output possibilities
	rdax	pot2,1   
	and 	%01100000_00000000_00000000	;mask off only 2 bits, leaving only 4 possibilities 
	skp 	zro,LowPass 			;if zero, the skip over other code to Test1 
	sof 	1,-0.25 				;subtract 1/4 
	skp 	zro,BandPass 			;if zero, skip over other code to Test2 
	sof 	1,-0.25 				;subtract 1/4 
	skp 	zro,HighPass 			;if zero, skip over other code to Test3 
	clr   					;clear the accumulator, there's 1/4 left in it! 
NotchOut:
	ldax	notch
	skp	run, Pend			;skip to end of code
LowPass:
	ldax	lp_dly
	skp	run, Pend			;skip to end of code
BandPass:
	ldax	bp_dly
	skp	run, Pend			;skip to end of code
HighPass:
	ldax	hp
	skp	run, Pend			;skip to end of code
;
Pend:
	wrax	dacl,1				;write both outputs
	wrax	dacr,0				
-mark
My blog: http://tubenexus.com
mdroberts1243
Posts: 18
Joined: Tue Jul 22, 2008 3:54 pm
Location: Ottawa, Canada
Contact:

Update.

Post by mdroberts1243 »

Correction... the code does seem to work and I've been able to see a clear notch and move it around... my issues seem to be with scaling the frequency coefficient properly. The bandpass shape gets weird as you dial up the frequency coefficient to the higher settings.

I measured a -3dB point at about 14,500Hz going into and out of the eval board.
-mark
My blog: http://tubenexus.com
seancostello
Posts: 74
Joined: Mon Sep 11, 2006 10:04 pm

Post by seancostello »

Lazy man's answer:

Do you really need 3x oversampling? Most of the oversampling SVFs I have seen are for floating point applications, where setting the cutoff above fs/6 results in an almost instant "blowup." Fixed point SVFs don't blow up in the same way as the floating point ones - they may oscillate, but often the aural result will just be a limit to the maximum cutoff. I haven't tried your FV-1 code, but I have programmed fixed point SVFs on the Blackfin, and they worked great, with the limitation that the maximum cutoff was fs/6, which worked fine for Mutron-type apps. Above fs/6, the filter would not sweep properly, but at least it wouldn't spit out a bunch of NaNs, as that is a concept that fixed point systems are blissfully unaware of.

As far as scaling the frequency coefficient, you could always use a low-order Taylor series approximation for sine, to get Chamberlin's frequency equation working, and use 3fs in the place of fs. However, the behavior of the cutoff with regards to the raw f1 coefficient is fairly linear up to around fs/6, which is a nice coincidence, so I don't think that the sine is necessarily for most applications, especially if your control is just a knob with fairly low precision.

Sean Costello
Post Reply