electro-music.com   Dedicated to experimental electro-acoustic
and electronic music
 
    Front Page  |  Radio
 |  Media  |  Forum  |  Wiki  |  Links
Forum with support of Syndicator RSS
 FAQFAQ   CalendarCalendar   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   LinksLinks
 RegisterRegister   ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in  Chat RoomChat Room 
go to the radio page Live at electro-music.com radio 1 Please visit the chat
  host / artist show at your time
today> Modulator ESP Adventures In Sound
 Forum index » DIY Hardware and Software » Microcontrollers and Programmable Logic
Digital LPF with adjustable resonance and cutoff
Post new topic   Reply to topic Moderators: State Machine
Page 1 of 1 [10 Posts]
View unread posts
View new posts in the last week
Mark the topic unread :: View previous topic :: View next topic
Author Message
volting



Joined: May 09, 2010
Posts: 11

PostPosted: Wed Jun 15, 2011 9:20 am    Post subject: Digital LPF with adjustable resonance and cutoff
Subject description: Algorithms / theory for adjustable FIR / IIR suitable for dsPIC
Reply with quote  Mark this post and the followings unread

Im designing a MIDI controlled digital monosynth synth based on a dsPIC33F, at the moment it has three NCOS, an LFO and ADSR.

Iv been looking for the last few weeks for algothrithms, code, theory etc. (basically anything) on digital filters that can have adjustable resonance and cut-off, but Iv come up with nothing useful -maybe I'm missing some terminology which would provide better search results!

I'm in coding C using MPLAB C30, and plan to utilize MCHPs dsp library if possible, so solutions compatible with would be preferable, but really I'm just looking for anything to get an idea of how I can do this.
Back to top
View user's profile Send private message
JovianPyx



Joined: Nov 20, 2007
Posts: 1988
Location: West Red Spot, Jupiter
Audio files: 224

PostPosted: Wed Jun 15, 2011 12:25 pm    Post subject: Reply with quote  Mark this post and the followings unread

My favorite is the digital State Variable Filter. It has 2 pole response and can be any or all of bandpass, lowpass, highpass or bandreject. One parameter controls Fc, another parameter controls Q.

http://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/

http://www.musicdsp.org/archive.php?classid=3#142

I used the block diagram to construct the code to implement it in an FPGA.

Note that the Q input is really 1/Q. With my synths that use this filter, I use a patch editor written in VB.NET which lets the user select a Q value which is converted to 1/Q before sending to the synth. This relieves the synth of having to do a divide (which is time expensive). The other thing I should mention is that the F input is not exactly Fc, you can see that the computation involves the sine function, but because the filter is useful only up to about 1/6 of the sample rate, it turns out that the value is linear enough at the low end to use directly as a pitch control (as in when the filter is used as a resonator) for several octaves (I got 5).

When I started doing this stuff, I too had a need for a filter with easily adjusted Fc and Q. I found lots of filters, but most required complex arrays of coefficients and that to change the cutoff or Q would require tables of data that needed interpolation.

The main drawback of the SVF is the fact that it doesn't work well with Fc set above 1/6 of the sample rate. For 44.1 KHz, this is 7.35 KHz. For this reason, I run those synths with a sample rate of at least 100 KHz, but many are 200 KHz or 250 KHz (which I know you can't do with the built in DACs on dsPIC - but they will operate at 100 KHz).

Another thing to remember about this type of filter is that it suffers from Q enhancement. A static Q value will not have the same resonance at all values of Fc, rather, resonance increases as Fc increases and the filter acts as if the Q were increased. Also, the resonance causes amplification of the signal at the frequency of Fc. This means that if the level of the system is set to just touch the upper amplitude limit with Q = 1.0, increasing the Q will cause distortion either from wrapping or from clipping if clipping is implemented. So this must be considered. I solve this by limiting the level of the input so that at maximum intended Q, the signal doesn't distort.

_________________
FPGA, dsPIC and Fatman Synth Stuff

Time flies like a banana.
Fruit flies when you're having fun.
BTW, Do these genes make my ass look fat?
corruptio optimi pessima

Last edited by JovianPyx on Wed Jun 15, 2011 2:58 pm; edited 1 time in total
Back to top
View user's profile Send private message Visit poster's website
volting



Joined: May 09, 2010
Posts: 11

PostPosted: Wed Jun 15, 2011 2:34 pm    Post subject: Reply with quote  Mark this post and the followings unread

Thanks for your reply, rich with useful info. Funnily enough I had read about SVFs but obviously not enough -I didn't realize they allowed adjustment of Fc and Q.

