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

If you start hearing digital artifacts such as random clicks, try the option "-t 50000" to enlarge the alasaloop buffer.

I noticed that - I played around with it, and managed to get the buffer size to settle (on this particular machine) at around 25000, but I might need to increase it once plugins start to come into play.

I forgot to reply to your question about the basic crossover requirements, so here's what I'm looking at:

  • Bass crosses over to mid at 100Hz
  • Mid crosses over to HF at 500Hz

There are a couple of different speaker designs that I'm working with, so I imagine I could change the values to suit.
 
  • Bass crosses over to mid at 100Hz
  • Mid crosses over to HF at 500Hz

This should work:


pcm.!default { type plug; slave.pcm Crossover } # the name !default overrides default
ctl.!default { type hw; card 0 } # assuming your device is at address 0
#
pcm.Crossover { type ladspa; slave.pcm Plug_Why;
path "/usr/local/lib/ladspa";
channels 6; plugins {
#
#
# Channel 0 - bass left
# Channel 1 - bass right
# Channel 2 - mid left
# Channel 3 - mid right
# Channel 4 - top left
# Channel 5 - top right
#
#mid left - channel 2
# Type P dB fp qp fz qz
0 { label ACDf; policy none;
input.bindings.0 "Input"; output.bindings.2 "Output";
input { controls [ 21 1 0 500 0.707 0 0 ] } # low pass 2nd order
} # creates channel 2 from 0
1 { label ACDf; policy none;
input.bindings.2 "Input"; output.bindings.2 "Output";
input { controls [ 22 1 0 100 0.707 0 0 ] } # high Pass 2nd order
} # channel 2 to channel 2

# mid right
# Type P dB fp qp fz qz
2 { label ACDf; policy none;
input.bindings.1 "Input"; output.bindings.3 "Output";
input { controls [ 21 1 0 500 0.707 0 0 ] } # low pass 2nd order
} # creates channel 3 from 1
3 { label ACDf; policy none;
input.bindings.3 "Input"; output.bindings.3 "Output";
input { controls [ 22 1 0 100 0.707 0 0 ] } # high Pass 2nd order
} # channel 3 to channel 3

#top left
# Type P dB fp qp fz qz
4 { label ACDf; policy none;
input.bindings.0 "Input"; output.bindings.4 "Output";
input { controls [ 22 1 0 500 0.707 0 0 ] } # high pass 2nd order
}
# top right
# Type P dB fp qp fz qz
5 { label ACDf; policy none;
input.bindings.1 "Input"; output.bindings.5 "Output";
input { controls [ 22 1 0 500 0.707 0 0 ] } # high pass 2nd order
}
}
#
# Bass left
# Type P dB fp qp fz qz
6 { label ACDf; policy none;
input.bindings.0 "Input"; output.bindings.0 "Output";
input { controls [ 21 1 0 100 0.707 0 0 ] } # low pass
}
#
# Bass right
# Type P dB fp qp fz qz
7 { label ACDf; policy none;
input.bindings.1 "Input"; output.bindings.1 "Output";
input { controls [ 21 1 4 100 0 0 0 ] } # low pass
}
} # End of plugins
} # End of crossover
#
# Type LADSPA to type multi does not work, hence this extra one between them
pcm.Plug_Why { type plug; slave { pcm Route_2x3; channels 6 } }
#
# the syntax of type multi seems to be fixed as below
#
pcm.Route_2x3 { type multi
slaves.a.pcm "hw:0,0" # address of your sound card
slaves.a.channels 6
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 mid
bindings.2.channel 1
bindings.3.slave b # right mid
bindings.3.channel 1
bindings.4.slave a # left top
bindings.4.channel 1
bindings.5.slave b # right top
bindings.5.channel 1
}
 
@RAndyB:

Thanks for posting these ALSA examples.

I do have a question. It seems that each channel is getting the input plus one filter (only). What if the user wants to implement multiple LADSPA filters on a channel? For example, if I wanted to use an LR4 crossover and some EQ on a driver, this would require at least three of my LADSPA filters (two Q=0.707 2nd order, plus at least one PEQ). How is that done under ALSA?

