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

Hi @HenrikEnquist,

I was poking around the CamillaDSP code a bit and noticed that the coefficients used for fractional-sample delay don't seem optimal. Given the transfer function
transfer_func.png
,
your code uses
eta_current.png
,
where delta is the fractional part of the delay. The typical formula is
eta_correct.png
,
which results in the delay approaching delta as the frequency goes to zero. See the CCRMA page on first-order allpass interpolation. Additionally, very small fractional delay can be problematic for reasons discussed in the linked article. It is suggested to restrict the delay range to [0.1, 1.1] rather than [0, 1].

You could also save some CPU time by not implementing the filter as a generic biquad. Due to the symmetry of allpass coefficients, you only need a single multiply per sample for a first-order allpass (or two multiplies for secord order). The first-order difference equation can be written as
diff_eqn.png


If you feel like getting a bit fancier, there's a fairly simple and numerically stable way to implement Thiran fractional delay filters of arbitrary order: A Simple Ladder Realization of Maximally Flat Allpass Fractional Delay Filters. I wrote a basic (i.e. not particularly optimized) implementation in C that I'm happy to share if you're interested.
 
What happens here:

Code:
2025-02-25 00:23:21.660959 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:21.661431 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(3.723))
2025-02-25 00:23:21.719098 DEBUG [src/coreaudiodevice.rs:886] Measured sample rate is 44102.7 Hz
2025-02-25 00:23:21.719219 DEBUG [src/coreaudiodevice.rs:903] Rate watcher, measured sample rate is 44103.8 Hz
2025-02-25 00:23:21.768056 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:21.768493 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(3.83))
2025-02-25 00:23:21.874761 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:21.875149 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(3.937))
2025-02-25 00:23:21.962200 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetVolume)
2025-02-25 00:23:21.963079 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetMute)
2025-02-25 00:23:21.981328 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:21.981779 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(4.044))
2025-02-25 00:23:22.088138 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:22.088586 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(4.15))
2025-02-25 00:23:22.194911 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:22.195279 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(4.257))
2025-02-25 00:23:22.301645 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:22.302165 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(4.364))
2025-02-25 00:23:22.407470 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:22.407847 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(4.47))
2025-02-25 00:23:22.514076 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:22.514560 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(4.576))
2025-02-25 00:23:22.620839 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:22.621192 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(4.683))
2025-02-25 00:23:22.621621 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetCaptureRate)
2025-02-25 00:23:22.621887 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetRateAdjust)
2025-02-25 00:23:22.622158 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetBufferLevel)
2025-02-25 00:23:22.622448 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetClippedSamples)
2025-02-25 00:23:22.622686 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetProcessingLoad)
2025-02-25 00:23:22.728792 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:22.729298 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(4.791))
2025-02-25 00:23:22.810318 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 441 with len 4096
2025-02-25 00:23:22.821922 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 442 with len 4096
2025-02-25 00:23:22.833461 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 443 with len 4096
2025-02-25 00:23:22.835026 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:22.835483 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(4.897))
2025-02-25 00:23:22.845147 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 444 with len 4096
2025-02-25 00:23:22.856691 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 445 with len 4096
2025-02-25 00:23:22.868373 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 446 with len 4096
2025-02-25 00:23:22.879995 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 447 with len 4096
2025-02-25 00:23:22.891524 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 448 with len 4096
2025-02-25 00:23:22.903180 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 449 with len 4096
2025-02-25 00:23:22.914806 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 450 with len 4096
2025-02-25 00:23:22.926391 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 451 with len 4096
2025-02-25 00:23:22.937997 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 452 with len 4096
2025-02-25 00:23:22.940207 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetState)
2025-02-25 00:23:22.940488 DEBUG [src/socketserver.rs:522] parsed command: Ok(GetSignalLevelsSince(5.004))
2025-02-25 00:23:22.949589 DEBUG [src/coreaudiodevice.rs:687] Dropping captured chunk 453 with len 4096

A lot of dropped chunks?

Why? How to fix?

//
 
Upgraded tov3 and after an import, which seemed successful, I get:

import.jpg


2025-02-25 01:19:41.137813 ERROR [src/socketserver.rs:1202] Error parsing yaml: pipeline: unknown field channel, expected one of channels, names, description, bypassed at line 753 column 1

What to do?

//
 
Yes exactly. For some reason this device doesn't always wake up automatically when playback is supposed to resume. I'm testing if it helps to send a specific unpause request. Looks promising but needs some more testing, and if it works I'll include it in the next release.
Thank you! Disabling the silencer solved the issue for me. Now everything seems to work as it should, no random crashes or channel remappings.
 
Something is fishy...

I installed freshly 3.0.2 from here: https://github.com/HEnquist/camilladsp-setupscripts/releases/tag/v3.0.2

Choose a default config
Add a filter
Add a pipeline... error

unexp.jpg


Why o why?

My other installation (on Apple HW) looked like it was only one verision number but hoovering over it showed the rest. On this Intel HW mac, the versions are all visible..