At present I'm running the DAC at 77.4K to allow for enough cycles for synthesis between DAC loads, I guess once I implement the filter Ill do some testing to determine the trade-offs to see if its worth increasing Fs (prior to reading your post I had been considering lowering it!).
Back to top
View user's profile Send private message
volting



Joined: May 09, 2010
Posts: 11

PostPosted: Thu Jun 23, 2011 4:42 am    Post subject: Reply with quote  Mark this post and the followings unread

I tried to implement the algorithm, hardcoding the paramaters for testing... I reduce the amplitude of the sample before filtering to avoid clippling, but all Im getting is noise on the output. Does signage matter to this algorithm, as my current setup is for signed ints for sample and DAC..

Below is a snippet of the code, which resides in the DAC update ISR. ISR execution time is fine, so this is not the problem.

Code:
  currentSample = currentSample >> 1;//Reduce the amplidtude first to avoid clipping

   
    //  8/100 = 0.08 = F1 for 1KHz, Fs = 77402Hz
    static int L=0,B=0,H=0, N=0, F1_num=8, F1_denom=100, Q1=1, D1,D2;
   
    L = D2 + (F1_num * D1)/F1_denom;
    H = currentSample  - L -  Q1 * D1;
    B = (F1_num * H)/F1_denom + D1;
    N = H + L;
   
    D1 = B;
    D2 = L;
 
    currentSample = L;


Edit:
The I jumped to conclusions a little too quickly, I had some school boy errors that I have fixed now. It seems to be working (or atleast not producing noise). Now for some tests!!
Back to top
View user's profile Send private message
JovianPyx



Joined: Nov 20, 2007
Posts: 1988
Location: West Red Spot, Jupiter
Audio files: 224

PostPosted: Thu Jun 23, 2011 7:07 am    Post subject: Reply with quote  Mark this post and the followings unread

For integer based DSP there's a some important points:

As with floating point, the algorithm needs to deal with signals that range between -1 and 1. That probably makes your face screw up because you're dealing with integers and those values allow for only 3 DAC values -1, 0 and +1.

While the arithmetic the processor will do is integer - we fool it a little by "pretending" the integers represent fixed point numbers in the range of -0.99999 to 0.99999. This means that (for 16 bits) 0x3FFF is about .999. This has an odd effect on multiplication (and division).

What happens if we multiply 0.999 by 0.999 ? Well, in floating point, the answer is 0.998001.

Now do this: 0x3FFF * 0x3FFF and the answer is: 0x0FFF8001 OH NO... OVERFLOW - there are more than 16 bits there!

What is needed is to then right shift the result by 15 bits to get the result back into the 16 bit range and preserve fixed point magnitude. This means that while your processor may allow 32 bit numbers, you can use only 16 of the bits (now knowing that 16x16 bit multiplies can result in 32 bit products). Note that the right shift must be arithmetic and not logical. Logical right shifts will insert zeroes from the left where arithmetic shifts will shift in a copy of the sign bit.

I've never used a divide operation in DSP, so I don't have a well thought out explanation for how it's done - I work with FPGAs which aren't divide friendly, so I avoid them at all costs... That doesn't mean that DSP can't work, it does. On the odd occasion where I must use a divide, I use either a table or if the divide is by a power of 2, I do arithmetic right shifts.

_________________
FPGA, dsPIC and Fatman Synth Stuff

Time flies like a banana.
Fruit flies when you're having fun.
BTW, Do these genes make my ass look fat?
corruptio optimi pessima
Back to top
View user's profile Send private message Visit poster's website
volting



Joined: May 09, 2010
Posts: 11

PostPosted: Thu Jun 23, 2011 12:51 pm    Post subject: Reply with quote  Mark this post and the followings unread

You are correct the problem is overflows. I had been avoiding using fixed math (sticking to what I know) probably at my own peril! and almost have this working without it.
When Q1 is less than 0.5 or Fc is not too low (havent done alot of testing here but its fine if its over 1Khz),
the output is as expected for all input frequencies, but if these conditions arent met then at higher input frequencies say over 1500Hz a lot of sharp edges are introduced which to me
looks like overflows are occuring somewhere.

This (the below code) is where Im at, at the moment which Ill admit looks bit ugly...
The builtin functions for multiplication return long ints and the numerator parameter of the builtin signed division function is also a long int,
so should this should take care of overflows and in most cases it does.
I think the problem is in the third line from the bottom,
The max possible return val of __builtin_divsd(__builtin_mulus(F1_num, H), F1_denom) is 29490
i.e (9*32767) / 10 = 29490, and B can be anything up to 32767 so obvious overflows here, not really sure how this could be avoided.

