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.
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.
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
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.
Also, the twitter might have to be driven with a smaller amplitude as the bass.
The crossover can be implemented by (IIR) biquad filters or by finite impulse response (FIR) filters.
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
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.
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 2and 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.
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).
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).
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
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.
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