FIR bandpass with >500 taps and 96 kHz sample rate

jldmv

New member
Hello, this is my first post here, please bear with me if I unknowingly break any netiqutte.

For days I have been googling, reading many threads, the Audio and OpenAudio documentation, and I am stuck. I may have missed some relevant links, but, believe me, I have tried.

This is my project:
I want to apply a real time FIR band pass filter to an analog signal with teensy 3.6, Audioshield and Arduino IDE.
I would like to use 96 kHz sampling frequency, have a 1 kHz bandpass and a stopband attenuation of -40dB. These conditions can be relaxed slightly.
I would like to use a FIR filter, and that would need something between 500-800 taps (approximately) to get those requirements.
Initially, the passband frequency could be fixed (say 25 kHz), but in future implementations, I would like to tune it from 10 to 48 kHz.

With the Audio library for Teensy (AudioFilterFIR class) I am limited to 200 taps and the default 44117 samples/s rate. I can get that working without issues.

With the OpenAudio_f32 library, the AudioFilterFIR_F32 class is also limited to 200 taps, but could be used at higher sample rate, if I am not mistaken. And the AudioFilterFIRGeneral_F32 class can handle more taps, but it is not ready to accept a 96 kHz sampling rate, again, if I am not mistaken.

So, my question is: is there an 'easy' way to get the higher number of coefficients and the higher sample rate at the same time? I think I do not really need float precision. By 'easy' I mean that I am not a programmer, and I would rather use something similar that is already working and I would only tweak it with my whims.

If you could give me some hint or point me to an example, post, web page ... from which I could build upon, it would be most greatly appreciated.

And I also would like to thank all the developers and contributors for the Tennsy and audio libraries. You are awesome!

Cheers,

Pepe
 
Last edited:
As the band-pass needs coefficients for intrinsic low- and high-pass, why not split the filter in first low-pass and then high-pass. maybe the 2x200 tabs give you sufficient steep transition bands.
Concerning 96 kHz, there are multiple threads on how to do that. If you need a working example, years ago I did some tests. you find code and result in https://github.com/WMXZ-EU/test_sgtl5000
 
For large numbers of FIR taps its best to use FFT based convolution - there are various threads here about this, I doubt there's a particularly simple one - try searching "partitioned convolution" perhaps?
 
Thank you! WMXZ and MarkT.
I'm working on it. The idea of using two chained filters works, at the standard rate. And it seems that at 96 kHz 200 taps could do it, but I am failing with the sampling rate changes. By the looks of it, I may have some installation IDE problem.
I will get back when I sort it out.
@MarkT: I am reluctant to change the approach. I am not full time with this project, and I'd raher follow the current path.
Thanks again!
 
Hi!

I'm the original author of the OpenAudio library (though Bob Larkin carries it forward now). In OpenAudio are a couple of frequency domain examples. These are probably your best shot without coding up something whole-ly knew. While I know that you want a bandpass filter, the OpenAudio library includes an frequency-domain example of doing a lowpass filter...


This would be very easily extended to be a bandpass filter instead of a lowpass filter. Specifically, in that example, there's the file AudioEffectLowpassFD_OA_F32.h. In its `update` method, you'll see this block of code:

Code:
  //this is lowpass, so zero the bins above the cutoff
  int NFFT = myFFT.getNFFT();
  int nyquist_bin = NFFT/2 + 1;
  float bin_width_Hz = sample_rate_Hz / ((float)NFFT);
  int cutoff_bin = (int)(lowpass_freq_Hz / bin_width_Hz + 0.5); //the 0.5 is so that it rounds instead of truncates
  if (cutoff_bin < nyquist_bin) {
    for (int i=cutoff_bin; i < nyquist_bin; i++) { //only do positive frequency space...will rebuild the neg freq space later
        //zero out the bins (silence
        complex_2N_buffer[2*i] = 0.0f;  //real
        complex_2N_buffer[2*i+1]= 0.0f; //imaginary
    }
  }

As you can see, this bin of code (1) calculates which FFT bin is at the desired cutoff frequency for the lowpass filter and then (2) loops over all of the bins above the cutoff frequency and sets them to zero amplitude (both the real portion of the frequency-domain signal and the imaginary portion).

You said that you wanted a bandpass filter, not a lowpass filter. So, you will need to expand this code a bit to make it silence bins that are below your bandpass as well as silencing the bins above your bandpass. By simply looping over every frequency bin and comparing its frequency to your low and high cutoff values, your new code might look like this:

Code:
  //this is bandpass, so zero the bins below the BP_low_Hz cutoff and zero the bins above the BP_high_Hz cutoff
  float BP_low_Hz = 500.0;  //set this to whatever you'd like
  float BP_high_Hz = 2000.0;  //set this to whatever you'd like
  int NFFT = myFFT.getNFFT();
  int nyquist_bin = NFFT/2 + 1;
  float bin_width_Hz = sample_rate_Hz / ((float)NFFT);
 float bin_freq_Hz;  //create this variable, which we'll use in a second to hold the frequency that is represented by a given FFT bin
  for (int i = 0; i < nyquist_bin; i++) {  //only do positive frequency space...will rebuild the neg freq space later
      bin_freq_Hz = i * bin_width_Hz;
      if ((bin_freq_Hz < BP_low_Hz) || (bin_freq_Hz > BP_high_Hz)) {  //look for frequencies outside of the desired pass band
          //zero out the bins (silence)
          complex_2N_buffer[2*i] = 0.0f;  //real
          complex_2N_buffer[2*i+1]= 0.0f; //imaginary
      }
  }