Do I really have 3.0.2 I wonder...

//
 
I now see 3.0.2 as I expected it to look like. Was this a WEB browser thing....? Yeah probably..

It's one of those days...

I have new DACs which uses a diyinhk multi channel XMOS board. An I have a hard time starting CDSP now.. how are they interrelated?

The order how I power up tings and the assigned id for different USB units in the mac seem to have a role in this problem.

Most often I can't get the UV meters to react to what the connected player is doing.

  • I have Squeezplay to output to Blackhole 2ch
  • I have CDSP to listen to BH 2ch

Yet it dosen't seem that CDSP gets any input. Or is it freezing immediately?

Out of say 6-7 trails with restarts etc I managed - but this time a left the diyinhk powered off, this made CDSP startup complain about a the missing device but I ignored that, and after having configured Blackhole 2ch to be used by Squeezplay, I powered on the DAC and the VUs moved.

Strange thing was that in this state I didn't get any sound out of the DACs... silence.

Maybe there is some strange race for resources... but most things go bad today so maybe one shall with until tomorrow 🙂

//
 
your code uses
eta_current.png
,
where delta is the fractional part of the delay. The typical formula is
eta_correct.png
,
which results in the delay approaching delta as the frequency goes to zero.
Thanks! I don't remember where I got the formulas from, so don't know if the source was wrong or if I copied it wrong. Luckily he difference isn't very big, but I'll fix it it the next release.


You could also save some CPU time by not implementing the filter as a generic biquad.
Yes I know, I used biquad just because it was already available. This is on the list of small improvements to be done some day 🙂


If you feel like getting a bit fancier, there's a fairly simple and numerically stable way to implement Thiran fractional delay filters of arbitrary order:
I checked the Thiran filter of Octave. For first order it gives the same result as the simple formula. I use a delay line for the full samples, and a first order iir for the fractional. Is there some advantage with using a higher order Thiran filter here?
 
  • Like
Reactions: rickmcinnis
I now see 3.0.2 as I expected it to look like. Was this a WEB browser thing....? Yeah probably..
Looks like your browser had cached the old frontend code! A page reload should take care of that.


I have new DACs which uses a diyinhk multi channel XMOS board.
I have one of those xmos boards. I have not connected it to a Mac for some time, but last time I did it worked fine. The log you sent me doesn't give any clues. Can you try again with trace logging, -vv?
 
  • Thank You
Reactions: TNT
Now it all seem to work. But I have to change the way I start the system and it behave now repeatable. It seems that USB ports are being shuffle around?

See to that player is first started...
1) cd /usr/local/bin
./SPstartScript.sh -s
->1 (44,1)
->2 (Blackhole 2ch)

2) DINHK DAC power on

3) Then start the gui :

cd /Users/au/camilladsp/gui
conda run -n camillagui python main.py

4) ~/camilladsp/bin/camilladsp -w -p 1234 --statefile /Users/au/camilladsp/statefile.yml

5) Safari: localhost:5005

But its not good that it require me to mange power swithes to turn on the system... should a solution..

This for a Mac mini (Late 2012) / Catalina

//
 
I checked the Thiran filter of Octave. For first order it gives the same result as the simple formula.
That formula is indeed that of a first-order Thiran allpass, so this is expected 😉

The answer to that is also on CCRMA, higher orders stay flat longer, but once they start deviating they deviate further.
Depends on whether you're looking at group delay or phase delay. The phase delay is always some number of whole samples at Fs/2, so any order will have the same phase error at Fs/2. Group delay is related to the slope of the phase response, so it makes sense that the group delay deviates more near Fs/2 for higher orders. Even a first order filter looks bad if the target delay is small (say <0.1 samples).

Probably best to use an order >1, <10, but which one?
You get into diminishing returns rather quickly. Optimal would depend on the application and the sample rate. Here's the phase error (i.e. deviation from an ideal fractional delay) of a few Thiran allpass filters (Fs=44.1kHz; delay=N-0.5 samples, where N is the filter order):
thiran_ap.png
 
Last edited:
You get into diminishing returns rather quickly. Optimal would depend on the application and the sample rate.
I was looking at group delay yes, now I added a plot of phase delay as well. It's quite tempting to use order = 2. It's better than first order, and I can continue being lazy and implement the delay with a biquad 😛

Some plots, phase delay at 44.1 kHz, with fraction set to some evenly spaced values from close to 0 to just over 1:
phasedelay_order2.png
group delay for the same:
groupdelay_order2.png
and resulting phase delay vs requested for a couple of frequencies, follows quite nicely:
phasedelay_vs_requested_order2.png
 
Hello fellow DSPers!
I recently reinstalled the "server" I run CamillaDSP on. "Server" in quotation marks because it's just an old laptop with the lid closed running headless most of the time, with a USB soundcard attached (UMC1820 from Behringer).

I did a fresh install of Ubuntu 24.04 Server and the latest CamillaDSP and GUI (3.00 and 3.02 respectively).