Also, if the user would like to implement "global" filters on the input, before the multi-way crossover, how is that handled?
 
It seems that each channel is getting the input plus one filter (only). What if the user wants to implement multiple LADSPA filters on a channel? For example, if I wanted to use an LR4 crossover and some EQ on a driver, this would require at least three of my LADSPA filters (two Q=0.707 2nd order, plus at least one PEQ). How is that done under ALSA?

Also, if the user would like to implement "global" filters on the input, before the multi-way crossover, how is that handled?

Charlie,

if you look at the example in post 17 then you will see that it does crossover and EQ.

I don't know whether it is possible to have two type ladspa pcms to implement global filters, didn't find any need to do that.

Andy
 
Charlie,

if you look at the example in post 17 then you will see that it does crossover and EQ.

I don't know whether it is possible to have two type ladspa pcms to implement global filters, didn't find any need to do that.

Andy

Looking at your ALSA script I only see my LADSPA types 21 or 22 used. These are simply 2nd order high or low pass filters. There is no "EQ" applied from what I can tell.

It would be very restrictive if each channel is limited to only one LADSPA filter. That seems to be how you have constructed the crossover in this case. There is likely a way to do it, but I don't know what that might be, or how best to go about it.

If ALSA could be more flexible and the crossover processing more complex I would love to try it. Honestly, while I have seen examples such a this one, I have not been able to understand the overall capabilities and/or limitations to routing and LADSPA processing when done under ALSA.
 
Last edited:
Charlie,

I was referring to post 17, which includes high and low pass filters, a number of parametric EQ, and low shelf. There is a total of 40 LADSP filters, 20 each side.

Regards,

Andy

Ah, sorry, I looked up and mistook the post #27 for #17. Thanks for pointing that out.

You are using multiple filters on the audio that is going to each output - let's call these "chains" of filter. In each chain, the first filter is used to route the input channel (0 or 1) to one of four output channels. The rest of the filters in the chain then use the same channel as both output and input. I guess ALSA handles concatenating them into a processing chain behind the scenes.
 
Charlie,

that's right. In post 27 (my current asound) channels 2 and 3 are created from channels 0 and 1, using high pass filters for the wide band drivers, then the EQ applied. Channels 0 and 1 remain as full range, so those channels have the low pass filters and EQ applied and sent to the bass drivers. Although I guess you have already worked that out!

One of the most awkward aspects is that the lines all must be numbered sequentially and in the right order; seems a bit pointless, but presumably makes it easier for the software to decipher.

Regards,

Andy
 
CamillaDSP looks like it should be a good solution for PC DSP things.But i am using Jack audio solution. The routing posibilities are limitless. Convolution is done with jconvolver. At some development stage i was running more than 20 convolution processes stereo, so more that 40 at a time with some unusual routing. And this was increased a lot more sometimes. Example in photo is quite normal process of development not very complex. Works perfect and the limit is only PC capabilities. For someone doing something very complex i would take a look at Jack audio. And because it works good for complex i don't see the reason not to use this solution for simple projects. And i do that because i am already familiar with it. GUI or GUI less doesn't matter.

routing.jpeg
 
Member
Joined 2007
Paid Member
I just noticed this thread and would offer this comment. Charlie's ACDf IIR filters are very conservative with CPU resources. I use them to run a 3-way crossover in ALSA on a single-core BBB and have no issues creating 6 channels at up to 192kHz. Whereas, the same CPU running BruteFIR can only manage 2K taps up to 48kHz on a stereo pair. Certainly not feasable for crossover filtering in my system. One advantage of running LADSPA in ALSA is the ability to direct different music/sound sources to different input plugs. They can be uniquely customized for pre-crossover peak or room corrections, or set up to synchronize with another signal - eg. video. The system has served me very well and given much enjoyment for the last 5 years.

Yes, I am interested in FIR crossovers. But that will require significant hardware and software re-building, and the (as I understand it, difficult) issue of adapting the filters to native sample rates would be my strong preference. ...just ran across this - food for thought: GitHub - scripple/alsa_cdsp: ALSA plugin for Camilla DSP

