How do I make a Linux/ALSA loudspeaker crossover DSP?

Hello everyone!

I was wondering if it was possible to take a stereo signal from, say, the line input of the computer, route it through LADSPA plugins that would control the filtering and delay of the signal, and then output it to the physical outputs of the sound device.

I recently accomplished this using JACK in this thread, and while it was quick and easy to do, I've found that asound.conf has always eluded me, and wanted a lower-level challenge :)

I have seen that there are a number of threads here that document how this work is done when chained to a music player's output, but I would like to do the same with the live input, if possible, without the need for an application.

So, to recap, is it possible to:

  • Receive the stereo signal from the line input
  • Split the signal into 3 stereo pairs
  • For the first pair, use a low pass filter for Bass
  • For the second pair, use a bandpass filter for Mid
  • For the third pair, use a high pass filter for Treble
  • Output each pair to a different output (I'm only using one audio device)

Thanks for the help!
 
Last edited:
Member
Joined 2008
Paid Member
I am pretty sure CamillaDSP could do this. The author is really helpful. Also, the PA Crossover Rack can do it. I am eagerly waiting for these two to marry (CamillaDSP engine to PAXOR GUI). I have a Behringer UMC1820 card connected to a HP T510 thin client with Debian. Will most probably switch to Mint soon.
 
I am pretty sure CamillaDSP could do this. The author is really helpful. Also, the PA Crossover Rack can do it. I am eagerly waiting for these two to marry (CamillaDSP engine to PAXOR GUI). I have a Behringer UMC1820 card connected to a HP T510 thin client with Debian. Will most probably switch to Mint soon.

Thanks pelanj! I'll dig into CamillaDSP, but it looks pretty interesting :)

I'd managed to do this with JACK, Jalv and Calf XOver 3-Band, and that works quite well as a headless solution (SSH into a tmux session is all I need), but I was hoping for a native ALSA configuration where nothing is required outside of asound.conf. It might not be possible, but I thought I'd ask. I might be misunderstanding the ALSA API, and that it needs an application to funnel the data through.
 
No need for "fancy" software that needs a GUI. Instead, use ecasound and LADSPA. I have been doing this for years. Some info here:
Implementing Loudspeaker Crossovers using Ecasound and ACDf LADSPA plugins

It's simple if you want to process audio from the soundcard input. If you also want to direct the computer's own audio thru the DSP chain, then you would change the input to be from the ALSA loopback (it's like a pipe) and then you send the computer audio to that. You could create a script to choose which input you are directing to the DSP chain, for example.

Just make sure your soundcard can run in full duplex mode (simultaneous input and output).
 
Just make sure your soundcard can run in full duplex mode (simultaneous input and output).

The CPU-efficient asynchronous resampling which monitors input and output rates continuously as implemented in camillaDSP is a major feature for this use case. Typically the input and output audio channels run asynchronously (sometimes even at different samplerates). E.g. SPDIF input via USB-audio -> I2S output.
 
No need for "fancy" software that needs a GUI. Instead, use ecasound and LADSPA. I have been doing this for years. Some info here:
Implementing Loudspeaker Crossovers using Ecasound and ACDf LADSPA plugins

It's simple if you want to process audio from the soundcard input. If you also want to direct the computer's own audio thru the DSP chain, then you would change the input to be from the ALSA loopback (it's like a pipe) and then you send the computer audio to that. You could create a script to choose which input you are directing to the DSP chain, for example.

Just make sure your soundcard can run in full duplex mode (simultaneous input and output).

Thanks Charlie. My misunderstanding was that ALSA didn't require an application or sound server to work, but that asound.conf would be able to handle the audio streams by itself (e.g. by using a live input and a configuration like this was all that was needed). Definitely wrong :)

I'll give ecasound a go! In all of the years of being a Linux, ALSA and JACK user I can't believe I've not used it! I think JACK solved most of my problems, but I've always been impressed by the incredibly low latencies we can get from ALSA directly.

Thanks again! I'll post my configuration, if it differs from the original art for any reason :)
 
Last edited:
Thanks Charlie. My misunderstanding was that ALSA didn't require an application or sound server to work, but that asound.conf would be able to handle the audio streams by itself (e.g. by using a live input and a configuration like this was all that was needed). Definitely wrong :)

I'll give ecasound a go! In all of the years of being a Linux, ALSA and JACK user I can't believe I've not used it! I think JACK solved most of my problems, but I've always been impressed by the incredibly low latencies we can get from ALSA directly.

Thanks again! I'll post my configuration, if it differs from the original art for any reason :)

There is nothing wrong with the "in-ALSA" implementation of LADSPA plugins, such as the one you linked to. But when the crossover becomes more complicated the ALSA formalism starts to become difficult to work with. I personally find it confusing, but more complicated crossovers have indeed been done that way. The advantage is that ALSA will handle sample rate changes automatically, whereas with ecasound the DSP processing chain runs only at the sample rate that was specified when the ecasound chain was started. In your case when you will be using the analog input (ADC) to your soundcard that is not a problem. When you are playing lots of different computer files that have various rates and bit depths you need to constantly kill and re-run ecasound with the new rate (although this can be automated via ALSA) or just resample everything to a single rate that you decide on a priori (that is what I do using PulseAudio, and that is used as the input to the ecasound chain).

Anyway, let us know how you get on, and your thoughts on the setup.
 
There is nothing wrong with the "in-ALSA" implementation of LADSPA plugins, such as the one you linked to. But when the crossover becomes more complicated the ALSA formalism starts to become difficult to work with. I personally find it confusing, but more complicated crossovers have indeed been done that way. The advantage is that ALSA will handle sample rate changes automatically, whereas with ecasound the DSP processing chain runs only at the sample rate that was specified when the ecasound chain was started. In your case when you will be using the analog input (ADC) to your soundcard that is not a problem. When you are playing lots of different computer files that have various rates and bit depths you need to constantly kill and re-run ecasound with the new rate (although this can be automated via ALSA) or just resample everything to a single rate that you decide on a priori (that is what I do using PulseAudio, and that is used as the input to the ecasound chain).

Anyway, let us know how you get on, and your thoughts on the setup.

Thanks for the thoughts, Charlie! Sample rates that change are definitely one of the bugbears of ALSA, and I think that's one of the reasons I was thinking of using sound sources from outside of the computer. The gear that this machine will be receiving audio signals from is analogue anyway, so the decision had kind of been made for me :)

So yes, I'm really interested in creating a 'complicated crossover' in ALSA and just leaving the machine to it :) If you have any suggestion as to where I should look for this kind of implementation, please let me know - I've been searching around, but I must be searching for the wrong keywords.

Thanks for your help!
 
You can search the DIYaudio forums using terms like LADSPA ALSA crossover. I think the user jrubins has posted some example ALSA files.

Thanks Charlie - found them :)

So the configuration of the crossover subdevices and whatnot make sense, but I get the impression from this that I still need an application to call to ALSA in order to do the routing. I was kind of hoping for setting an ALSA 'state' where the capture device is routed through the plugins and outputs, and so not requiring the need for something like ecasound, jackd or the like to be called.

I suppose the simplest thing would be something like piping arecord to aplay with the appropriate switches for the in and out devices...
 
Thanks Charlie - found them :)
I suppose the simplest thing would be something like piping arecord to aplay with the appropriate switches for the in and out devices...
There is no need for another application. asound.conf is rather fiddly - there are some bits of syntax that must be just so.

Avoid arecord to aplay; alsaloop is the part of alsa that you need - negligible delay, so lip sync. on video is spot on. I have posted an asound.conf, but some time ago, so here it is again. You will need to work through plenty of stuff to understand it fully. I can help some more if you wish.

CharlieLaub's LADSPA plugins are the ones to use - he is very helpful and they just work.

asound.conf:

pcm.!default { type plug; slave.pcm Crossover }
ctl.!default { type hw; card 2 }
#
pcm.Crossover { type ladspa; slave.pcm Plug_Why;
path "/usr/local/lib/ladspa";
channels 4; plugins {
#
# ACDf filter types and their required parameters:
# TYPE DESCRIPTION REQUIRED PARAMETERS
# 0 gain block db, polarity
# 1 1st order LP db, polarity, fp
# 2 1st order HP db, polarity, fp
# 3 1st order AP polarity, fp
# 4 1st low shelf fp, db
# 5 1st high shelf fp, db
# 21 2nd order LP db, polarity, fp, qp,
# 22 2nd order HP db, polarity, fp, qp
# 23 2nd order AP polarity, fp, qp
# 24 2nd order low shelf db, polarity, fp, qp
# 25 2nd order high shelf db, polarity, fp, qp
# 26 parametric EQ db, fp, qp
# 27 2nd order notch db, polarity, fp, qp, fz
# 28 biquadratic filter db, polarity, fp, qp, fz, qz
#
# Channel 0 - bass left
# Channel 1 - bass right
# Channel 2 - top left
# Channel 3 - top right
#
#top left
# Type P dB fp qp fz qz
0 { label ACDf; policy none;
input.bindings.0 "Input"; output.bindings.2 "Output";
input { controls [ 22 1 0 150 0.707 0 0 ] } #HP 2nd order
}
1 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 -8 12300 7 0 0 ] }
}
2 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 5 8520 9 0 0 ] }
}
3 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 -3 4180 8 0 0 ] }
}
4 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 -4 2940 10 0 0 ] }
}
5 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 -3 940 8 0 0 ] }
}
6 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 6 1250 10 0 0 ] }
}
7 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 3 2150 7 0 0 ] }
}
8 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 6 752 8 0 0 ] }
}
9 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 3 5460 6 0 0 ] }
}
10 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 10 261 7 0 0 ] }
}
11 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 -10 93 12 0 0 ] }
}
12 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 13 110 10 0 0 ] }
}
13 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 -3 1450 15 0 0 ] }
}
14 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 3 6230 15 0 0 ] }
}
15 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 26 1 -3 5440 18 0 0 ] } }
#
# top right
# Type P dB fp qp fz qz
16 { label ACDf; policy none;
input.bindings.1 "Input"; output.bindings.3 "Output";
input { controls [ 22 1 0 150 0.707 0 0 ] } #HP 2nd order
}
17 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 -8 12300 7 0 0 ] }
}
18 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 5 8520 9 0 0 ] }
}
19 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 -3 4180 8 0 0 ] }
}
20 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 -4 2940 10 0 0 ] }
}
21 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 -3 940 8 0 0 ] }
}
22 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 6 1250 10 0 0 ] }
}
23 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 3 2150 7 0 0 ] }
}
24 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 6 752 8 0 0 ] }
}
25 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3"Output";
input { controls [ 26 1 3 5460 6 0 0 ] }
}
26 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 10 261 7 0 0 ] }
}
27 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 -10 93 12 0 0 ] }
}
28 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 13 110 10 0 0 ] }
}
29 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 -3 1450 15 0 0 ] }
}
30 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 3 6230 15 0 0 ] }
}
31 { label ACDf; policy none;
input.bindings.3"Input"; output.bindings.3 "Output";
input { controls [ 26 1 -3 5440 18 0 0 ] } }
#
# Bass left
# Type P dB fp qp fz qz
32 { label ACDf; policy none;
input.bindings.0 "Input"; output.bindings.0 "Output";
input { controls [ 1 1 0 200 0 0 0 ] } # low pass
}
33 { label ACDf; policy none;
input.bindings.0 "Input"; output.bindings.0 "Output";
input { controls [ 4 1 15 40 0.707 0 0 ] } # low shelf
}
34 { label ACDf; policy none;
input.bindings.0 "Input"; output.bindings.0 "Output";
input { controls [ 26 1 -20 140 2 0 0 ] }
}
35 { label ACDf; policy none;
input.bindings.0 "Input"; output.bindings.0 "Output";
input { controls [ 26 1 -15 75 7 0 0 ] }
}
#
# Bass right
# Type P dB fp qp fz qz
36 { label ACDf; policy none;
input.bindings.1 "Input"; output.bindings.1 "Output";
input { controls [ 1 1 4 200 0 0 0 ] } # low pass
}
37 { label ACDf; policy none;
input.bindings.1 "Input"; output.bindings.1 "Output";
input { controls [ 4 1 15 40 0.707 0 0 ] } # low shelf
}
38 { label ACDf; policy none;
input.bindings.1 "Input"; output.bindings.1 "Output";
input { controls [ 26 1 -20 140 2 0 0 ] }
}
39 { label ACDf; policy none;
input.bindings.1 "Input"; output.bindings.1 "Output";
input { controls [ 26 1 -15 75 7 0 0 ] }
}
} # End of plugins
} # End of crossover
#
# Channel 0 - bass left
# Channel 1 - bass right
# Channel 2 - top left
# Channel 3 - top right
#
pcm.Plug_Why { type plug; slave { pcm Route_2x2; channels 4 } }

pcm.Route_2x2 { type multi
slaves.a.pcm "hw:0,0" # Sabaj to Left
slaves.a.channels 2
slaves.b.pcm "hw:1,0" #Sabaj to Right
slaves.b.channels 2
bindings.0.slave a # left bass
bindings.0.channel 0
bindings.1.slave b # right bass
bindings.1.channel 0
bindings.2.slave a # left top
bindings.2.channel 1
bindings.3.slave b # right top
bindings.3.channel 1
}
 
Last edited:
unaHm

That last post would have been better with only the crossover sections; the bulk of it is equalisation.

If you post your crossover frequencies then I will put a skeleton asound together, with more explanation for each part.

Regards,

Andy

Thanks for your response Andy, and thanks for sharing your asound.conf! I didn't realise that alsaloop could route a live input to ALSA plugins - most of the documentation I've been able to find addresses processing desktop audio through a configuration.

So going from your reponse, ALSA can work this way:

ADC input -> alsaloop -> alsa virtual devices (LADSPA plugins etc) -> DAC output

Is that correct?