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

Hi all

Rust is a main topic of mine, and I wrote some of the SIMD libs which you might want to use (https://github.com/Lokathor/wide) for performance; Rust is great at threading so maybe I can take a look at the code on that?
Hi! So RonnieX = Lokathor? I have seen that you have been contributing quite a bit to simd in Rust :)
The heaviest parts of Camillladsp is the FFT/iFFT steps used for convolution and synchronous resampling (from RustFFT), and the multiply-accumulate steps in the asynchronous resampler. These are already using AVX, SSE or Neon if available (almost, Neon is nearly done for RustFFT).
Some other parts could be accelerated too but the overall benefit would be quite small. Considering that I prefer to keep the code simpler.

I would like to break the processing pipeline up into more than a single thread at some point. That will take some thinking since most steps in the pipeline depend on the previous one.
 
This is just how convolution works. Mathematically it's equivalent to convolve a signal first with one IR and then another, as to first convolve the two IRs and then convolve the signal with that result.

Let's say both IRs consist of a large main feature in the middle, surrounded by smaller wiggles that get smaller and smaller towards each end. To calculate the discrete convolution of them, we slide one over the other. For each step, we multiply all elements, and then sum all the products. This sum is one point of the result. The first point is when the IRs overlap in just a single point, and the last point is when they have passed so only the last points overlap. This gives lenth1+lenght2 new points.

Because the wiggles at the start and end of the IRs are of small amplitude, the result will have very very low amplitude far from the center. You should be able to truncate it back down to the length of the IRs you started with, just apply a window function to make sure the ends go to zero.
HenrikEnquist,

Thanks much. That helps. I tweaked the DRC-FIR code to optionally merge a supplied RePhase XO with the target solution it generates so the XO defines the correction gating. I looked at how it's code calculated the RMS value and the center value. The RMS code just walks all of the samples from beginning to end searching for the sample and position with the largest absolute value of all of the (64-bit floats) samples. Once the centering value was right, the test convolutions snapped into place. I then compared that DRC-FIR modified result with the CamillaDSP result (after realizing it could be done in CamillaDSP). The results of the 2 are very close, but the modified code file length is off by 1 sample so I have some more work to do [(2 x #taps) - 1 versus (2 x #taps)].

My assumption was the following would happen [ sample c = sample a convolve sample b]:

IR1 - aaaaaaaaaaaaaAAAAAAAAAAAAAAaaaaaaaaaaaaa
IR2 - bbbbbbbbbbbbbBBBBBBBBBBBBBBbbbbbbbbbbbbb
SUM - cccccccccccccCCCCCCCCCCCCCCccccccccccccc

But if I understand your response correctly, the following would happen:

IR1 - aaaaaaaaaaaaaAAAAAAAAAAAAAAaaaaaaaaaaaaa
IR2 - bbbbbbbbbbbbbBBBBBBBBBBBBBBbbbbbbbbbbbbb
SUM - ccccccccccccccccccccccccccCCCCCCCCCCCCCCCCCCCCCCCCCCCCcccccccccccccccccccccccccc

Which can be windowed down to something like this:

SUM - cccccCCCCCCCCCCCCCCCCCCCCCCCCCCCCccccccc
 