If anybody would like to see my current asound.conf file, with it's various plugs (some of which I never used - just tested) feel free to speak up. I'll post here or put up a fresh copy over on GitHub.

Best,

Frank
 
Last edited:
I just noticed this thread and would offer this comment. Charlie's ACDf IIR filters are very conservative with CPU resources. I use them to run a 3-way crossover in ALSA on a single-core BBB and have no issues creating 6 channels at up to 192kHz. Whereas, the same CPU running BruteFIR can only manage 2K taps up to 48kHz on a stereo pair. Certainly not feasable for crossover filtering in my system. One advantage of running LADSPA in ALSA is the ability to direct different music/sound sources to different input plugs. They can be uniquely customized for pre-crossover peak or room corrections, or set up to synchronize with another signal - eg. video. The system has served me very well and given much enjoyment for the last 5 years.

Yes, I am interested in FIR crossovers. But that will require significant hardware and software re-building, and the (as I understand it, difficult) issue of adapting the filters to native sample rates would be my strong preference. ...just ran across this - food for thought: GitHub - scripple/alsa_cdsp: ALSA plugin for Camilla DSP

If anybody would like to see my current asound.conf file, with it's various plugs (some of which I never used - just tested) feel free to speak up. I'll post here or put up a fresh copy over on GitHub.

Best,

Frank

Thanks for your thoughts Frank! I'd definitely like to see the asound.conf you're using. My own application is within a studio environment, so I'm attempting to keep the latency in the signal chain as low as possible.
 
Member
Joined 2007
Paid Member
Greetings @unaHm,

Here are three things for you - 1) excerpts of my asound.conf with comments; 2) some potentially useful links, because one of the major problems with ALSA is documentation (Right???); 3) a hint regarding latency from my own experience.

1.

