Digital Audio Filter

To start with my background is as a software developer, and I have not worked with Arduino before.

The issue at hand is there is a voice intercom system that occasionally has noise in the form of a single frequency tone. The tone is not always the same frequency. So the suggested solution is a tunable notch filter inline with the audio output to a user's headphone.

At this point I am thinking of using either an Arduino MKR Zero or Due as the board. The solution would be to pass the audio signal to an Analog Input, filter it, and put it out the DAC output.

Sample rate would be 8000 Hz. There would be a potentiometer input to a second Audio Input to adjust the filter frequency.

My 2 questions would be

  1. Do the boards I am thinking about have enough computing power for the job ?
  2. Is there an alternate solution method I should be looking at (given my limited audio experience) ?

Thanks for your time
Dale Pennington

I think you will need a much faster processor for this idea to work (both Arduinos have clock frequencies less than 100 MHz). Something like an RPi Zero or Teensy 4.x would be much better choices.

The variable frequency of the interfering tone is a problem. To identify it on the fly requires you to do frequency analysis, then to remove it, reconfigure the notch filter.

Thanks for the feedback. The thought is to allow the user to turn a pot to adjust the notch frequency audibly (no visual aspect to the planned solution).

An analog electronic filter could do that rather easily. These examples are fixed frequency, but illustrate the simplicity of the idea:
https://www.ece.uvic.ca/~bctill/uvatt/opamps/avt08062.pdf

Switched capacitor filters are adjustable on the fly. Example:
https://www.edn.com/build-an-adjustable-high-frequency-notch-filter/

That I may need to pass to our EE for analysis as I am not very knowledgeable there. I have demonstrated a pure digital filter on a Linux workstation, but we want something a bit smaller in the real world.
On the Teensy, I looked at it, and the outputs are just PWM. Are those good enough for audio output (in the 0-4kHz range) ? One of the reasons I looked at the particular Arduinos was because they had higher speed ADC as well as DACs

Dale

It has I2S 16 bit data In/Out

You are correct, the Teensy 4.1 does not have a DAC. But PWM may be fine for 4 kHz audio (the output needs to be low pass filtered).

The Teensy 3.2 has a DAC and a decent audio signal processing library, but ~96 MHz maximum clock speed.

Do take a look at the Pi Zero, which has full blow linux, and high quality audio DAC add ons are available: JustBoom DAC Zero pHAT | 384kHz/32 bit DAC for Raspberry Pi Zero

  1. I've implemented digital filters at that sample rate on less capable processors, so computing power shouldn't be an issue. The core code for an IIR notch filter is only a few lines, but the requirement that it be tunable implies some level of filter design software to get coefficients and that's a non-trivial amount of code.

  2. The best way to deal with unwanted signal components in your data stream is to prevent them from being introduced in the first place. So a far better alternate solution is to figure out how this tone is being introduced and suppress it at the source rather than throwing processing power at it downstream. More details on the "voice intercom system" might help with guidance on what to look for.

This is inside a multi-person vehicle. They have been trying to track down the source with no success so an output filter was suggested as an attempt to fix the issue.

We have the math for calculating the coefficients, the most complicated function is needs is a cosine.

@jim-p From my lookup I2S is a digital standard, this would need some sort of external converter to get back to analog

1 Like

Yes you would need an I2S ADC/DAC but PJRC has an add-on board

I think it should be easier and cheaper to make a notch filter with few opamps.

But how would you tune it on the fly (the tone we want to filter out shifts some according to users)

What is the approximate range of the tone and how quickly does it change? Is this an intermittent problem or is it always there?

Is the audio a strictly analog circuit or is this digitized/packetized?

I would go straight for the Arduino R4 minima, I've been quite impressed with the quality of the ADC and DAC, and it also has a floating point unit that can run IIR filters incredibly quickly. For a 4th order filter the 48MHz M4 processor will have no problems

sounds like you're on top of things here but maybe others will find the below code snippets useful for Butterworth low pass and high pass filters. You could construct the notch by subtracting the bandpass filtered data from the raw data or adapt code from the link to the source in the code snippets.

filter::filter(float Fc,float fs, int ncascs,int type){
		_ncascs  = constrain(ncascs,1,5);
		float K = tan(PI * Fc/fs);
		float norm;
		for(int i=0;i<ncascs;i++){
			_z1[i]=0.0;
			_z2[i]=0.0;
			float Q= 1/(2*cos(PI/(ncascs*4)+i*PI/(ncascs*2)));
			switch (type) {
			 case lowpass:
			    norm = 1 / (1 + K / Q + K * K);
				_b0[i] = K * K * norm;
				_b1[i] = 2 * _b0[i];
				_b2[i] = _b0[i];
				_a1[i] = 2 * (K * K - 1) * norm;
				_a2[i] = (1 - K / Q + K * K) * norm;
				break;
             case highpass:
				norm = 1 / (1 + K / Q + K * K);
				_b0[i] = 1 * norm;
				_b1[i] = -2 * _b0[i];
				_b2[i] = _b0[i];
				_a1[i] = 2 * (K * K - 1) * norm;
				_a2[i] = (1 - K / Q + K * K) * norm;
				break;
			}
			
		}
	}