The results of the 2 are very close, but the modified code file length is off by 1 sample so I have some more work to do [(2 x #taps) - 1 versus (2 x #taps)].
Check the actual values, I would bet that the extra value in the longer one is a zero at either start or end.
But if I understand your response correctly, the following would happen:

IR1 - aaaaaaaaaaaaaAAAAAAAAAAAAAAaaaaaaaaaaaaa
IR2 - bbbbbbbbbbbbbBBBBBBBBBBBBBBbbbbbbbbbbbbb
SUM - ccccccccccccccccccccccccccCCCCCCCCCCCCCCCCCCCCCCCCCCCCcccccccccccccccccccccccccc

Which can be windowed down to something like this:

SUM - cccccCCCCCCCCCCCCCCCCCCCCCCCCCCCCccccccc
Yes correct!
 
Check the actual values, I would bet that the extra value in the longer one is a zero at either start or end.

Yes correct!

It depends on how you define zero. =)

The printouts below only print out differing indexes (e.g. index [1 & 2] are both zero, thus omitted).

Beginning of both files:
Code:
  INDEX             CamillaDSP Merge                  DRC-FIR-MOD Merge
[0000000]    -0.000000000000000000027105 ?=     0.000000000000000000000000
[0000003]    -0.000000000000000000027105 ?=     0.000000000000000000000000
[0000004]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000006]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000008]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000009]     0.000000000000000000027105 ?=     0.000000000000000000000000
[0000010]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000011]    -0.000000000000000000054210 ?=     0.000000000000000000000000
[0000016]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000018]     0.000000000000000000027105 ?=     0.000000000000000000000000
[0000019]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000020]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000021]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000023]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000025]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000026]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000027]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000029]    -0.000000000000000000027105 ?=     0.000000000000000000000000
[0000031]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000032]    -0.000000000000000000027105 ?=     0.000000000000000000000000
[0000033]    -0.000000000000000000013553 ?=     0.000000000000000000000000
[0000034]    -0.000000000000000000013553 ?=     0.000000000000000000000000

Leading edge where second merge transitions from all-zeros to non-zeros at index 84,420 with a center sample @ 262,144.
Code:
  INDEX             CamillaDSP Merge                  DRC-FIR-MOD Merge
[0084807]    -0.000000000231745564208937 ?=     0.000000000000000000000000
[0084808]    -0.000000000231836689473423 ?=     0.000000000000000000000000
[0084809]    -0.000000000231927804393042 ?=     0.000000000000000000000000
[0084810]    -0.000000000232018908999877 ?=     0.000000000000000000000000
[0084811]    -0.000000000232110003233046 ?=     0.000000000000000000000000
[0084812]    -0.000000000232201087091332 ?=     0.000000000000000000000000
[0084813]    -0.000000000232292160558535 ?=     0.000000000000000000000000
[0084814]    -0.000000000232383223626291 ?=     0.000000000000000000000000
[0084815]    -0.000000000232474276263472 ?=     0.000000000000000000000000
[0084816]    -0.000000000232565318476960 ?=     0.000000000000000000000000
[0084817]    -0.000000000232656350263895 ?=     0.000000000000000000000000
[0084818]    -0.000000000232747371579704 ?=     0.000000000000000000000000
[0084819]    -0.000000000232838382369275 ?=     0.000000000000000000000000
[0084820]    -0.000000000232929382718635 ?=    -0.000000000465661287307739
[0084821]    -0.000000000233020372547688 ?=    -0.000000000465661287307739
[0084822]    -0.000000000233111351857068 ?=    -0.000000000465661287307739
[0084823]    -0.000000000233202320621311 ?=    -0.000000000465661287307739
[0084824]    -0.000000000233293278813789 ?=    -0.000000000465661287307739
[0084825]    -0.000000000233384226471295 ?=    -0.000000000465661287307739
[0084826]    -0.000000000233475163564446 ?=    -0.000000000465661287307739
[0084827]    -0.000000000233566090021935 ?=    -0.000000000465661287307739
[0084828]    -0.000000000233657005878594 ?=    -0.000000000465661287307739
[0084829]    -0.000000000233747911120713 ?=    -0.000000000465661287307739
[0084830]    -0.000000000233838805699588 ?=    -0.000000000465661287307739
[0084831]    -0.000000000233929689626017 ?=    -0.000000000465661287307739

The center of the files mound up together and taper in reverse order of the preceding dumps.

Code:
  INDEX             CamillaDSP Merge                  DRC-FIR-MOD Merge