Code:
pcm.!default {
     type plug
     slave.pcm filter1
}
ctl.!default {
     type hw
     card 0
}
pcm.TV-in {
     type plug
     slave {
         pcm filter1
     }
}
pcm.TV-inhw {
     type plug
     slave {
         pcm "hw:0,0"
     }
}
pcm.pre-eq4 {
     type ladspa
     slave.pcm filter1
     path "/usr/lib/ladspa"
     plugins
     {

        0 {
               label ACDf
               policy none
               input.bindings.0 "Input"
               output.bindings.0 "Output"
               input { controls [26 1 -5 760 4 1 1] }   # mid bump med.volume left
          }
        1 {
               label ACDf
               policy none
               input.bindings.1 "Input"
               output.bindings.1 "Output"
               input { controls [26 1 -5 760 4 1 1] }   # mid bump med. volume right
          }
     }
}
pcm.filter1 {
     type ladspa
     slave.pcm filter2
     path "/usr/lib/ladspa"
     channels 8
     plugins
     {
          0 {
               label ACDf
               policy none
               input.bindings.0 "Input"
               output.bindings.2 "Output"
               input { controls [21 1 -4 240 0.707] }   # low left LR4 top 1
          }
          1 {
               label ACDf
               policy none
               input.bindings.1 "Input"
               output.bindings.3 "Output"
               input { controls [21 1 -4 240 0.707] }   # low right LR4 top 1
          }
          2 {
               label ACDf
               policy none
               input.bindings.0 "Input"
               output.bindings.4 "Output"
               input { controls [22 1 -9 2700 0.707] }   # high left LR4 bottom 1 -1 phase (2nd#)
          }
          3 {
               label ACDf 
               policy none
               input.bindings.1 "Input"
               output.bindings.5 "Output"
               input { controls [22 1 -9 2700 0.707] }   # high right LR4 bottom 1 -1 phase(2nd#)
          }
          4 {
               id 1098 # label ACDf
               policy none
               input.bindings.0 "Input"
               output.bindings.6 "Output"
               # input { controls [5 1 -9 700 ] }   # mid left LR4 bottom 1 -8,520 was -8 700
          }
          5 {
               id 1098 # label ACDf
               policy none
               input.bindings.1 "Input"
               output.bindings.7 "Output"
               # input { controls [5 1 -9 700 ] }   # mid right LR4 bottom 1 -8,520
          }
          6 {
               label ACDf
               policy none
               input.bindings.6 "Input"
               output.bindings.6 "Output"
               input { controls [21 1 -12 2700 0.707] }   # mid left LR4 top 1 #was-8
          }
          7 {
               label ACDf
               policy none
               input.bindings.7 "Input"
               output.bindings.7 "Output"
               input { controls [21 1 -12 2700 0.707] }   # mid right LR4 top 1 #was -8
          }

     }
}
pcm.filter2 {
     type ladspa
     slave.pcm speaker
     path "/usr/lib/ladspa"
     channels 8
     plugins
     {
          0 {
               label ACDf
               policy none
               input.bindings.2 "Input"
               output.bindings.2 "Output"
               input { controls [21 1 0 240 0.707] }   # low left LR4 top 2
          }
          1 {
               label ACDf
               policy none
               input.bindings.3 "Input"
               output.bindings.3 "Output"
               input { controls [21 1 0 240 0.707] }   # low right LR4 top 2
          }
          2 {
               label ACDf
               policy none
               input.bindings.4 "Input"
               output.bindings.4 "Output"
               input { controls [22 1 0 2700 0.707] }   # high left LR4 bottom 2
          }
          3 {
               label ACDf
               policy none
               input.bindings.5 "Input"
               output.bindings.5 "Output"
               input { controls [22 1 0 2700 0.707] }   # high right LR4 bottom 2
          }
          4 {
               label ACDf
               policy none
               input.bindings.6 "Input"
               output.bindings.6 "Output"
               input { controls [21 1 0 2700 0.707] }   # mid left LR4 top 2
          }
          5 {
               label ACDf
               policy none
               input.bindings.7 "Input"
               output.bindings.7 "Output"
               input { controls [21 1 0 2700 0.707] }   # mid right LR4 top 2
          }
          6 {
               id 1098
               policy none
               input.bindings.6 "Input"
               output.bindings.6 "Output"
              #  input { controls [21 1 0 2700 0.707] }   # mid left LR4 bottom 2
          }
          7 {
               id 1098
               policy none
               input.bindings.7 "Input"
               output.bindings.7 "Output"
              #  input { controls [21 1 0 2700 0.707] }   # mid right LR4 bottom 2
          }

      }
}
pcm.speaker {
    type plug
    slave {
     pcm "t-table"
     channels 8
     rate "unchanged"
    }
}
pcm.t-table {
    type route
    slave {
     pcm "hw:0,0"
     channels 8
    }
    ttable {
      0.7   0
      1.3   0
      2.1   1  #left bass 2.1
      3.5   1  #right bass 3.5
      4.2   1  #left tweeter 4.2
      5.6   1  #right tweeter 5.6
      6.0   1  #left mid 6.0
      7.4   1  #right mid 7.4
    }
}
pcm.plughw.slave.rate = "unchanged";

Comments:
A) This started out as an LR4 3-way, and I have played around with the crossover points vs. REW output. My midrange drivers give extremely high resolution, and I decided their natural rolloff on the low side looked and sounded better than using the filters, so I used placeholder filters (id 1098) where the LR2 filters would have been.
B) I included one 2 channel "pre-crossover EQ", which I could either use or bypass simply by directing program output to a plug (vs. a device or default) Example syntax in player command line startup: 'squeezelite -z -C 1 -o plug:filter4 -a 4096:1024:32:0'.
C) An decent way to A/B test two (or multiple) chains of plugs/filters is to kill the player and quickly restart with output to a different input plug.
D) I always use the 1098 placeholder 'filter' when I have a series of synchronous operations, but one or more channels need no modification.

