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

Thanks @phofman !
I'm planning to use my ultralite's loopback, so software loopback won't be a problem.
I found that it actually works if I type in the channel name that the audio interface provides like S/PDIF OUT 1-2(Ultralite-mk5). That's a good start.
However, this method only allows me to utilize two of the stereo input/output channels, for example Line Out 1-2,Line Out 3-4, Line Out 5-6, etc. And it doesn't appear to allow me to input multiple playback devices.
What I was hoping for was to recognize the entire device at once and use all 16 inputs and 18 outputs at once, and apparently the tutorials using the Raspberry Pi do this in the form of hw:Ultralitemk5. Your link also shows 2 channel application.. so I'm stuck again 😢
Hi maybe I can help you with attaching my post on audiosciencereview. I managed to display alle the separate channels of my device as one multichannel interface.

Its requiring a specific setup in Windows.

https://audiosciencereview.com/forum/index.php?threads/camilladsp-and-rme-interfaces.35838/

Gr Marcel
 
I have a parametric filter running Camilla in Moode. My DAC won't accept higher than 96kHz so I want to add a resampler so that everything comes out at 48kHz. I looked but can't find this. Can someone help me with this? Here is my existing filter:
 

Attachments

  • Diatone.7z
    767 bytes · Views: 4
Last edited:
HiFiBerry DAC8X on Raspberry Pi 5 with CamillaDSP and touchscreen.

pi and amps.jpg


I put this together as an experiment to show that a full DSP could be homebuilt with a Raspberry Pi mounted to a touchscreen for control.
Pi screen back.jpg

This shows the DAC8X mounted on the RPi5. The screen is an Elecrow 7" (I need to invert the RPi so the cables come out the top. I will hunt down some right angled 3.5mm plugs). Screen works like a phone screen, pinch out to zoom expand, pinch in to shrink, drag with finger etc.

Pi screen pinched for Vol ctl.jpg

Screen pinched out to show volume control slider.

The zoom controls make the slider easy to control. Selection of other configs is also easy after selecting the Files tab and zoom.

Pi pipeline.jpg
Pi pipeline collapsed.jpg
pi pipeline collapsed & expanded.jpg

These pics are the Pipeline, pipeline collapsed and the pinched out.

For setup I used a Logitech K400r keyboard, and bookmarked the web addresses I use frequently in the browser. I have not bothered with a "virtual" keyboard yet.
This demo was using Squeezelite as a streamer. A USB audio interface such as a Motu M4 could be added to provide analog input.
The Pi is running Raspberry Pi OS Lite 64bit and the Chromium browser, so with a couple of bookmarks I can run the Squeezebox server or check the state of my Solar PV system.
Pi screen of Squeezebox server.jpg
Pi screen os EmonPi.jpg


The K-Horn speakers are very sensitive (110db/W for mid & hi) but I could not hear any hiss even with the volume right up and my ear in the mid horn mouth, the DAC8X does not add any noise. The first pic shows the amps, an N-Core from Audiophonics for bass and a pair of SMSL SP200 THX headphone amps for the mid and hi.



https://www.elecrow.com/rc070p-7-in...-ips-display-with-built-in-speaker-stand.html
 
Last edited:
  • Like
Reactions: 5 users
That's exactly the difference. The reasoning is that the real-world capture/playback ratio does not deviate much (is basically constant) and the adjustment should strive to approach that value, instead of jumping around with some average. You can play with the adjustment constants in https://github.com/HEnquist/camilladsp/blob/eagain/src/alsadevice_utils.rs#L221 . The algorithm and the param values were taken from alsaloop, IIRC.

Maybe the algorithm constants could dynamically reflect the chunksize - larger chunksize could be slower will fewer fluctuations, smaller chunks more agile.

Keeping small chunksize/latency = small buffers goes against feedback rate adjustments, especially should the processing times vary (due to varying load on the DSP CPUs caused by other processes).

If you look at the chart at https://www.diyaudio.com/community/...rce-to-playback-hw-device.408077/post-7574371 - IMO the optimum target level should be slightly above chunksize, not at chunksize.

Revisiting this post as I've been experimenting with a HifiBerry DAC8x and as a result I've been dealing with an asynchronous system that requires rate adjust.

I know that it would seem that in the real-world the capture/playback ratio would be constant, but this does not seem to always be the case.

Using an ALSA loopback as capture device and the DAC8x with rate adjust but no resampling seems pretty constant. I have the rate adjust interval set to 2 seconds and the buffer level usually only changes ~10 samples each time and stays within +/- ~100 samples of the target level. On occasion I get a bit more variation but usually not.

However, when I use a SPDIF to USB card (like the hifime S2 digi) as capture device with rate adjust and async resampling I sometimes get jumps of 100s of samples each interval. With the 2 second rate adjust interval I am able to avoid buffer under runs but sometimes it gets close to using up the buffer.

Any idea what would cause this behavior? I've tried a few different SPDIF sources, and I see it on all of them. This is only anecdotal, but it usually seems like what happens is the buffer level exceeds my target and the rate adjust adjusts continues to adjust down (usually only going down to 0.9997) but the buffer level keeps increasing. Eventually there will be a sequence of large drops in buffer level until I am hundreds of samples below my target level. It is almost like the feedback is delayed.

Michael
 
@mdsimon2 : Lately I have been stress-testing weak RK3308 and its capabilities, using CDSP as a bridge + optionally async resampling between UAC2 gadget and the SoC I2S interface/s. That SoC has a driver which supports merging two I2S to produce 16ch out + 10ch in or any combination up to 26 channels.

