CamillaDSP - Cross-platform IIR and FIR engine for crossovers, room correction etc.

I spent a bit of time tweaking my room EQ filters on the weekend. Recreating a new set of filters for 5 different sample rates (ie : 10 filters) in rephase is quite laborious and error prone. This is in no way a criticism of rephase! Would it be useful if CamillaDSP could directly import REW filter settings which are in XML format? Rephase can import this format. Additionally CamillaDSP could then create filters on the fly for whatever sample rate was incoming?
This is probably best handled by a small script that translates the xml into something CamillaDSP can use. Would it be ok to simply use IIR filters? Then it becomes very easy. The filter definitions are the same so it's basically just a translation from xml to yaml.

Or do you do something with the phase, that would require generating an impulse response and use FIR?
 
Ok thanks. I have been using FIR filters generated by rephase mainly because most guides I have read seem to suggest they are better for room EQ? I have been meaning to look into phase and impulse response aspect of my setup to see if it is less than optimal in that regard but haven't gotten that far yet. I probably need to do more reading?
 
FIR's are not necessarily better but they can do things that IIR filters cannot and they have one other benefit that they are exact. Transferring IIR filter settings between devices gets you into the quagmire of Q definitions. Rephase exports from REW use constant Q.

Room EQ can only be successful below the Schroder frequency but speaker correction based on shorter windows can be run full range.

This thread is a good place to start as there is a great deal of misunderstanding about this aspect of processing

A convolution based alternative to electrical loudspeaker correction networks
 
So it really depends on what you do with the file after importing it into rePhase. If you just create an "IIR-like" impulse response, then a simple translation rePhase/REW XML -> CamillaDSP yaml is enough. But if you want to use the phase linearization features of rePhase, then the translation script would have to duplicate a lot of the functionality of Rephase. Not so simple any more :) Especially since rePhase isn't open source even though it's free (why is this so often the case with software for windows??).



Is it possible to call Rephase from a script, or is it usable from the gui only?
 
Anybody over here using DRC filters for room correction (and C-DSP)?

It seems there've been some improvements done around 2018.

I worked with it some years ago. And just found a script I've written that's been
doing the measurements and generating the filters fully automated.

If anybody could confirm that using DRC still very much makes sense
I'd hit that direction for the time being.

You can have it all running on a headless PI and by pushing a single button ( years ago I wrote a script running the whole process ) you'd be set.
 
@Henrik.

I compiled ( just stdin/out for a PI4 and fftw) CDSP yesterday and all went fine.
I am now in the phase of getting C-DSP configured as a 1/1 drop in for brutefir. I'd like to switch them easily for comparison.

I am having a question related to chunksize.

In brutefir I configured filter_length as no of taps either 65536 or 8192,8. How would that look like in C-DSP?
 
@Henrik.

I compiled ( just stdin/out for a PI4 and fftw) CDSP yesterday and all went fine.
I am now in the phase of getting C-DSP configured as a 1/1 drop in for brutefir. I'd like to switch them easily for comparison.

I am having a question related to chunksize.

In brutefir I configured filter_length as no of taps either 65536 or 8192,8. How would that look like in C-DSP?
You don't need to specify filter length, it's automatic. To get the equivalent of the brutefir 8192,8 and assuming your filter is 65536 elements long, just put the chunksize to 8192. This will enable segmented convolution with 8 segments.

A chunksize of 65536 will disable segmented mode, same as the 65536 setting in brutefir.
 
I spent a bit of time tweaking my room EQ filters on the weekend. Recreating a new set of filters for 5 different sample rates (ie : 10 filters) in rephase is quite laborious and error prone. This is in no way a criticism of rephase! Would it be useful if CamillaDSP could directly import REW filter settings which are in XML format? Rephase can import this format. Additionally CamillaDSP could then create filters on the fly for whatever sample rate was incoming?
Here is a simple script to transltate:
Code:
import xml.etree.ElementTree as ET 
import sys 
import yaml 
 
 
fname = sys.argv[1] 
shortname = fname.rsplit('/')[-1].split('.')[0] 
 
tree = ET.parse(fname) 
root = tree.getroot() 
 
filters = {} 
 