Code:
//  8/100 = 0.08 = F1 for 1KHz, Fs = 77402Hz
    static int  L=0,B=0,H=0, N=0,  F1_denom=100, Q1_denom=10;
    static unsigned int Q1_num=4,  F1_num=8;
    static int I = 0 ;
    I =  currentSample;
     
    L = L + __builtin_divsd( __builtin_mulus(F1_num, B), F1_denom);
    H = __builtin_divsd(__builtin_mulus(Q1_num, I),Q1_denom) - L - __builtin_divsd(__builtin_mulus(Q1_num,B),Q1_denom);
    B = __builtin_divsd(__builtin_mulus(F1_num, H), F1_denom) + B;
    N = H + L;
    currentSample = L;


PS
Thanks for taking the time to help me out with this, it means alot.

Edit
Just noticed and fixed an error in my first paragraph, the problem is when Q1 is 0.5 or over not under

Last edited by volting on Thu Jun 23, 2011 1:59 pm; edited 3 times in total
Back to top
View user's profile Send private message
JovianPyx



Joined: Nov 20, 2007
Posts: 1988
Location: West Red Spot, Jupiter
Audio files: 224

PostPosted: Thu Jun 23, 2011 1:44 pm    Post subject: Reply with quote  Mark this post and the followings unread

I'll just write this inside code blocks because it's being a pain in the rear... It keeps eating and changing what I write ... GRRRRR

Code:

I just looked at the earlevel site that I gave you - this is somewhat different than the Stanford site where I originally got the information about the state variable filter.  I couldn't find it, so I posted the earlevel one thinking it was equivalent.  It had, at least, the block diagram and basic information.  But they do mention that Q can be from 0.5 to infinity (where when Q=infinity, the filter oscillates).  The Stanford site was more specific saying that the Q cannot be less than 0.5.  I've personally never run the filter at less than Q=1.0, mainly because I was interested in Q >= 1.0 and because when Q is less than 1.0, q is then greater then 1.0 and that presents a problem for fixed point arithmetic meant to represent numbers from -0.99999 to 0.99999.

In C I wrote the overflow protection using integers this way:

prod = ( a * b ) >> 17;


This was for a, b and prod being signed 18 bit integers.

The double right pointing arrow operator does the right shifts needed.

C (or C++) is a great help for me because I can work out the mechanics of integer arithmetic and still print out answers on a screen which is something that is difficult at best to do with FPGA hardware.  However, I don't do realtime DSP in C, I do it in the FPGA using Verilog.

One thing I can also say/recommend is to work with floating point first if you can.  That way, you can ensure that your understanding of the algorithm works correctly in a "test bench".  Once you understand the mechanics of the algorithm, you can then move to fixed point using integers in the same algorithmic structure which will process your DSP faster.

_________________
FPGA, dsPIC and Fatman Synth Stuff

Time flies like a banana.
Fruit flies when you're having fun.
BTW, Do these genes make my ass look fat?
corruptio optimi pessima
Back to top
View user's profile Send private message Visit poster's website
volting



Joined: May 09, 2010
Posts: 11

PostPosted: Mon Jul 04, 2011 8:25 am    Post subject: Reply with quote  Mark this post and the followings unread

Iv been busy, so I havent been able to spend much time on this,
but today Iv managed to get it working.
I switched to using fixed point math, and made better use of the DSP engine by using some of __builtin functions that are C wrappers for assembly instructions. To avoid over/underflow addition and subtraction is performed using the Microchips fixed point library functions.

Cutoff adjustment sounds a little bit granular at the moment, but I think that due that fact that adjustment value is been scaled from 10 to 16bits. The reason its scaled from 10bits is that its coming from the ADC of PIC18F. The PIC18F is handling all the i/o lcd, switches, pots, midi etc.. The pots are temporary, Im waiting on rotary encoders which should give me as much adjustment resolution as I need.


The code ain't pretty but it works!!!
It generates 74 instructions and runs at approx 1.88us at FCY = 39MHz.

Code:

   //Global Filter variables..
   //define inital vals..
   volatile int L=0,H=0, N=0, B=0;
   volatile int F1 = Q15(0.3);
   volatile int Q1 = Q15(0.2);
   volatile int I;
 

   //snippet from DAC isr

   register int accA asm("A"); // accumulator variable (40 bits long)   
    I = currentSample;
    //F1 * B + L
    accA = __builtin_mpy(F1, B, NULL, NULL, 0, NULL, NULL, 0);
    L = _Q15add(L, __builtin_sac(accA,0));
     
    accA = __builtin_mpy(Q1,  I, NULL, NULL, 0, NULL, NULL, 0);
    H =   __builtin_sac(accA,0);
   
    accA =  __builtin_mpy(Q1, B, NULL, NULL, 0, NULL, NULL, 0);
    H =   _Q15sub(H, __builtin_sac(accA,0));
    H = _Q15sub(H, L);
   
    accA = __builtin_mpy(F1, H, NULL, NULL, 0, NULL, NULL, 0);
    B =   _Q15add(B,  __builtin_sac(accA,0) );
    N = _Q15add(H, L);