As FFTs and FIRs are two sides of the same coin, you should get the kind of filtering performance that you expect. Looking at the main *.ino file, I see that this example is set to do FFTs that are 1024 points long, which should yield a filter performance similar to a 1024-point FIR. [Curiously, when I first wrote the example years ago, I think that it max'd out at Nfft of 256 or 512, not 1024. So, someone has done more work to enable the longer FFT. Cool.] Also, in the main *.ino file, I see that the default sample rate is around 44 kHz. If you want to run at 96 kHz, simply change `sample_rate_Hz` up to 96000 and it should simply work (assuming that your audio chip can run that fast).

If you're against doing your filtering in the frequency domain (ie, via this FFT->IFFT processing) and you absolutely must do your filtering in the time-domain (ie, via FIR processing), your solution will be more involved. It's not super hard, but it will be a bit harder than simply extending this frequency-domain lowpass filter example.

Chip
 
Last edited:
I want to apply a real time FIR band pass filter
Which rather rules out the naive FFT/zeroing/IFFT approach as its not real-time, suggesting you require either a FIR filter (limited by performance as number of taps increases), or a filter done with fast convolution (partitioned convolution for low latency), which can hand 10's of thousands of taps with only moderate latency.
 
>>> Which rather rules out the naive FFT/zeroing/IFFT approach as its not real-time

While FFT-based processing has a different latency than FIR, it's as real-time as an FIR filter.

For a linear-phase FIR filter, the latency is approximately half the FIR length (or the whole FIR length depending on your perspective). Then, on the Teensy, you add in the latency of two audio processing blocks (128 points each) plus the latency of the decimation and reconstruction filters used in the sigma-delta pipeline (~30?).

For an FFT-based system where overlapping FFT blocks are used (such as in the OpenAudio and Tympan libraries), the latency ends up being about half the FFT length (or the whole FFT length depending upon your perspective). Then, you'd add in all the same additional latency factors described above.

So, if one is a strict "real-time" person, no filtering on the Teensy is real time because there is always a delay. Even with no filtering, Teensy will show a delay on the order of 250-300 samples. But, if by "real time" one simply means that the input-output delay is reasonable and stable, both the FIR-based and FFT-based filtering approaches are qualitatively the same.

IMO, the main factor driving whether one uses a time domain (ie, FIR) or frequency domain (ie, FFT) approach is the background of the human user. Many folks schooled in DSP have been trained to think in terms of FIR and their properties. The hardest part of FIR is designing the filter coefficients. Most FIR-schooled folks will already have their favorite routines for designing the filter coefficients. They're ready to go. Fantastic.

But, there's another group of folks for whom it is easier to think in the frequency domain. Unfortunately for these folks, they've been held back by the difficulty of actually implementing an FFT->IFFT based processing stack. Luckily, open source libraries like the OpenAudio and Tympan libraries help people overcome that hurdle because of their built-in examples.

So, now, there are many solution approaches available! Everybody wins!

Chip
 
Last edited:
FIR filters don't have to be linear-phase though - its not clear if there is a requirement for linear-phase, but the phrase real-time suggests its not such a priority? Partitioned convolution can give upto 0 sample latency if done the right way, but its not so simple, and not worth it with ADC and DAC delays already present - but its easy to reduce the latency to a small fraction of the tap count by partitioning to that size.

FFT convolution provides O(log(N)) complexity per sample, straight FIR is O(N), partitioned convolution is intermediate in complexity, the more complex schemes can approach O(log(N)) though, by having a range of partition sizes in a geometrical progression.

Perhaps an IIR filter is actually a better approach though if the filter is minimum-phase in the first place? Yes you have to take care with numeric accuracy issues, but the processing overhead is low.
 
Partitioned convolution can give upto 0 sample latency if done the right way, but its not so simple
I get lost here, can you give a link?
IMHO, any FIR (time-based or spectrum based) or convolution, being partitioned or not, has an information (group) delay (i.e. is complete) that corresponds to half the number of tabs. Not to be confused with phase delays, but I guess, we are all aware of this.
 
Re-reading the original post, can the Teensy Audio Shield do 96 kHz?

The audio codec in the Teensy-based Tympan can do 96 kHz, but I don't know about the SGTL5000 codec in Teensy's own Audio Shield.

Does anyone know?

Chip
 
Re-reading the original post, can the Teensy Audio Shield do 96 kHz?

The audio codec in the Teensy-based Tympan can do 96 kHz, but I don't know about the SGTL5000 codec in Teensy's own Audio Shield.

Does anyone know?

Chip
Yes, it can:
1720281064702.jpeg
 
Hi there!
first of all, thank you all for your answers, and my apologies for not being very responsive. I am not full time dealing with this, and I've been sick last couple of days.
Anyway, I am making progress. Thanks to WMXZ example, I can run the audio shield at 96 kHz and implement a BP filter centered at a frequency higher than the 22058 KHz limit with the standard sample rate.
I am now working on getting sharper band edges. I will try other filter topologies, but, for now, I feel more comfortable with the FIR filter. Basically because it is the one I have been dealing with lately, and the availability of resources to calculate the coefficients. The "real time" requisite means that the input-output delay has to be stable, actually, phase delay is not such an stringent requirement, I believe.
Again, Thank you folks!

Clipboard02.jpg

Clipboard01.jpg
 
Back
Top