for speaker in root: 
    speakername = speaker.get('location') 
    print(f"Speaker: {speakername}") 
    for filt in speaker: 
        filt_num = filt.get('number') 
        filt_enabled = filt.get('enabled') 
        freq = float(filt.find('frequency').text) 
        gain = float(filt.find('level').text) 
        q = float(filt.find('Q').text) 
        print(f"Filter: {filt_num}, enabled: {filt_enabled}, f: {freq}, Q: {q}, gain: {gain}") 
        filter_name = f"{speakername}_{filt_num}" 
        filtparams = {"type": "Peaking", "freq": freq, "gain": gain, "q": q} 
        filtdata = {"type": "Biquad", "parameters": filtparams } 
        filters[filter_name] = filtdata 
 
config = {"filters": filters} 
print(yaml.dump(config))


Output:
Code:
> python xmltranslate.py "L front.xml" 
Speaker: leftFront 
Filter: 1, enabled: true, f: 102.0, Q: 3.52, gain: -9.8 
...
Filter: 9, enabled: true, f: 11052.0, Q: 2.26, gain: -1.4 
filters: 
  leftFront_1: 
    parameters: 
      freq: 102.0 
      gain: -9.8 
      q: 3.52 
      type: Peaking 
    type: Biquad 
  leftFront_2: 
    parameters: 
      freq: 154.0 
      gain: 5.1 
      q: 6.14 
      type: Peaking 
    type: Biquad 

...
 
I'm using 0.4 beta 3 and have come across some odd, but I guess documented, behavior and wondered if this is really the way you want it to work.

If I send the command "Stop" over the websocket it stops and goes to the invalid state.

But then if I send it SetConfigName followed by Reload it doesn't restart. The documentation for Stop does say that it needs to be restarted with a SetConfig command, but maybe Reload should restart it as well?
 
I'm using 0.4 beta 3 and have come across some odd, but I guess documented, behavior and wondered if this is really the way you want it to work.

If I send the command "Stop" over the websocket it stops and goes to the invalid state.
Invalid or Inactive? I hope Inactive, otherwise there is something strange somewhere :)

But then if I send it SetConfigName followed by Reload it doesn't restart. The documentation for Stop does say that it needs to be restarted with a SetConfig command, but maybe Reload should restart it as well?
Good idea, it would make sense if Reload also works from the stopped state. It's an easy fix, I'll include it in the next release.
 
Sorry, yes it's invalid. Here's the results cut and pasted from a series of commands where I attempted to restart it with reload. No types from brain or fingers this way. :p

Code:
{"SetConfigName":{"result":"Ok"}}
{"Reload":{"result":"Ok"}}
{"GetConfig":{"result":"Ok","value":"---\n~"}}
{"GetState":{"result":"Ok","value":"Inactive"}}
 
Ok, another bug I've just noticed. If Stop is sent while camilla is already stopped, then camilla will die the next time a SetConfig command is sent. It's not completely dead. The process is still running, the websocket still responds, but the -vv logging indicating it is doing something stops and it no longer releases any alsa handles it has open when stop is called again after the failed SetConfig.
 
Ugggg! It's inactive! Not invalid. Sorry. I did the cut and paste and STILL managed to screw up the post. I do not see "Invalid" from camilla.

On the bright side though I just got things working so that camilla can be used with different sample rate and format changes from alsa programs without any mods required to the alsa program other than pointing them at the right alsa device.

I've tested it with aplay and mpd and one of the example scripts from alsa. It does assume you can't change rates without closing the pcm device. I think that's safe though because when I tried that in the alsa example program it threw an error saying I wasn't allowed to change the hw_params after I'd already set them. (I'm not an alsa expert though.)

It's a little delicate but if you're up for some hardening we can probably make it fairly robust. At least we can provide an option that's a little easier than modifying every program to be aware of informing camilla to close its end of the loopback device.
 
FIR's are not necessarily better but they can do things that IIR filters cannot and they have one other benefit that they are exact. Transferring IIR filter settings between devices gets you into the quagmire of Q definitions. Rephase exports from REW use constant Q.

Room EQ can only be successful below the Schroder frequency but speaker correction based on shorter windows can be run full range.

This thread is a good place to start as there is a great deal of misunderstanding about this aspect of processing

A convolution based alternative to electrical loudspeaker correction networks

Thanks mate! Some excellent reading there!
 
Here is a simple script to transltate:
Code:
import xml.etree.ElementTree as ET 
import sys 
import yaml 
 
 
fname = sys.argv[1] 
shortname = fname.rsplit('/')[-1].split('.')[0] 
 
tree = ET.parse(fname) 
root = tree.getroot() 
 
filters = {} 
 
for speaker in root: 
    speakername = speaker.get('location') 
    print(f"Speaker: {speakername}") 
    for filt in speaker: 
        filt_num = filt.get('number') 
        filt_enabled = filt.get('enabled') 
        freq = float(filt.find('frequency').text) 
        gain = float(filt.find('level').text) 
        q = float(filt.find('Q').text) 
        print(f"Filter: {filt_num}, enabled: {filt_enabled}, f: {freq}, Q: {q}, gain: {gain}") 
        filter_name = f"{speakername}_{filt_num}" 
        filtparams = {"type": "Peaking", "freq": freq, "gain": gain, "q": q} 
        filtdata = {"type": "Biquad", "parameters": filtparams } 
        filters[filter_name] = filtdata 
 
config = {"filters": filters} 
print(yaml.dump(config))


Output:
Code:
> python xmltranslate.py "L front.xml" 
Speaker: leftFront 
Filter: 1, enabled: true, f: 102.0, Q: 3.52, gain: -9.8 
...
Filter: 9, enabled: true, f: 11052.0, Q: 2.26, gain: -1.4 
filters: 
  leftFront_1: 
    parameters: 
      freq: 102.0 
      gain: -9.8 
      q: 3.52 
      type: Peaking 
    type: Biquad 
  leftFront_2: 
    parameters: 
      freq: 154.0 
      gain: 5.1 
      q: 6.14 
      type: Peaking 
    type: Biquad 

...

Wow thanks a lot Henrik! I will have a play with it tonight. BTW I think the code block has scrambled the text again. I had to to look at the page html source to get an unscrambled version (attached)
 

Attachments

  • xmltranslate.zip
    583 bytes · Views: 43
@Henrik

Back to RustFFT and FFTW3 and ARM Neon support.

If I compile C-DSP with customized options as you've suggested

RUSTFLAGS='-C target-feature=+neon -C target-cpu=native'

Does this mean RustFFT also runs with Neon enabled?

In general you say ARM Neon is supported:

Linux on Armv7 with Neon, intended for Raspberry Pi 2 and up but should also work on others


What actually would be Neon enabled for us PI users ? Rubato? RustFFT ?


Enabling FFTW3 Neon supports seems top be a challenging task. I'll put that on hold for the time being.

I'll run some performance benchmarks as soon as I know how to enable/disable Neon properly.

And hopefully you can build the earlier discussed "Dirac" function. That'd make testing much easier.

Thx.
 
Last edited:
Ugggg! It's inactive! Not invalid. Sorry. I did the cut and paste and STILL managed to screw up the post. I do not see "Invalid" from camilla.
Haha ok no worries :)


On the bright side though I just got things working so that camilla can be used with different sample rate and format changes from alsa programs without any mods required to the alsa program other than pointing them at the right alsa device.

I've tested it with aplay and mpd and one of the example scripts from alsa. It does assume you can't change rates without closing the pcm device. I think that's safe though because when I tried that in the alsa example program it threw an error saying I wasn't allowed to change the hw_params after I'd already set them. (I'm not an alsa expert though.)

It's a little delicate but if you're up for some hardening we can probably make it fairly robust. At least we can provide an option that's a little easier than modifying every program to be aware of informing camilla to close its end of the loopback device.
Alright! I'm very interested in this! Please tell more :)
 
@Henrik

Back to RustFFT and FFTW3 and ARM Neon support.

If I compile C-DSP with customized options as you've suggested

RUSTFLAGS='-C target-feature=+neon -C target-cpu=native'

Does this mean RustFFT also runs with Neon enabled?

In general you say ARM Neon is supported:




What actually would be Neon enabled for us PI users ? Rubato? RustFFT ?
Basically everything that is compiled from Rust sources is using neon then. Firstly it seems to use neon instructions instead of the fpu for floating point stuff. I haven't investigated so not sure what the advantage of that is. Then there is also auto-vectorization of loops. In practice there aren't that many loops where it manages to do that, so the speed avdantage isn't that impressive. I don't remember the numbers I got, but at least the difference was big enough to make it worthwhile to use neon.


And hopefully you can build the earlier discussed "Dirac" function. That'd make testing much easier.

Thx.
If you build the current develop branch you have this now. I didn't have time to test it properly yet but it should work. I didn't make a separate, type, instead use the "Values" variant for Conv and give it a "length" parameter (see the readme of the develop branch). Then it will just pad with zeroes to the values you give up until the given length.