2. Regarding latency...
A. I have only ever tried adding delays via LADSPA, never really tried tweaking for fastest possible performance within a plug itself. The alsa default buffer and period parameters always 'just worked'. But along the lines of documenting what is possible, here is the best information I have seen:
ALSA
There are references here to the Alsa control language and examples of C code for using it in ways that might be very helpful in a production environment. Perhaps at one time these utility programs were included with the alsa library (like aplay and arecord), but my Debian distro now does not have them. However, you should be able to copy and compile what is in this link and have some great tools. In particular, I'd spend a bit of time to see if this latency measurement code would work:
ALSA project - the C library reference: /test/latency.c
NB! keep volumes very low to avoid killer feedback...

B. Note this example of controlling latency-determining parameters within a plug:
Code:
pcm.card0 {
  type hw
  card 0
}

pcm.!default {
  type plug
  slave.pcm "dmixer"
}


pcm.dmixer {
  type dmix
  ipc_key 2048
  slave {
    pcm "hw:0,0"
    period_time 0
    period_size 2048
    buffer_size 65536
    buffer_time 0
    periods 128
    rate 48000
    channels 2
  }
  bindings {
    0 0
    1 1
  }
}

...and this link:
Low latency howto - AlsaProject

3. My system is set up to pass SPDIF from video equipment through the crossover and out to the 6 channel DAC. I tried various ways to route and manage this data pass-through, and some methods that I expected to work well introduced surprising (unacceptable) latency. Purely by experimenting, I arrived at a very workable pathway and it uses SoX. Here is the command line that I currently use to send hw:1,0 through the crossover and out hw:0,0:
Code:
chrt -f 45 sox --buffer 512 -r 48000 -c 2 -t alsa hw:1,0 -t alsa plug:filter4 &
'chrt -f 45' just bumps up the process priority. Latency using this data route gives really good lip-synch, but I have never tried to measure it.

Finally, did you know that SoX has a convolver? I tried it once instead of the 'plug:filter4' "pre-crossover eq plug" that I otherwise use. I made a FIR filter set in MathCad with about 2k taps and it ran just fine, though pushed the BBB CPU up a bit over 90%. The thing about the SPDIF signal is that a) it's typically jittery and b) how often would you say that a soundtrack is 'audiophile-quality'? :) So I don't bother with the SoX convolver. But compared to the setup of BruteFIR, SoX is a piece of cake! Here's the pass-throgh syntax I used:
Code:
chrt -f 45 sox --buffer 512 -r 48000 -c 2 -t alsa hw:1,0 -t alsa plug:pre-eq5 gain -h fir /usr/filter/fir2peak48mild.txt &

OK, that should do it for now. Good luck with your work, and if anything above leads to breakthroughs, it would be nice to hear. In the absence of any kind of 'ALSA Bible', it takes sharing to succeed.

Frank
 
Thanks very much for the post Frank! I'll have to give SoX a try - I was fiddling around with a relatively old computer here and was able to get down to 32 buffers with jackd...not that it was actually doing anything, so it would probably have fallen over as soon as I'd added plugins to it :)

One audio program I've used in the past (Renoise) uses ALSA natively on the Linux version, and I've been able to get low latencies with that, so it enthuses me with the potential something like SoX could achieve for my purpose (2-in, 6-out, with some crossovers and delays, which is little in comparison).

The Arch Linux documentation is about as close as it gets to a 'bible', but I'll be sure to post anything that's new and exciting ;)
 
I've been noodling around a bit this evening, and have come up with a working .asoundrc:

Code:
pcm.!default { type plug; slave.pcm Crossover } # the name !default overrides default
ctl.!default { type hw; card 1 } # assuming your device is at address 1

