1
$\begingroup$

I have an audio streaming receiver written in Python running on a raspberry pi. This receiver writes the streamed audio out to an alsa device, which in turn passes it to an amplifier which is driving a DML speaker that I made. DML speakers require rather heavy equalization to make them sound correct - so I want to add in digital signal processing on the rpi.

Using Room EQ Wizard, I have created a correction filter. I am able to see the raw equalizer values (frequency, gain and Q), and also export the filter as an impulse response wav. I have tested this IR wav using shairport-sync (I even wrote the directions) and it works very well.

The question is how to implement the equalization under linux (headless, no GUI) for audio that is coming from a python script.

I think there are three different routes that could work -

  1. using a software based graphic equalizer
  2. applying convolution to the audio signal either under ALSA after the audio is written by python
  3. or directly in python before it is written

There is an alsa equalizer plugin, however this is too limited as there aren't enough bands, the bands are preset frequencies, and the Q is not adjustable.

There is also an experimental alsa convolution plugin, which unfortunately I can't get working.

I have also found this question which lead me to ponder whether or not python could apply the convolution directly. Unfortunately I am not a python (or audio processing) expert, so this would take quite a lot of effort to figure out.

Is anyone aware of an equalizer or alsa plugin that will do what I need?

$\endgroup$
1
1
$\begingroup$

You can simply implement this as an IIR filter directly in Python using https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.sosfilt.html

Each Room EQ wizard section can be turned into a second order filter section using RBJs Audio EQ Cookbook (See for example https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html or https://www.musicdsp.org/en/latest/Filters/197-rbj-audio-eq-cookbook.html). You want the "peaking EQ". Then just cascade all sections into a single SOS.

Note: If your filter parameters look like https://github.com/mikebrady/shairport-sync/wiki/Digital-Signal-Processing-with-Shairport-Sync I advise some caution: You have a lot of VERY high Q features are 6 Hz/12 Hz (very low freqencies). These could be numerically unstable and you really don't need them. I would eliminate all sections below 30 Hz and just make sure you have decent highpass filter somewhere in the signal chain.

$\endgroup$
0
$\begingroup$

I had a look at using both CamillaDSP and doing filtering directly in Python, but the learning curve for both seemed steep.

What I found instead is a LADSPA filter called ACDf, which provides a multiband parametric equalizer that can be used directly with alsa. The steps to getting this to work are quite simple:

  1. Download the ACDf source code
  2. make it to build the ACDf.so binary then move to /usr/lib/ladspa/
  3. Load and configure the ACDf plugin in the alsa ~/.asoundrc configuration file

The configuration options for the ACDf multiband parametric EQ allow you to specify the frequency, the gain and the q factor. Room EQ Wizard displays all three of these values, so it's just a matter of transcribing them from REW into the ~/.asoundrc file.

One equalizer band config for the left channel looks like this:

        0 {
            label ACDf
            policy none
            input.bindings.0 "Input"   #process input channel 0 (left)
            output.bindings.0 "Output" #send to output channel 0
            input { controls [26 1 6.0 53.1 8.530 1 1]}
        }

Note that input/output.bindings.0 is the left input/output. Use 1 for the right channel.

In the controls section, the numbers are as follows:

  • 26 - the parametric equalizer is filter number 26 (there are a bunch of different filters available in ACDf)
  • 1 - not used
  • 6.0 - the db gain
  • 53.1 - the frequency in hz
  • 8.53 - the Q factor
  • 1 - not used
  • 1 - not used

Here is the EQ generated by Room EQ Wizard for my DML speaker: REW Suggested EQ

And now my complete ~/.asoundrc file which has 15 EQ settings per channel:

pcm.!default {
    type plug
    slave.pcm equalizer
}

pcm.equalizer {
    type ladspa
    path "/usr/lib/ladspa"
    channels 2
    slave {
        pcm "plughw:0"
    }

    plugins {
        0 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 6.0 53.1 8.530 1 1]}
        }
        1 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -8.2 59.1 3.793 1 1]}
        }
        2 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -6.0 69.7 9.912 1 1]}
        }
        3 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -3.5 126 18.191 1 1]}
        }
        4 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 6.0 305 5.000 1 1]}
        }
        5 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -5.4 325 5.000 1 1]}
        }
        6 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -3.3 509 5.000 1 1]}
        }
        7 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -4.3 685 5.000 1 1]}
        }
        8 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 6.0 1433 5.000 1 1]}
        }
        9 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -8.2 1501 5.000 1 1]}
        }
        10 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -5.8 1780 5.000 1 1]}
        }
        11 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -5.8 2017 5.000 1 1]}
        }
        12 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -5.4 2231 5.000 1 1]}
        }
        13 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -8.3 3265 5.000 1 1]}
        }
        14 {
            label ACDf
            policy none
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input { controls [26 1 -4.1 4985 5.000 1 1]}
        }
        15 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 6.0 53.1 8.530 1 1]}
        }
        16 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -8.2 59.1 3.793 1 1]}
        }
        17 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -6.0 69.7 9.912 1 1]}
        }
        18 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -3.5 126 18.191 1 1]}
        }
        19 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 6.0 305 5.000 1 1]}
        }
        20 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -5.4 325 5.000 1 1]}
        }
        21 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -3.3 509 5.000 1 1]}
        }
        22 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -4.3 685 5.000 1 1]}
        }
        23 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 6.0 1433 5.000 1 1]}
        }
        24 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -8.2 1501 5.000 1 1]}
        }
        25 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -5.8 1780 5.000 1 1]}
        }
        26 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -5.8 2017 5.000 1 1]}
        }
        27 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -5.4 2231 5.000 1 1]}
        }
        28 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -8.3 3265 5.000 1 1]}
        }
        29 {
            label ACDf
            policy none
            input.bindings.1 "Input"
            output.bindings.1 "Output"
            input { controls [26 1 -4.1 4985 5.000 1 1]}
        }
    }
}

Many thanks to jrubinstein for the help on GitHub.

$\endgroup$

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.