Im also working on a more efficient version of the filter code which should half execution time by making better use of the DSP instructions, but Im at a stand still with that at the moment, to me the code looks like it should work, but for some reason it doesnt!! i guess It was time I learnt assembly and was done with it!!

Code:
I = currentSample;
   
    /*********************************************************************/
    // L = F1 * B + L
    //
    // output accA = L
    /*********************************************************************/
    accA = __builtin_mpy(F1, B, NULL, NULL, 0, NULL, NULL, 0);                          //accA = F1 * B
    accA = __builtin_add(accA, L, 0);                                                   //accA += L
    L =    __builtin_sac(accA, 0);                                                      //keep a copy of the LowPass o/p     

     
    /*********************************************************************/
    // H = Q1 * I - L - Q1*B
    //
    // output accA = H
    /*********************************************************************/
    accA =  __builtin_msc(accA, Q1, B, NULL, NULL, 0, NULL, NULL, 0, 0, 0);            //accA = accA - (Q1 * B)
    accA =  __builtin_msc(accA, Q1, I, NULL, NULL, 0, NULL, NULL, 0, 0, 0);            //accA = (Q1 * I) -  accA
    H =     __builtin_sac(accA, 0);                                                    //keep a copy of the highPass o/p     

   
    /*********************************************************************/
    // B = F1 * H + B
    //
    //
    /*********************************************************************/
    accA = __builtin_lac(B, 0);                                                         //load B into accA
    accA = __builtin_mac(accA, F1, H, NULL, NULL, 0, NULL, NULL, 0, NULL, 0);           //accA = accB + (F1 * H)
    B =    __builtin_sac(accA, 0);                                                      //keep a copy of the highPass o/p
   
    /*********************************************************************/
    // N = H + L
    //
    /*********************************************************************/
    N = _Q15add(H, L);
Back to top
View user's profile Send private message
JovianPyx



Joined: Nov 20, 2007
Posts: 1988
Location: West Red Spot, Jupiter
Audio files: 224

PostPosted: Mon Jul 04, 2011 8:53 am    Post subject: Reply with quote  Mark this post and the followings unread

Ah! Very good! I wasn't aware you were doing this on a PIC. I've got a project I want to do on a dsPIC that will use the same filter! But I will be doing my project in assembly, I "feel" I have more control over what is going on that way.

It sounds like you are using a PIC for mundane stuff and dsPIC for DSP... yes?

_________________
FPGA, dsPIC and Fatman Synth Stuff

Time flies like a banana.
Fruit flies when you're having fun.
BTW, Do these genes make my ass look fat?
corruptio optimi pessima
Back to top
View user's profile Send private message Visit poster's website
volting



Joined: May 09, 2010
Posts: 11

PostPosted: Mon Jul 04, 2011 10:18 am    Post subject: Reply with quote  Mark this post and the followings unread

Yes your right, Im using a PIC18F43K22 for processing all IO (except audio of course). It serializes all the inputs into a simple command set which it pipes over SPI to the dsPIC, using a synchronous protocol like SPI eliminates the overhead of software syncing and buffering of the the midi and other commands. The other nice thing about using the PIC18F is that its a 40pin IC (35 GPIO) so I can connect a number of switches, rotary encoders and a parallel interface LCD without the need for shift registers , muxes or port expanders etc...

I have been following your thread on your dsPIC synth, ...good to hear your still working on it.

Coding the DSP intensive stuff in Assembly (i.e writing C callable assembly functions) is probably the way to go I think as you cant really gain access to the DSP engine through C unless you use the builtin functions, even though there is one for each DSP instruction, and from what I can tell using the builtin function is the same as using inline assembly, I think it would be possible to write more efficent code in pure assembly. Some day soon (when I run out of cycles..) Ill learn it, until then Ill see how far I can push it with C & the builtins, --I just halfed my DAC ISR time by switching to fixed point math, and therefore removing any divide instructions which I just found out take 18 cycles !! so this has given me a lot of breathing room.

Thanks again,
V
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic Moderators: State Machine
Page 1 of 1 [10 Posts]
View unread posts
View new posts in the last week
Mark the topic unread :: View previous topic :: View next topic
 Forum index » DIY Hardware and Software » Microcontrollers and Programmable Logic
Jump to:  

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Forum with support of Syndicator RSS
Powered by phpBB © 2001, 2005 phpBB Group
Copyright © 2003 through 2009 by electro-music.com - Conditions Of Use