pcm.Crossover { type ladspa; slave.pcm Plug_Why;
   path "/usr/lib/ladspa";
   channels 6;
   plugins {

      # Channel 0 - bass left
      # Channel 1 - bass right
      # Channel 2 - mid left
      # Channel 3 - mid right
      # Channel 4 - top left
      # Channel 5 - top right

      # mid left - channel 2
      # Type P dB fp qp fz qz
      0 { label ACDf; policy none;
         input.bindings.0 "Input"; output.bindings.2 "Output";
         input { controls [ 21 1 0 500 0.707 0 0 ] } # low pass 2nd order
      } # creates channel 2 from 0
      1 { label ACDf; policy none;
         input.bindings.2 "Input"; output.bindings.2 "Output";
         input { controls [ 22 1 0 100 0.707 0 0 ] } # high Pass 2nd order
      } # channel 2 to channel 2

      # mid right
      # Type P dB fp qp fz qz
      2 { label ACDf; policy none;
         input.bindings.1 "Input"; output.bindings.3 "Output";
         input { controls [ 21 1 0 500 0.707 0 0 ] } # low pass 2nd order
      } # creates channel 3 from 1
      3 { label ACDf; policy none;
         input.bindings.3 "Input"; output.bindings.3 "Output";
         input { controls [ 22 1 0 100 0.707 0 0 ] } # high Pass 2nd order
      } # channel 3 to channel 3

      #top left
      # Type P dB fp qp fz qz
      4 { label ACDf; policy none;
         input.bindings.0 "Input"; output.bindings.4 "Output";
         input { controls [ 22 1 0 500 0.707 0 0 ] } # high pass 2nd order
      }
      # top right
      # Type P dB fp qp fz qz
      5 { label ACDf; policy none;
         input.bindings.1 "Input"; output.bindings.5 "Output";
         input { controls [ 22 1 0 500 0.707 0 0 ] } # high pass 2nd order
      }

      # bass left
      # Type P dB fp qp fz qz
      6 { label ACDf; policy none;
         input.bindings.0 "Input"; output.bindings.0 "Output";
         input { controls [ 21 1 0 100 0.707 0 0 ] } # low pass
      }

      # bass right
      # Type P dB fp qp fz qz
      7 { label ACDf; policy none;
         input.bindings.1 "Input"; output.bindings.1 "Output";
         input { controls [ 21 1 4 100 0 0 0 ] } # low pass
      }
   }
   # End of plugins
}
# End of crossover

# Type LADSPA to type multi does not work, hence this extra one between them
pcm.Plug_Why { type plug; slave { pcm Route_2x3; channels 6 } }

# the syntax of type multi seems to be fixed as below

pcm.Route_2x3 { type multi ;
   slaves.a.pcm "hw:1,0" # address of your sound card
   slaves.a.channels 6
   bindings.0.slave a # left bass
   bindings.0.channel 0
   bindings.1.slave a # right bass
   bindings.1.channel 1
   bindings.2.slave a # left mid
   bindings.2.channel 2
   bindings.3.slave a # right mid
   bindings.3.channel 3
   bindings.4.slave a # left top
   bindings.4.channel 4
   bindings.5.slave a # right top
   bindings.5.channel 5
}

This is from the first full-length example in this thread, from RAndyB. Using this, I was able to run SoX with a buffer size of 48 - on the hardware I had, a buffer of 32 was too small, and ALSA spewed under/over-runs.

I'm not sure if I have the output mappings right. I was literally testing this through a SSH session, so all I was looking for was visuals on how things were running. The audio could have been complete garbage for all I know :)

Here's the command that I ran to test it, adapted from Francolargo's post above:

Code:
 chrt -f 45 sox --buffer 48 -r 48000 -c 2 -t alsa hw:1,0 -t alsa plug:Plug_Why

Bear in mind that this is running on an old AMD Athlon X2, using the snd_hda_intel driver for the onboard sound. There's a SoundBlaster Audigy2 ZS in there, but I went with what I knew for now :)

The machine seemed to sit at around 2.7% cpu usage (according to htop) while SoX was running, which is pretty impressive given such a small buffer size.

The next thing I'll try is Francolargo's asound configuration. I'm definitely starting to understand how this elusive file works now, but I have a long way to go :)
 
The machine seemed to sit at around 2.7% cpu usage (according to htop) while SoX was running, which is pretty impressive given such a small buffer size.

IIUC your setup does just loopback because the PCM crossover device using LADSPA filters is not used. For plain loopback perhaps you might want to look at alsaloop which is designed just for this particular purpose and cares about latency with proper options.