install DSP - fcorthay/RPi-multiroom-audio GitHub Wiki
A Digital Signal Processor (DSP) allows to shape the audio sent to the loudspeakers. This includes, amongst others, to:
- adapt the frequency response of the the loudspeaker boxes
- split the audio signal into a lower frequency range for the bass driver and a higher frequency range for the tweeter
Different reasons argue for applying digital signal processing right before the amplifier soundcard.
In order to test the audio of a loudspeaker (together with its amplifier), one can inject an audio signal to it and record its output with the help of a microphone. Possible test signals are:
- a signal containing all audio frequencies such as
- a white noise
- a cardinal sine or sinc function
- a signal sweeping trough a given set of frequencies
sudo apt install -y python3-scipy sudo apt install -y python3-matplotlib
With that, the following script builds a wav file containing the notes within a selection of octaves.
source ~/.bash_aliases $AUDIO_BASE_DIR/LoudspeakerTest/buildNoteSet.py -s 1 -e 8 -d 0.5 -i 0.1 -w -v
If mopidy is playing to the loopback device, the generated audio file can be played over it:
$AUDIO_BASE_DIR/Mopidy/control.bash pause aplay -D dmix:Loopback,0,0 ~/Documents/RPi-multiroom-audio/LoudspeakerTest/octave-1-8.wav $AUDIO_BASE_DIR/Mopidy/control.bash play
Before playing the generated audio file, one can start a recording:
arecord -l microphone='Quadcast' arecord -D plughw:$microphone --format S16_LE --duration 80 record.wav
In the repository, the scripts:
-
LoudspeakerTest/analyseOctave.bashcreates a sound file with the 12+1 (or 7+1) notes of an octave, plays it on the sound card and records the loudspeaker's output -
LoudspeakerTest/analyseAll.bashdoes this for octaves 1 to 8
The following picture shows the sound file for octave 3:
And the next picture shows the recorded wave:
Obviously, there is a transient and then a steady-state response for each note change. Yet, the most impressive fact is to what extent the amplitudes of the different notes (here in linear scale) can differ one from another.
This is where the signal processing comes in to equalize the audio response of loudspeakers in the room where they sit.
A 2-way loudspeaker is a cabinet enclosing 2 drivers:
- the bass driver reproduces the lower frequency sounds
- the treble driver (tweeter) reproduces the higher frequencies
In a passive system, the audio signal from the amplifier is split into its low and high frequency parts with the help of an audio crossover to separately feed the bass and the treble drivers. The audio crossover is typically a passive circuit built out of resistors, capacitors and inductors. This type of circuit inserts a phase distortion and has to be adapted to the impedances of the amplifier and of the loudspeaker drivers.
An active system has filters which separate low and high frequency parts of the audio at signal level. Both filter outputs are separately amplified and fed to the loudspeaker drivers. Here the filters can be analog or digital, yet only digital filters allow to keep a linear phase in the signal path. An active 2-way loudspeaker system requires 2 amplifiers and a stereo system 4 (2 stereo) amplifiers.
The peaks in the following pictures follow the transfer functions of a passive crossover for an example loudspeaker:
The lowpass transfer function has a cutoff frequency of about 3 kHz. The highpass transfer function has a cutoff frequency of about 4 kHz. Between these two frequencies, both the bass and the treble drivers participate to generate the sound.
For 2-ways loudspeakers, a fully digital audio path can be achieved using 2 RPi's, each with a digital stereo amplifier hat or board. For example, the RPi's could receive the same audio stream from a single audio streamer with their multiroom audio receiver. One of them would select the left audio track and split it into the low and high frequency parts with the help of DSP, and the other one would do the same on the right track. The audio system would use one enclosure per loudspeaker, each with an Ethernet input and a 4 wire loudspeaker output. A 2-ways crossover design example is provided in the wiki.
With the signal processing at the end of the chain, the audio path will be the following:
Mopidy reads music from the disk and sends it to the loopback device. Snapserver reads music from the loopback device and sends it to the Ethernet port. Snapclient reads music from the the Ethernet port and sends it to another subdevice of the loopback device. CamillaDSP reads music from the loopback device, processes it and sends it to the soundcard.
Here again, snapclient can receive music from another server. CamillaDSP is the last link in the chain and provides signal processing specific to the local amplifier/loudspeaker(s) matching. A possible configuration would be to have 2 RPi receivers, one for the left and one for the right channel, each splitting the audio for the bass and the treble of 2 loudspeakers.
The present time path can be checked with:
$AUDIO_BASE_DIR/Management/showInstallation.bash
In a first step, we will replace the DSP by a simple forwarding by alsaloop.
This is done in 2 steps:
1. Reroute the audio stream which was going to the soundcard and bring it to the loopback device. In the case of snapclient:
sudo sed -i "s/$AMPLIFIER_SOUNDCARD/Loopback/" /etc/default/snapclient sudo service snapclient restart
- At this point, you should have no more sound.
loopback audio to the soundcard:
alsaloop -C hw:$ALSA_LOOPBACK_PLAYBACK_DEVICE,$CAMILLADSP_LOOPBACK_SUBDEVICE -P hw:$AMPLIFIER_SOUNDCARD 2>/dev/null &
In the preceeding command, C defines the capture and P the playback devices.
The CamillaDSP configuration files are in YAML format.
In order for the local scripts to read them, install yq:
sudo apt install -y yq
In order to install the DSP system, one might want to grant sudo privileges to the user sudo.
sudo usermod -aG sudo amp su amp
The CamillaDSP web page explains how to install the software. Here a specific guide for installing it on a RPi.
The CamillaDSP configuration directories are specified in $AUDIO_BASE_DIR/configuration.bash.
Look for the current version of the binary and adapt :
uname -m VERSION='v4.1.3' BINARY='camilladsp-linux-aarch64.tar.gz' mkdir -p ~/Downloads rm -f ~/Downloads/camilla* wget https://github.com/HEnquist/camilladsp/releases/download/$VERSION/$BINARY -P ~/Downloads/ sudo tar -xvf ~/Downloads/$BINARY -C /opt/ /opt/camilladsp --help
The digital signal processing is executed on the base of a configuration file.
Sample configuration files are found in the config page of CamillaDSP. Download the configuration files to the CamillaDSP directory :
mkdir -p ~/Downloads mkdir -p $CAMILLA_CONFIGURATION_DIR CONFIGURATION='camilladsp-config.zip' wget -O ~/Downloads/$CONFIGURATION https://github.com/HEnquist/camilladsp-config/archive/refs/heads/master.zip unzip ~/Downloads/$CONFIGURATION -d $CAMILLA_DIR mv $CAMILLA_DIR/camilladsp-config-master/* $CAMILLA_CONFIGURATION_DIR rmdir $CAMILLA_DIR/camilladsp-config-master
Have a look at how to build a config file step by step.
Get the amplifier card's capacities:
pkill alsaloop AMPLIFIER_RATE=`aplay -v -D dmix:$AMPLIFIER_SOUNDCARD --dump-hw-params /dev/zero 2>&1| grep ^RATE | sed 's/.*:\s*//'` echo echo "sampling rate : $AMPLIFIER_RATE" AMPLIFIER_FORMAT=`aplay -v -D dmix:$AMPLIFIER_SOUNDCARD --dump-hw-params /dev/zero 2>&1| grep ^FORMAT | sed 's/.*:\s*//'` echo "samples format: $AMPLIFIER_FORMAT" alsaloop -C hw:$ALSA_LOOPBACK_PLAYBACK_DEVICE,$CAMILLADSP_LOOPBACK_SUBDEVICE -P hw:$AMPLIFIER_SOUNDCARD 2>/dev/null &
Edit a stereo to mono configuration file.
mkdir -p $CAMILLA_CONFIGURATIONS_DIR CAMILLA_CONFIG_FILE="$CAMILLA_CONFIGURATIONS_DIR/stereo2mono.yaml"
Define the devices:
cat << EOF > $CAMILLA_CONFIG_FILE
---
devices:
samplerate: $AMPLIFIER_RATE
enable_rate_adjust: true
resampler:
type: AsyncPoly
interpolation: Cubic
capture_samplerate: $AUDIO_RATE
chunksize: 2048
silence_threshold: -60
silence_timeout: 3.0
capture:
type: Alsa
channels: 2
device: "hw:Loopback,1,$CAMILLADSP_LOOPBACK_SUBDEVICE"
format: $AMPLIFIER_FORMAT
playback:
type: Alsa
channels: 2
device: "dmix:$AMPLIFIER_SOUNDCARD"
format: $AMPLIFIER_FORMAT
EOF
Define the signal processing elements:
cat << EOF >> $CAMILLA_CONFIG_FILE
mixers:
stereoToMono:
channels:
in: 2
out: 2
mapping:
- dest: 0
sources:
- channel: 0
gain: -6
inverted: false
- channel: 1
gain: -6
inverted: false
- dest: 1
sources:
- channel: 0
gain: -6
inverted: false
- channel: 1
gain: -6
inverted: false
EOF
Define the signal processing path:
cat << EOF >> $CAMILLA_CONFIG_FILE
pipeline:
- type: Mixer
name: stereoToMono
EOF
Check the validity of the configuration file:
/opt/camilladsp -c $CAMILLA_CONFIG_FILE
Check which service is driving the soundcard:
$AUDIO_BASE_DIR/Management/showInstallation.bash
Free the path for CamillaDSP and test it:
sudo pkill alsaloop /opt/camilladsp $CAMILLA_CONFIG_FILE &
With this, one should hear the mono sound on the right-side loudspeaker and only with a faint volume on the left.
Make a copy of stereo2mono.yaml and edit it to create a passthrough:
cp $CAMILLA_CONFIGURATIONS_DIR/stereo2mono.yaml $CAMILLA_CONFIGURATIONS_DIR/passthrough.yaml sed -i 's/stereoToMono/passthrough/g' $CAMILLA_CONFIGURATIONS_DIR/passthrough.yaml nano $CAMILLA_CONFIGURATIONS_DIR/passthrough.yamlChange
dest: 0 to have only channel: 0 with a gain of 0
and dest: 1 to have only channel: 1 with a gain of 0.
The CamillaDSP filter list is abundant:
- gain
- volume
- loudness
- delay
- Finite Impulse Response (FIR) filter
- Infinite Impulse Response (IIR) filter
- dither
- limiter
- difference equation
In order to set up CamillaDSP as a service one can base on the provided service configuration file.
The directory $CAMILLA_CONFIGURATION_DIR contains a link, camillaconfig.yaml,
which points to one of the configuration files in the subdirectory $CAMILLA_CONFIGURATIONS_DIR
where we already have the newly created stereo2mono.yaml and passthrough.yaml.
ls -l $CAMILLA_CONFIGURATION_DIR echo ls -l $CAMILLA_CONFIGURATIONS_DIR
Link the configuration to the passthrough:
source $AUDIO_BASE_DIR/configuration.bash ln -fs $CAMILLA_CONFIG_FILE $CAMILLA_CONFIGURATION_FILE
The provided service file has to be adapted to the present configuration:
# backup
cp $CAMILLA_CONFIGURATION_DIR/camilladsp.service $CAMILLA_CONFIGURATION_DIR/camilladsp.service.original
# ExecStart
EXEC="ExecStart=/opt/camilladsp -p $CAMILLA_CONTROL_PORT -s $CAMILLA_GUI_STATE_FILE $CAMILLA_CONFIGURATION_DIR/camillaconfig.yaml"
EXEC=${EXEC////\\/}
sed -i "s/ExecStart=.*/$EXEC/" $CAMILLA_CONFIGURATION_DIR/camilladsp.service
# User
sed -i "s/User=.*/User=$(whoami)/" $CAMILLA_CONFIGURATION_DIR/camilladsp.service
# Group
sed -i "s/Group=.*/Group=users/" $CAMILLA_CONFIGURATION_DIR/camilladsp.service
# WantedBy
sed -i "s/WantedBy=graphical.target/WantedBy=multi-user.target/" $CAMILLA_CONFIGURATION_DIR/camilladsp.service
# copy to system
cat $CAMILLA_CONFIGURATION_DIR/camilladsp.service
sudo cp $CAMILLA_CONFIGURATION_DIR/camilladsp.service /lib/systemd/system
Test the service:
sudo pkill camilladsp sudo systemctl daemon-reload sudo service camilladsp start sudo service camilladsp status | cat
Once it works, enable the service:
sudo systemctl enable camilladsp
Show the audio path:
$AUDIO_BASE_DIR/Management/showInstallation.bash
A Web-based backend allows to graphically edit CamillaDSP's configuration.
The CamillaDSP configuration directories are specified in $AUDIO_BASE_DIR/configuration.bash.
Look for the current version of the binary and adapt :
VERSION='v4.1.0' BINARY='camillagui.zip' mkdir -p ~/Downloads mkdir -p $CAMILLA_BACKEND_DIR wget https://github.com/HEnquist/camillagui-backend/releases/download/$VERSION/$BINARY -P ~/Downloads/ unzip ~/Downloads/$BINARY -d $CAMILLA_BACKEND_DIR
Install the python libraries as found in the requirements.txt :
sudo apt install -y python3-aiohttp sudo apt install -y python3-jsonschema sudo pip install --break-system-packages git+https://github.com/HEnquist/pycamilladsp.git sudo pip install --break-system-packages camilladsp-plot[plot]@git+https://github.com/HEnquist/pycamilladsp-plot.git
Make a backup of the backend configuration file and adapt the original:
mkdir -p $CAMILLA_COEFFICIENTS_DIR
mkdir -p $CAMILLA_CONFIGURATIONS_DIR
CAMILLA_GUI_CONFIGURATION_FILE="$CAMILLA_BACKEND_DIR/config/camillagui.yml"
cp $CAMILLA_GUI_CONFIGURATION_FILE $CAMILLA_GUI_CONFIGURATION_FILE.original
# camilla host
sed -i "s/camilla_host:\s.*/camilla_host: \"localhost\"/" $CAMILLA_GUI_CONFIGURATION_FILE
# port
sed -i "s/camilla_port:\s.*/camilla_port: $CAMILLA_CONTROL_PORT/" $CAMILLA_GUI_CONFIGURATION_FILE
# bind address
hostname='0.0.0.0'
hostname=`hostname`.local
sed -i "s/bind_address:\s.*/bind_address: \"$hostname\"/" $CAMILLA_GUI_CONFIGURATION_FILE
# port
sed -i "s/^port:\s.*/port: $CAMILLA_GUI_PORT/" $CAMILLA_GUI_CONFIGURATION_FILE
# generic path
CAMILLA_BACKEND_DIR_ESCAPED=${CAMILLA_BACKEND_DIR////\\/}
sed -i "s/~\/camilladsp/$CAMILLA_BACKEND_DIR_ESCAPED/" $CAMILLA_GUI_CONFIGURATION_FILE
# config_dir
CAMILLA_CONFIGURATIONS_DIR_ESCAPED=${CAMILLA_CONFIGURATIONS_DIR////\\/}
sed -i "s/config_dir:\s.*/config_dir: \"$CAMILLA_CONFIGURATIONS_DIR_ESCAPED\"/" $CAMILLA_GUI_CONFIGURATION_FILE
# coeff_dir
CAMILLA_COEFFICIENTS_DIR_ESCAPED=${CAMILLA_COEFFICIENTS_DIR////\\/}
sed -i "s/coeff_dir:\s.*/coeff_dir: \"$CAMILLA_COEFFICIENTS_DIR_ESCAPED\"/" $CAMILLA_GUI_CONFIGURATION_FILE
# default_config
CAMILLA_CONFIGURATION_DIR_ESCAPED=${CAMILLA_CONFIGURATION_DIR////\\/}
sed -i "s/default_config:\s.*/default_config: \"$CAMILLA_CONFIGURATION_DIR_ESCAPED\/camillaconfig.yaml\"/" $CAMILLA_GUI_CONFIGURATION_FILE
# result
cat $CAMILLA_GUI_CONFIGURATION_FILE
Start the graphical user interface:
python3 $CAMILLA_BACKEND_DIR/main.py &
Wait until the console displays a message similar to ======== Running on http://audioAmp.local:5006 ========.
Open a browser and navigate to the provided link. With this, the configurations can be graphically edited.
Here too, one can base on the provided service configuration file. Make a backup of the backend configuration file and adapt the original:
CAMILLA_GUI_SERVICE_FILE="$CAMILLA_CONFIGURATION_DIR/camillagui.service"
cp $CAMILLA_GUI_SERVICE_FILE $CAMILLA_GUI_SERVICE_FILE.original
# after
sed -i "s/^After=.*/After=camilladsp.service/" $CAMILLA_GUI_SERVICE_FILE
# user
sed -i "s/^User=.*/User=$(whoami)/" $CAMILLA_GUI_SERVICE_FILE
# path to executable
CAMILLA_BACKEND_DIR_ESCAPED=${CAMILLA_BACKEND_DIR////\\/}
sed -i "s/\/home\/pi\/camillagui-backend/$CAMILLA_BACKEND_DIR_ESCAPED/" $CAMILLA_GUI_SERVICE_FILE
# result
cat $CAMILLA_GUI_SERVICE_FILE
Copy the service file to the system:
sudo cp $CAMILLA_GUI_SERVICE_FILE /lib/systemd/system/
Test the service:
sudo pkill -f CamillaDSP/Backend/main.py sudo systemctl daemon-reload sudo service camillagui start sudo service camillagui status
Once the backend GUI web page works, enable the service:
sudo systemctl enable camillagui
I had the problem that camiullagui had started before camiulladsp was fully ready.
To cope for this, I have added in the service description file
- within
[Unit]:Requires=camilladsp.service
- within
[Service]:ExecStartPre=/bin/sleep 2
The configuration can be changed online via the Camilla GUI web interface.
Selecting a different configuration is done by:
- selecting the
Filestab - clicking on the load arrow of one of the configurations
- clicking on the
Apply to DSPbutton near to the bottom of the left window pane.
The configuration can also be updated by modifying the configuration file and restarting camilladsp:
CONFIGURATION=passthrough ln -fs $CAMILLA_CONFIGURATIONS_DIR/$CONFIGURATION.yaml $CAMILLA_CONFIGURATION_FILE ll $CAMILLA_CONFIGURATION_DIR sudo service camilladsp restart
The pyCamillaDSP companion Python library for CamillaDSP handles the communication over websocket with the CamillaDSP process. The library has been previously installed under the user interface installation section.