[0438702]     0.000000000234196688609010 ?=     0.000000000465661287307739
[0438703]     0.000000000234072690078163 ?=     0.000000000465661287307739
[0438704]     0.000000000233948688711343 ?=     0.000000000465661287307739
[0438705]     0.000000000233824684533485 ?=     0.000000000465661287307739
[0438706]     0.000000000233700677508221 ?=     0.000000000465661287307739
[0438707]     0.000000000233576667724752 ?=     0.000000000465661287307739
[0438708]     0.000000000233452655142634 ?=     0.000000000465661287307739
[0438709]     0.000000000233328639805223 ?=     0.000000000465661287307739
[0438710]     0.000000000233204621740631 ?=     0.000000000465661287307739
[0438711]     0.000000000233080600942505 ?=     0.000000000465661287307739
[0438712]     0.000000000232956577433079 ?=     0.000000000465661287307739
[0438713]     0.000000000232832551220136 ?=     0.000000000000000000000000
[0438714]     0.000000000232708522375885 ?=     0.000000000000000000000000
[0438715]     0.000000000232584490849504 ?=     0.000000000000000000000000
[0438716]     0.000000000232460456669951 ?=     0.000000000000000000000000
[0438717]     0.000000000232336419895301 ?=     0.000000000000000000000000
[0438718]     0.000000000232212380517561 ?=     0.000000000000000000000000
[0438719]     0.000000000232088338538687 ?=     0.000000000000000000000000
[0438720]     0.000000000231964293993039 ?=     0.000000000000000000000000
[0438721]     0.000000000231840246897028 ?=     0.000000000000000000000000
[0438722]     0.000000000231716197257748 ?=     0.000000000000000000000000
[0438723]     0.000000000231592145130149 ?=     0.000000000000000000000000
[0438724]     0.000000000231468090476646 ?=     0.000000000000000000000000
[0438730]     0.000000000230723711030715 ?=     0.000000000000000000000000

Might this be the difference between Linear and Minimum Phase merges ???
 
Last edited:
Actually, it seems like there is an integer step somewhere in the DRC-FIR version.
0.000000000465661287307739 = 2^-31, that's what you get if you use 32-bit signed integers and then convert to floats in the range -1.0 .. 1.0.

Thanks for the tip.

I will check both source FIRs for similar behavior as inspect the source that merged them. One source is from DRC-FIR and one is from RePhase.
 
Actually, it seems like there is an integer step somewhere in the DRC-FIR version.
0.000000000465661287307739 = 2^-31, that's what you get if you use 32-bit signed integers and then convert to floats in the range -1.0 .. 1.0.
It appears to come from using SoX to convert between raw PCM and WAV.

SoX should ideally just add/remove a header when switching between the 2 without changing sample rate, bits, format, etc.
 
IMO for SoX a file conversion is like any other chain - input -> processing ->output, where the processing stage applies no effects. The SoX internal format is int32.
"... The SoX internal format is int32. ..." :(

Int32 is losing precision.

Time to file a bug/enhancement report to bring SoX out of the 90's.

I am writing my own code to switch between WAV and PCM that just strips the headers and meta-data footers to convert to PCM and adds a WAV header to convert to WAV so the raw data is left unscathed for when I don't have to change anything else.
 
Int32 is losing precision.

Time to file a bug/enhancement report to bring SoX out of the 90's.
I have been using SoX for over a decade and never had a usecase where int32 would not suffice. Your case is extremely special, IMO.

I am afraid changing the internal inter-effect format would be a very large change (effects use other formats internally as needed, int32 is for passing samples (type sample_t)). You can file a report, but I would expect a response of the kind "patch welcome" :)
 
I have been using SoX for over a decade and never had a usecase where int32 would not suffice. Your case is extremely special, IMO.

I am afraid changing the internal inter-effect format would be a very large change (effects use other formats internally as needed, int32 is for passing samples (type sample_t)). You can file a report, but I would expect a response of the kind "patch welcome" :)
f64 in general is not "extremely special". It is a standard numerical format and more audio tools are going all 64-bit (including CamillaDSP, JRMC, and RePhase).

Henrik was able to catch the issue by just looking at a partial hexdump of an IR.

I plan to download the code and take a look at how much work is involved and have no problem issuing a patch. Acceptance of the patch is another thing. DRC-FIR uses #ifdef/#define types to swap between f32 and f64 builds. SoX should be able to do similar especially since they offer misleading f64 "pseduo-support". False advertising.