It works, but for some reason my log file is getting demolished with lines like these:
Code:
2025-02-26 14:39:32.270959 WARN  [src/alsadevice.rs:320] Capture read 6 frames instead of the requested 13
2025-02-26 14:39:32.271089 WARN  [src/alsadevice.rs:320] Capture read 6 frames instead of the requested 7
2025-02-26 14:39:32.271363 WARN  [src/alsadevice.rs:320] Capture read 11 frames instead of the requested 64

It's filling the log at such a rate that it's not long before it's GB sized.

Now the question is why and how do I fix it? I've searched on the web but not really found anything to help me fix this.

Since it looks kind of buffer related I've tried going way higher with the buffer size in asound.conf together with larger chunk size in CamillaDSP, but all that seems to accomplish is to change the numbers. Ie, here's an example where the buffer size is several times larger than the example above:
Code:
2025-02-26 15:02:52.825825 WARN  [src/alsadevice.rs:320] Capture read 40 frames instead of the requested 64
2025-02-26 15:02:52.826920 WARN  [src/alsadevice.rs:320] Capture read 24 frames instead of the requested 64
2025-02-26 15:02:52.827330 WARN  [src/alsadevice.rs:320] Capture read 24 frames instead of the requested 40
So the read/requested numbers are higher, but otherwise nothing changed, it's still spamming my poor log file.

Configuration settings don't seem to change anything, even a config with everything on "Default" and no filters or anything in the Pipeline has the same issue as my normal config.

EDIT: Oh, and here's what the log looks like with very verbose instead (-vv):
Code:
2025-02-26 15:33:34.422593 TRACE [src/alsadevice.rs:267] Capture pcmdevice.wait with timeout 20 ms
2025-02-26 15:33:34.422644 TRACE [src/alsadevice_utils.rs:370] Got 1 ready fds
2025-02-26 15:33:34.422650 TRACE [src/alsadevice.rs:292] Capture waited for Some(56.509µs)
2025-02-26 15:33:34.422682 WARN  [src/alsadevice.rs:320] Capture read 6 frames instead of the requested 34
2025-02-26 15:33:34.422690 TRACE [src/alsadevice.rs:267] Capture pcmdevice.wait with timeout 20 ms
2025-02-26 15:33:34.422769 TRACE [src/alsadevice_utils.rs:370] Got 1 ready fds
2025-02-26 15:33:34.422778 TRACE [src/alsadevice.rs:292] Capture waited for Some(88.241µs)
2025-02-26 15:33:34.422811 WARN  [src/alsadevice.rs:320] Capture read 6 frames instead of the requested 28
2025-02-26 15:33:34.422816 TRACE [src/alsadevice.rs:267] Capture pcmdevice.wait with timeout 20 ms
2025-02-26 15:33:34.422891 TRACE [src/alsadevice.rs:149] PB: device waited for Some(699.137µs), ready
2025-02-26 15:33:34.422923 TRACE [src/alsadevice_utils.rs:370] Got 1 ready fds
2025-02-26 15:33:34.422929 TRACE [src/alsadevice.rs:292] Capture waited for Some(113.384µs)
2025-02-26 15:33:34.422951 WARN  [src/alsadevice.rs:320] Capture read 6 frames instead of the requested 22
2025-02-26 15:33:34.422956 TRACE [src/alsadevice.rs:267] Capture pcmdevice.wait with timeout 20 ms
2025-02-26 15:33:34.423050 TRACE [src/alsadevice_utils.rs:370] Got 1 ready fds
2025-02-26 15:33:34.423054 TRACE [src/alsadevice.rs:180] PB: wrote 128 frames to playback device as requested
2025-02-26 15:33:34.423059 TRACE [src/alsadevice.rs:292] Capture waited for Some(103.493µs)
2025-02-26 15:33:34.423063 TRACE [src/countertimer.rs:153] Averager: added value 142, nb. 737
2025-02-26 15:33:34.423092 WARN  [src/alsadevice.rs:320] Capture read 6 frames instead of the requested 16
2025-02-26 15:33:34.423097 TRACE [src/alsadevice.rs:267] Capture pcmdevice.wait with timeout 20 ms
2025-02-26 15:33:34.423160 TRACE [src/alsadevice_utils.rs:370] Got 1 ready fds
2025-02-26 15:33:34.423171 TRACE [src/alsadevice.rs:292] Capture waited for Some(74.665µs)
2025-02-26 15:33:34.423207 WARN  [src/alsadevice.rs:320] Capture read 6 frames instead of the requested 10
 
Last edited:
How would I go about to do this?
You stop the CamillaGUI service/process, don't remember what OS you're on but basically that's it. CamillaDSP continues working but the GUI is down.

For me on Linux I'd use sudo systemctl stop CamillaGUI, on Windows I'd use services.msc (or in PowerShell Stop-Service CamillaGUI) etc. Not sure on OSX but probably something along those lines.
 
  • Thank You
Reactions: TNT