with the following for rough example for processing

// adapted from https://www.earlevel.com/main/2012/11/26/biquad-c-source-code/
#ifndef FILTER_H
#define FILTER_H
#include <Arduino.h>
enum {
    lowpass = 0,
    highpass,
	bandpass,
};
class filter {
public:
    filter(float Fc,float fs, int ncascs,int type);
	
	template <typename T> 
	T process(T in){
		float fout=0;
		float fprev= (float)in;
		for(int i=0; i<_ncascs;i++){
			fout= fprev * _b0[i] + _z1[i];
			_z1[i] = fprev * _b1[i] + _z2[i] - _a1[i] * fout;
			_z2[i] = fprev * _b2[i] - _a2[i] * fout;
			fprev=fout;
		}
		return (T)fout;
	}
	

private:
	float _b0[5], _b1[5], _b2[5], _a1[5], _a2[5], _z1[5], _z2[5];
	int _ncascs;
}; 
#endif

the code above has served me well in a number of audio situations. You could definitely adapt this to work on controllers without floating point but it requires more effort. hope this helps

@MrMark As far as I know it is intermittent (some days its there, somedays its not). Also I think its a pure analog system that does through a central intercom board. So far I have heard both 750 Hz and 1 kHz tones on some of the recordings we have.

@the2ndtierney Do you have experience using the R4 minima in this type of project ? I am asking because most other folks were saying we needed higher clock speeds. On the math mine looks similar, but it notch specific.

Thanks for all the feedback I have gotten so far from everybody.

Dale Pennington

What do you think you need the higher clock speed for? A higher clock speed doesn't always mean better performance. controllers without floating point units can struggle with some of these calculations, depending on the implementation.

I've been reading to the ADC at about 1MHz, filtering, downsampling to 10KHz, running a PID, and outputting to the DAC at 10KHz. It's not an audio-specific application but has similar specs to what you want. If you do go with the R4 I would try and familiarise yourself with code by @susan-parker which really makes the performance amazing.

One downside of the R4 is that I2S does not seem to be possible... so if you want to go down the I2S route I would reccomend the ESP32, Teensy, Feather M4, which have higher clocks, floating point and I2S ports. The downside of ESP32 is that the ADC and DAC are less than ideal if you wanna go the analogue route.

In this post I describe running a 2nd order IIR on an STM32F103C8 board (72 MHz Cortex-M3 vs the 48 MHz Cortex-M4 of the R4) at 40k samples/second. On this one might expect the R4 to run this code at about 27k samples/second (i.e. 48 MHz/72 MHz * 40 ksps).

Roughly, the 4th order IIR suggested by "the2ndtierney" is about twice the operations so something like 13k samples/second would be expected vs your 8k requirement on the R4.

My ICOM 7100 amateur radio transceiver has both an automatic notch filter and a manual notch filter as you describe. But, from using them I can see you are missing a very important part of your design.
That missing part is the "width" of the notch you are creating. My radio has adjustable width for both filters. If set too narrow, the notch only takes out a bit of the tone. Wider it will eliminate the tone entirely, but that notch also removes part of the audio that is being received.
You need to include a notch width adjustment as well as frequency. Part of the tone can be ignored by the listener if the speech will be distorted by a wide notch.

Hmm not sure I agree @MrMark, here is some benchmark code which shows each 4th order filter takes 1.63 us (>500KHz) on the R4 and the expected 80dB attenuation is observed but Maybe I have misunderstood something.

#include "filter.h"
const uint16_t sampleRate = 3000;
float sig[sampleRate];

void setup() {
  delay(3000);

  //begin serial after clocks set
  Serial.begin(115200);
  Serial.println("hello");

  // signal to be filtered
  for (int i = 1; i < (sampleRate + 1); i++) {
    sig[i - 1] = sin(2.0 * PI * 300 * i / sampleRate) * 32767 ;
  }
   for (int i = 0; i < sampleRate; i++) {
      Serial.println(sig[i]);
  }
  

  filter filt = filter(30, 3000, 2, lowpass);
  uint32_t beg = micros();
  for (int i = 0; i < sampleRate; i++) {
    sig[i] = filt.process(sig[i]);
  }

  Serial.print((micros() - beg)/3000.0);
  Serial.println(" us  used for 4th order filter");

    for (int i = 0; i < sampleRate; i++) {
      Serial.println(sig[i]);
  }
}
  

void loop() {
}

Ultimately, Don't get fooled by the low clock speed of the R4, it's a DSP beast