When converting between WAV and PCM and back (with no other changes), the PCM payload data should not be touched, but it is. No different than switching between WAV, FLAC and back to WAV. The audio data should remain the same (sans the meta data tags, headers and footers). That I consider a bug. Correct functionality for that conversion could be archived with int8 code (which is what I am using in the converter app I just wrote for 16, 32 and 64-bit payload formats).
 
I have just started using Camilladsp, intended use is to perform crossover for active 3-way replacing a miniDSP miniSHARC DSP. Experiments so far all good using Moode and front-ending wit squeezelite, gaining a reasonable understanding of the SW.
However I need to be able to feed Camilladsp with the frequency sweep out of REW running on my laptop. I am trying to get Camilladsp to take input from a USB ADC (cheap headphone/mic adapter) but not successful.

Here is the output from arecord for the USB device

Code:
**** List of CAPTURE Hardware Devices ****
card 3: Device [USB Audio Device], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

Code:
Recording WAVE '/dev/null' : Unsigned 8 bit, Rate 8000 Hz, Mono
HW Params of device "hw:Device":
--------------------
ACCESS:  MMAP_INTERLEAVED RW_INTERLEAVED
FORMAT:  S16_LE
SUBFORMAT:  STD
SAMPLE_BITS: 16
FRAME_BITS: 16
CHANNELS: 1
RATE: [44100 48000]
PERIOD_TIME: [1000 5944309)
PERIOD_SIZE: [45 262144]
PERIOD_BYTES: [90 524288]
PERIODS: [2 1024]
BUFFER_TIME: [1875 11888617)
BUFFER_SIZE: [90 524288]
BUFFER_BYTES: [180 1048576]
TICK_TIME: ALL
--------------------
arecord: set_params:1339: Sample format non available
Available formats:
- S16_LE

And here is the pipeline I am using

Code:
devices:
  adjust_period: 10
  capture:
    avoid_blocking_read: false
    channels: 1
    device: hw:3,0
    format: S16LE
    retry_on_error: false
    type: Alsa
  capture_samplerate: 0
  chunksize: 1024
  enable_rate_adjust: false
  enable_resampling: false
  playback:
    channels: 2
    device: hw:Headphones,0
    format: S16LE
    type: Alsa
  queuelimit: 1
  rate_measure_interval: 1
  resampler_type: Synchronous
  samplerate: 44100
  silence_threshold: -60
  silence_timeout: 3
  stop_on_rate_change: false
  target_level: 0
filters:
  gain:
    parameters:
      gain: 10
      inverted: false
      mute: false
    type: Gain
  vol:
    parameters:
      ramp_time: 200
    type: Volume
mixers:
  stereo:
    channels:
      in: 1
      out: 2
    mapping:
    - dest: 0
      mute: false
      sources:
      - channel: 0
        gain: 6
        inverted: false
        mute: false
    - dest: 1
      mute: false
      sources:
      - channel: 0
        gain: 6
        inverted: false
        mute: false
pipeline:
- name: stereo
  type: Mixer
- channel: 0
  names:
  - gain
  - vol
  type: Filter
- channel: 1
  names:
  - gain
  - vol
  type: Filter

I get no errors but no output from Camilla. Any help or suggestions greatly appreciated.
 
For just recording a sweep I would guess asynchronous resampling would not be needed. Typically it's possible to run glitch-free for a few minutes without it.

The config looks fine. Does the USB input device have a volume control that is muted or set very low? Can you record anything useful from it with arecord?
 
Managed to use arecord to record and play back the file on my laptop but struggling to do same on the Rpi (records but unable to successfully play back). I used alsamixer to set the volume to 100% on the mic but to no avail.
Coming back to the status, when I switch to the config for squeezelite and activate squeezelite renderer the status goes to "running" and I get music. With the config for the USB input it remains "offline" no matter what I do. Is there some action necessary to get the status to running for the USB input?