Recently, I have been studying IIR and FIR filters, and trying to create a parametric equalizer using a microcontroller. More specifically, I use the ADC on it to sample the audio at about 44 kHz, and in between grabbing samples, I use the time to process the signal and pass it out on the DAC.

What is a bit confusing to me though is that this process seems to be pretty intense, and often takes most of the time in between samples to do all the processing (with 8 2-pole IIR bandpass filters).

Programs like Spotify, or devices like most Galaxy phones have similar equalizers but they are running on non-dedicated hardware, and I am really confused how they are able to pull this off? Does anybody know if they are basically doing the same thing just faster, or is there some secret sauce to this that I am missing?

I have never tested either of the previously mentioned equalizers in those platforms, so maybe they are just really crappy, but at least just by listening to them, it doesn’t sound horrible.

  • $\begingroup$ Depends a lot on how you implement the filters/processing. I've seen implementations where coefficients for each filter are calculated in real time for each sample before processing the sample which is a real bad decision ... the right way is to calculate coefficients (outside the processing block) for only one filter at a time when any of it's parameters is changed ((re-)calculated coefficients are stored in memory where from processing block uses them). So, without knowing your implementation it's hard to say if that is your bottleneck. $\endgroup$ – Juha P Jul 23 at 6:21
  • 1
    $\begingroup$ You have a microcontroller. They have an expensive several-GHz multicore CPU. $\endgroup$ – user253751 Jul 23 at 14:33
  • $\begingroup$ I actually happen to know that spotify defers this to the audio processing stack on iOS/android, which may be hardware backed $\endgroup$ – Steve Cox Jul 23 at 18:29
  • $\begingroup$ @SteveCox Do you know where I could learn more about this? Like about how that stack structure / function works on those platforms? $\endgroup$ – Kevin Sullivan Jul 23 at 21:47
  • $\begingroup$ @SteveCox Actually nevermind, it seems like Apples developer page has a decent amount of information to keep me busy: $\endgroup$ – Kevin Sullivan Jul 23 at 21:53

This depends a lot on how you implement it.

  1. A single biquad takes about 10 arithmetic operations. (To be precise a Transposed Form II takes 4-5 multiplies and 3 adds, depending on how the gain management is done).
  2. Arithmetic operation translates into clock cycles of your processor. That depends a lot on the efficiency of your instruction set and how good yo are at writing code. On a good DSP you can do a single biquad in 4 clock cycles (ca. 2 ops per cycle) and a general purpose ARM with a decent ALU this may by 15-20 cycles. On a crappy micro controller with no native hardware multiplier this can take up to 50-100 cycles.
  3. Frame based vs sample based processing. Reading data from an ADC, moving it into the processor, and the moving it to the DAC creates a lot of overhead. That's why most implementations will process a frame of data and not one sample at a time. That "amortizes" the overhead across the entire frame. A good processor will also allow to do this with HW support, i.e. it will use DMA channels to move the data from/to DAC/ADC to internal memory without the CPU having to do anything.
  4. The chips inside modern smart phones (even the cheap ones) are quite powerful: they are typically quad core ARMs with some HW accelerators (Neon, SIMD) for media processing. They all clock in the multiple GHz range and have good HW support for peripherals and graphic accelerators. My trusty old Samsung S8 has a 8 core 2 GHz Snapdragon: https://www.zdnet.com/article/qualcomm-snapdragon-835-what-does-the-kryo-280-adreno-540-spectra-180-x16-and-hexagon-682-mean-for/#:~:text=The%20Snapdragon%20835%20is%20comprised,result%20in%20optimized%20power%20savings.

Let's a run a simple example: A well coded biquad on a decent ARM core should take about 15 cycles. For 8 biquads running at 44.1 kHz on a stereo signal (two channels) that would be just about 10M cycles/second. That's about 1% of a single core at 1 GHz or 0.25% of the entire chip. On my Snapdragon 835 it would be less than 0.03%

| improve this answer | |
  • $\begingroup$ This really puts it in perspective, thank you! I never knew the ones in smart phones could run that fast. Side-note, do you know if they actually have to use an ADC? I started thinking about it, and if the phone has the audio file on it, couldn’t it be processed straight from memory without actually having to digitize it? A bit of a tangent, but just figured I would check if you knew? $\endgroup$ – Kevin Sullivan Jul 23 at 12:08
  • $\begingroup$ @KevinSullivan Yes. The typical process of playing an audio file involves reading the data from storage e.g. an SD card, decoding the file format to the audio streams and then postprocessing this audio stream using digital equalizers, and all sorts of filters before sending them out to the DAC which generates the analog signal. Many filters aren't implemented using biquad structures but instead the signal is fourier transformed, multiplied with the transfer function and transformed back to time domain. Modern processors provide special command sets to execute these calculations efficiently. $\endgroup$ – GNA Jul 23 at 18:36
  • $\begingroup$ Most often these days the file will come over the network. No need for an ADC but you still need to feed a DAC or Bluetooth transmitter at a fixed clockrate. Biquads are still quite popular in audio processing. Getting decent resolution at bass frequencies requires large FFT sizes which eats into memory budgets and adds a lot of latency. Biquads are much more efficient by all standard metrics $\endgroup$ – Hilmar Jul 23 at 20:20

I don’t think you’re alone, but essentially this is simply a problem of optimization. Let’s say you have a processor with a 88MHz clock. That’s 2k clocks per sample at 44kHz. If we take the term ‘most’ to mean 50%, of the clocks, then that leaves 1k clocks per sample for filtering. Running 8 filters leaves 125 clocks per filter. That’s a decent amount of time, but data needs to get moved around, math needs to get done. So now we need to figure out how to minimize the the number of clocks per filter.

For one, you can process multiple samples at a time. This will reduce the amount of time spent moving data around the memory to the registers. Things like loading coefficients can be drastically reduced this way, and there are likely gains to made with the input and output streams as well.

For two, you can leverage the capabilities of the processor. If it doesn’t have a floating-point unit, use fixed point math. If it has multiply-accumulate instructions, make sure they’re used. Just basically make sure your assembly output doesn’t have a bunch of crazy stuff going on.

Lastly, you can use alternative filter topologies. Direct/transposed direct forms I or II. Maybe look at different structures like lattice, parallel, state-variable. A lot of people more clever than us have spent a lot of time on this stuff, and there’s some interesting literature on the subject. However, I’d generally recommend starting with the first two ideas. Different topologies can have limitations that are tricky to understand.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.