Since the I2S hardware has fixed max buffers (512kB per I2S interface, IIRC), at 16ch out at 384kHz the maximum buffer frames size ends up quite small. I used CDSP 2 with 4 chunks per buffer and 8 periods per buffer. Target level was set at 75% of the buffer size, i.e. 3 chunksizes. I increased the base adjustment step ten fold to 1e-4 https://github.com/HEnquist/camilla...45df8bfba6e22cc1/src/alsadevice_utils.rs#L233 , adjustment period down to 2 seconds, to make sure the regulation kicks in faster and is more steep.

WIth this setup the system was able to cleanly playback and capture e.g. IIRC USB host -> gadget -> CDSP1 compiled with float32 format sync resamplling 16ch from 48kHz to 384kHz -> 16ch I2S -> 10ch I2S -> CDSP2 rate adjust to playback, using 5 channels only (isochronous packet limit) -> gadget -> host, no xruns, no issues on sox spectrogram for 20 minutes. Amazingly stable performance from CDSP (and that SoC too). All four cores were quite loaded, yet no timing issues. CDSP1 doing the resampling was reniced to -19. I did not play with any RT priorities or core assignments.

Of course the buffer level deviates, but never got any wild fluctuations. Also if your processing thread takes time and your CPU load varies (other processing running on the background), the processed chunks get delivered to the playback with varying delay which makes the buffer fluctuate more.

What was your buffer size and target level? Hundreds of samples seem safe if your buffer is several thousands and the target level is above half of it. Using the whole buffer is no problem, in fact you want the buffer as full as possible - larger safety margin. There can be no buffer overflow on playback, writing the samples will simply wait. Underflow is a problem on playback. Unlike on capture where overflow is a problem.

This is only anecdotal, but it usually seems like what happens is the buffer level exceeds my target and the rate adjust adjusts continues to adjust down (usually only going down to 0.9997) but the buffer level keeps increasing. Eventually there will be a sequence of large drops in buffer level until I am hundreds of samples below my target level. It is almost like the feedback is delayed.
Look at the algorithm, its quite simple https://github.com/HEnquist/camilla...45df8bfba6e22cc1/src/alsadevice_utils.rs#L221 . At start it does not know the samplerate ratio (the capture_speed). If the delay difference (in samples) is outside of some "equality" range, the capture speed is increased (resp. decreased) by 3 speed_delta steps. If the delay difference is inside this equality range, the adjustment drops to one speed_delta step, in resp. direction.

The buffer level keeps increasing if the capture speed is still too fast. But it slows down every cycle and eventually will get too slow, causing adjustment to the other side.

It takes some cycles to reach and settle around the real capture speed, therefore it may be useful to decrease the cycle period (adjustment time). Exact target level will never be reached stably, too many timing factors in the chain - look at that linked chart how complicated the timing is. But if the deviations do not drop below some reasonable level (e.g. I would not be OK with some 20% of the buffer - hence my target at 75%), IMO no need to worry.

EDIT: corected async -> sync resampling in my setup description (no need for async).
 
Last edited:
What was your buffer size and target level? Hundreds of samples seem safe if your buffer is several thousands and the target level is above half of it. Using the whole buffer is no problem, in fact you want the buffer as full as possible - larger safety margin. There can be no buffer overflow on playback, writing the samples will simply wait. Underflow is a problem on playback. Unlike on capture where overflow is a problem.
At 44.1 kHz sample rate I was using a chunk size of 1024 and target level of 1024.

A noob question, I thought the CamillaDSP buffer size was 2x chunk size, is this correct? So when I am setting my target at 1024 that is actually half the buffer? And I actually want it to be something like 75% of the buffer, i.e. 1536? The GUI reported buffer is typically is in the 800-1300 range but I've seen it below 400 a few times.

I know I can increase the buffer size but I am trying to minimize latency. I've been spoiled by my normal synchronous systems which I run at 128 chunk size and no rate adjust (48 kHz sample rate). Sounds like the answer is decrease the rate adjust interval and increase the speed delta.

Michael
 
I thought the CamillaDSP buffer size was 2x chunk size, is this correct?
It was changed to 4x chunksize in v2 https://github.com/HEnquist/camilladsp/blame/v2.0.3/src/alsadevice_buffermanager.rs#L22

So if you set target level equal to chunksize, you are setting at 25% which is too low, IMO

For lower latencies - it's always better to have smaller granularity of buffer updates, that's why the Henrik's change from 2 to 4 chunks is very good. I would try using smaller chunks and still keep the target level at 3 chunks (see https://github.com/HEnquist/camilladsp/issues/335 to modify your code if needed). Smaller chunks will take a bit more CPU but more chunks give more buffer safety.

Of course you can play with the rate adjustment parameters too. Updates more often (adjustment period), larger single change (maybe) https://github.com/HEnquist/camilla...45df8bfba6e22cc1/src/alsadevice_utils.rs#L233 , larger change if too far from the target level https://github.com/HEnquist/camilla...45df8bfba6e22cc1/src/alsadevice_utils.rs#L237 + https://github.com/HEnquist/camilla...45df8bfba6e22cc1/src/alsadevice_utils.rs#L245

As of the buffer size - it's always good to check the actual device params in /proc/..../hw_params. Also CDSP reports the requested device params in logs with -v (debug), very useful to check for the initial runs with new configuration. The timing is designed for buffer = 4 x chunksize = 8 x period. In some cases I hit device limits where e.g. buffer would end up equal to chunksize (getting immediately xruns), period could not be of the requested size, etc. CDSP asks alsa to set some values, but the driver actually sets values within its constraints. Really good to check the reality.
 
Last edited: