2 ways crossover - fcorthay/RPi-multiroom-audio GitHub Wiki

In the context of multiroom audio, one can have a room with good loudspeakers which are to be driven actively and with an audio path remaining digital to the maximal extent. Indeed, the loudspeaker itself remains an analog device.

Table of Contents

Build

Hardware

The room uses 2 RPi receivers, each with a stereo amplifier hat. Each loudspeaker is driven by one of the RPi boxes. Its bass driver is connected to the RPi receiver's left channel output and the treble by the right channel.

Software

We will use the configuration presented in the DSP installation page. Make sure to have installed CamillaDSP.

If not already done with .bash_aliases, load the environment variables:

source ~/Documents/RPi-multiroom-audio/configuration.bash
env | grep ^CAMILLA | sort

Check the soundcard's controls

amixer -D hw:$AMPLIFIER_SOUNDCARD scontrols

amixer -D hw:$AMPLIFIER_SOUNDCARD get 'Digital'

Install libraries for the signal processing:

sudo apt install -y python3-scipy python3-matplotlib

Crossover parameters

Frequencies

The lowpass and highpass cutoff frequencies have to be determined. This can be done by measuring the responses of an analog crossover built for the specific loudspeaker, if available, or by measuring the audio response of the two drivers.

The bass lowpass filter frequency response below has been made by injecting sinewaves to the passive crossover input and measuring the driver input:

The oscilloscope used here has the features of calculating an FFT and to keep the peak of the result over time.

Before generating the stimuli to the loudspeaker driver, one should set a proper volume. This is done with:

alsamixer -D hw:$AMPLIFIER_SOUNDCARD
# or
amixer -D hw:$AMPLIFIER_SOUNDCARD sset 'Digital' 70%

In order to generate the sinewaves, one can use ffplay:

FREQUENCY=1000
DURATION=1
alias play2amp="SDL_AUDIODRIVER='alsa' AUDIODEV='dmix:$AMPLIFIER_SOUNDCARD' ffplay -autoexit -loglevel quiet"
play2amp -f lavfi "sine=frequency=$FREQUENCY:duration=$DURATION" >/dev/null 2>&1

The script LoudspeakerTest/playFrequencies.bash uses the preceding command to loop over a set of frequencies.

An alternative is to generate white noise:

AMPLITUDE=1
DURATION=10
play2amp -f lavfi "anoisesrc=duration=$DURATION:color=white:amplitude=$AMPLITUDE" >/dev/null 2>&1

A further alternative is to generate a series of cardinal sine (sinc) pulses with the script LoudspeakerTest/buildSinc.py. The sinc function sweeps all the frequencies up to a given one. Yet it is short in time and does not carry a lot of energy : hence the repeated series of pulses.

Amplitudes

Also, the twitter might have to be driven with a smaller amplitude as the bass.

Filter types

The crossover can be implemented by (IIR) biquad filters or by finite impulse response (FIR) filters.

Signal processing

Channel selection

As a first step, we will retrieve the left audio channel of the currently playing audio and output it to both the left and right channels. The newly produced left channel will be lowpass filtered to drive the bass and the right channel highpass filtered for the treble.

Make a copy of the stereo2mono.yaml configuration and edit it:

cp $CAMILLA_CONFIGURATIONS_DIR/stereo2mono.yaml $CAMILLA_CONFIGURATIONS_DIR/stereo2left.yaml
nano $CAMILLA_CONFIGURATIONS_DIR/stereo2left.yaml

Change the mixing to:

mixers:
  stereoToLeft:
    channels:
      in: 2
      out: 2
    mapping:
      - dest: 0
        sources:
          - channel: 0
            gain: 0
            inverted: false
      - dest: 1
        sources:
          - channel: 0
            gain: 0
            inverted: false
pipeline:
  - type: Mixer
    name: stereoToLeft

Update the DSP processing:

ln -sf $CAMILLA_CONFIGURATIONS_DIR/stereo2left.yaml $CAMILLA_CONFIGURATION_DIR/camillaconfig.yaml
sudo service camilladsp restart

IIR crossover

The CamillaDSP step by step configuration tutorial shows how to define a biquad based crossover, yet with a 4 channel output. As the RPi hats mainly only have 2 loudspeaker outputs, we will design a system with 2 channels.

Filter design

We want to design a lowpass and a highpass IIR filter implemented by the series connection of biquads. CamillaDSP allows to define them by the type, the frequency and the quality factor.

The script Crossovers/crossover-iir.py provides the required parameters.

The following picture shows a crossover using Bessel filters:

The second picture shows a crossover using Butterworth filters:

The third picture shows a crossover using Chebyshev filters:

All 3  crossovers use a filter pair with a cutoff frequency of 2 kHz and an order of 8. The blue curves depict the behaviour of the lowpass filters and the green the highpass.

Whilst passive crossovers ususally have an order between 1 and 3, the digital implementations allow to more easily implement higher order filters.

The Bessel filters do not separate the bands as sharply as the others, but they show a very flat group delay in the passband. The Butterworth filters have a group delay spike near the cutoff frequency. The Chebyshev filters additionally have an amplitude ripple in the passband.

Choosing an 8th order Bessel filter with a cutoff frequency of 3.5 kHz, we can run the design script:

$AUDIO_BASE_DIR/Crossovers/crossover-iir.py -v -o 8 -t bessel -c 3500 -f 2
and the output is:
Designing a Bessel crossover
Biquads
  Lowpass
    f = 3830.27, Q = 1.22567
    f = 3418.09, Q = 0.710852
    f = 3206.16, Q = 0.559609
    f = 3112.32, Q = 0.505991
  Highpass
    f = 3198.21, Q = 1.22567
    f = 3583.87, Q = 0.710852
    f = 3935.98, Q = 0.505991
    f = 3820.77, Q = 0.559609
  writing template /mnt/RPi/Crossovers/bessel.yaml
Plotting
  writing /mnt/RPi/Crossovers/bessel.png

The script also generates generates the bessel.yaml configuration template for CamillaDSP.

Configuration

With this, we can make a copy the stereo2left.yaml and edit it:

cp $CAMILLA_CONFIGURATIONS_DIR/stereo2left.yaml $CAMILLA_CONFIGURATIONS_DIR/crossoverBessel.yaml
nano $CAMILLA_CONFIGURATIONS_DIR/crossoverBessel.yaml

Update the file with the help of the previously generated bessel.yaml template.

Check the result for validity:

/opt/camilladsp -c  $CAMILLA_CONFIGURATIONS_DIR/crossoverBessel.yaml

Update the DSP processing:

ln -sf $CAMILLA_CONFIGURATIONS_DIR/crossoverBessel.yaml $CAMILLA_CONFIGURATION_DIR/camillaconfig.yaml
sudo service camilladsp restart

For measurement purpose, one can pause Mopidy and play an audio file to the ALSA loopback device:

$AUDIO_BASE_DIR/LoudspeakerTest/buildSinc.py -v
amixer -D hw:$AMPLIFIER_SOUNDCARD get 'Digital' | grep %

$AUDIO_BASE_DIR/Mopidy/control.bash pause
amixer -D hw:$AMPLIFIER_SOUNDCARD sset 'Digital' 100%
aplay -D plughw:$ALSA_LOOPBACK_CAPTURE_DEVICE,$CAMILLADSP_LOOPBACK_SUBDEVICE=0 $AUDIO_BASE_DIR/LoudspeakerTest/sinc-100000.000.wav

amixer -D hw:$AMPLIFIER_SOUNDCARD sset 'Digital' 70%
$AUDIO_BASE_DIR/Mopidy/control.bash play

The following picture shows measurements on the loudspeaker bass input:

And the following picture the loudspeaker treble input:

This DSP processing uses a little less CPU load as snapserver or snapclient (0.8 % on a RPi 4).

FIR crossover

Finite Impulse Response (FIR) filters allow the realization of a constant delay over all the frequencies (linear phase), at the expense of a much higher order than their IIR counterparts. They base on signal delays and as such are only implemented in the digital world (except for some analog structures which are far from mainstream).

Filter design

The scipy signal processing library provides a good overview of commonly used filters. For FIR filter design, it provides:

  • firwin, a generic method
  • firwin2, to approximate a piecewise linear amplitude response
  • firls which allows to specify bands and their associated gains
  • remez which allows to specify bands and their associated gains using the Remez exchange algorithm
The following picture shows the crossover transfer function for a Nuttall 1000-taps FIR crossover with a transition frequency of 2 kHz:

Create coefficient files:

$AUDIO_BASE_DIR/Crossovers/crossover-fir.py -v -t Nuttall -o 1001 -c 3500

This creates the coefficient files Crossovers/nuttall-o1001-c3500-s48000-w1.txt and Crossovers/nuttall-o1001-c3500-s48000-w2.txt. The way 1 coefficient file is for the lowpass filter to the bass and way 2 for the treble.

Configuration

Move the coefficient files to the CamillaDSP environment:

lowpassFile=`ls -t -A1 $AUDIO_BASE_DIR/Crossovers/ | head -n 3 | tail -n 1`
mv $AUDIO_BASE_DIR/Crossovers/$lowpassFile $CAMILLA_COEFFICIENTS_DIR/
highpassFile=`ls -t -A1 $AUDIO_BASE_DIR/Crossovers/ | head -n 2 | tail -n 1`
mv $AUDIO_BASE_DIR/Crossovers/$highpassFile $CAMILLA_COEFFICIENTS_DIR/

With this, we can make a copy the stereo2left.yaml and edit it:

cp $CAMILLA_CONFIGURATIONS_DIR/stereo2left.yaml $CAMILLA_CONFIGURATIONS_DIR/crossoverNutall.yaml
nano $CAMILLA_CONFIGURATIONS_DIR/crossoverNutall.yaml

Update the file with (adapt the paths):

filters:
  lowpass:
    type: Conv
    parameters:
      type: Raw
      format: TEXT
      filename: ../Coefficients/nuttall-o1001-c3500-s48000-w1.txt
  highpass:
    type: Conv
    parameters:
      type: Raw
      format: TEXT
      filename: ../Coefficients/nuttall-o1001-c3500-s48000-w2.txt

pipeline:
- type: Mixer
  name: stereoToLeft
- type: Filter
  channels:
  - 0
  names:
  - lowpass
- type: Filter
  channels:
  - 1
  names:
  - highpass

Check the result for validity:

/opt/camilladsp -c  $CAMILLA_CONFIGURATIONS_DIR/crossoverNutall.yaml

Update the DSP processing:

ln -sf $CAMILLA_CONFIGURATIONS_DIR/crossoverNutall.yaml $CAMILLA_CONFIGURATION_DIR/camillaconfig.yaml
sudo service camilladsp restart
⚠️ **GitHub.com Fallback** ⚠️