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

Further to above, the original post was more than 20,000 characters so I had to chop it up.

More information...
This is output of aplay -l
Code:
**** List of PLAYBACK Hardware Devices ****
card 0: Loopback [Loopback], device 0: Loopback PCM [Loopback PCM]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 0: Loopback [Loopback], device 1: Loopback PCM [Loopback PCM]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 1: sndrpihifiberry [snd_rpi_hifiberry_dac8x], device 0: HifiBerry DAC8x HiFi snd-soc-dummy-dai-0 [HifiBerry DAC8x HiFi snd-soc-dummy-dai-0]
  Subdevices: 0/1
  Subdevice #0: subdevice #0

This is wpctl status (The garbage characters are just lines, it outputs as a "tree")
Code:
PipeWire 'pipewire-0' [1.2.7, robert@berryx8, cookie:2639520924]
 â†Clients:
        33. pipewire                            [1.2.7, robert@berryx8, pid:871]
        35. WirePlumber                         [1.2.7, robert@berryx8, pid:870]
        36. WirePlumber [export]                [1.2.7, robert@berryx8, pid:870]
       273. xdg-desktop-portal-wlr              [1.2.7, robert@berryx8, pid:1238]
       274. xdg-desktop-portal                  [1.2.7, robert@berryx8, pid:1186]
       275. unknown                             [1.2.7, robert@berryx8, pid:1078]
       276. wpctl                               [1.2.7, robert@berryx8, pid:132077]

Audio
 â†Devices:
 â      59. Built-in Audio                      [alsa]
 â      60. Built-in Audio                      [alsa]
 â 
 â†Sinks:
 â      65. Built-in Audio Pro                  [vol: 1.00]
 â      66. Built-in Audio Pro 1                [vol: 1.00]
 â  *   69. Built-in Audio Pro                  [vol: 1.00]
 â 
 â†Sink endpoints:
 â 
 â†Sources:
 â  *   67. Built-in Audio Pro                  [vol: 1.00]
 â      68. Built-in Audio Pro 1                [vol: 1.00]
 â 
 â†Source endpoints:
 â 
 â†Streams:

Video
 â†Devices:
 â      42. rpi-hevc-dec                        [v4l2]
 â      43. pispbe                              [v4l2]
 â      44. pispbe                              [v4l2]
 â      45. pispbe                              [v4l2]
 â      46. pispbe                              [v4l2]
 â      47. pispbe                              [v4l2]
 â      48. pispbe                              [v4l2]
 â      49. pispbe                              [v4l2]
 â      50. pispbe                              [v4l2]
 â      51. pispbe                              [v4l2]
 â      52. pispbe                              [v4l2]
 â      53. pispbe                              [v4l2]
 â      54. pispbe                              [v4l2]
 â      55. pispbe                              [v4l2]
 â      56. pispbe                              [v4l2]
 â      57. pispbe                              [v4l2]
 â      58. pispbe                              [v4l2]
 â 
 â†Sinks:
 â 
 â†Sink endpoints:
 â 
 â†Sources:
 â 
 â†Source endpoints:
 â 
 â†Streams:

Settings
 â†Default Configured Node Names:
         0. Audio/Sink    alsa_output.platform-soc_107c000000_sound.pro-output-0
         1. Audio/Source  alsa_input.platform-snd_aloop.0.pro-input-0

pw-cli ls Node (which I hope helps makes sense of the above)
Code:
pw-cli ls Node
    id 30, type PipeWire:Interface:Node/3
         object.serial = "30"
         factory.id = "11"
         priority.driver = "200000"
         node.name = "Dummy-Driver"
    id 31, type PipeWire:Interface:Node/3
         object.serial = "31"
         factory.id = "11"
         priority.driver = "190000"
         node.name = "Freewheel-Driver"
    id 39, type PipeWire:Interface:Node/3
         object.serial = "39"
         factory.id = "11"
         client.id = "36"
         priority.driver = "1"
         node.name = "Midi-Bridge"
         media.class = "Midi/Bridge"
    id 65, type PipeWire:Interface:Node/3
         object.serial = "65"
         object.path = "alsa:acp:Loopback:1:playback"
         factory.id = "19"
         client.id = "36"
         device.id = "59"
         priority.session = "1000"
         priority.driver = "1000"
         node.description = "Built-in Audio Pro"
         node.name = "alsa_output.platform-snd_aloop.0.pro-output-0"
         node.nick = "Loopback PCM"
         media.class = "Audio/Sink"
    id 66, type PipeWire:Interface:Node/3
         object.serial = "66"
         object.path = "alsa:acp:Loopback:2:playback"
         factory.id = "19"
         client.id = "36"
         device.id = "59"
         priority.session = "728"
         priority.driver = "728"
         node.description = "Built-in Audio Pro 1"
         node.name = "alsa_output.platform-snd_aloop.0.pro-output-1"
         node.nick = "Loopback PCM"
         media.class = "Audio/Sink"
    id 67, type PipeWire:Interface:Node/3
         object.serial = "67"
         object.path = "alsa:acp:Loopback:3:capture"
         factory.id = "19"
         client.id = "36"
         device.id = "59"
         priority.session = "2000"
         priority.driver = "2000"
         node.description = "Built-in Audio Pro"
         node.name = "alsa_input.platform-snd_aloop.0.pro-input-0"
         node.nick = "Loopback PCM"
         media.class = "Audio/Source"
    id 68, type PipeWire:Interface:Node/3
         object.serial = "68"
         object.path = "alsa:acp:Loopback:4:capture"
         factory.id = "19"
         client.id = "36"
         device.id = "59"
         priority.session = "1728"
         priority.driver = "1728"
         node.description = "Built-in Audio Pro 1"
         node.name = "alsa_input.platform-snd_aloop.0.pro-input-1"
         node.nick = "Loopback PCM"
         media.class = "Audio/Source"
    id 69, type PipeWire:Interface:Node/3
         object.serial = "69"
         object.path = "alsa:acp:sndrpihifiberry:0:playback"
         factory.id = "19"
         client.id = "36"
         device.id = "60"
         priority.session = "1000"
         priority.driver = "1000"
         node.description = "Built-in Audio Pro"
         node.name = "alsa_output.platform-soc_107c000000_sound.pro-output-0"
         node.nick = "HifiBerry DAC8x HiFi snd-soc-dummy-dai-0"
         media.class = "Audio/Sink"

Hope this post wasn't too long and boring but I've tried to give all relevant info in one go.
 
If I use the pipewire.conf as it is, or with the suggested change to "api.alsa.path = "hw:0,0,0"" it loses all my devices.
"No internal audio devices found" in raspi-config.
The original /usr/share/pipewire/pipewire.conf doesn't have such a line.
 
Last edited:
Simple to do. In gui-config.yml there already are shortcuts for "Treble" and "Bass". Add one more for "Midrange". Here is how to do so:

1. Open your gui-config.yml file. Under "shortcuts", add the following.

- name: "Midrange (dB)"
config_elements:
- path: ["filters", "Midrange", "parameters", "gain"]
range_from: -12
range_to: 12
step: 0.5

Save and exit.

Note: My gui-config.yml is located in /opt/camillagui_backend/_internal/config/. Yours may be different, depending on where you installed the camillagui package.

2. Reload and restart your camillagui.service:

sudo systemctl daemon-reload
sudo systemctl restart camillagui.service

3. In the Filters tab of the GUI, create the filters you want controlled by the shortcuts. Name them "Bass", "Midrange" and "Treble", respectively. Note that the names MUST match the names defined in the respective path statements under shortcuts in the gui-config.yml file.

4. In the Pipleline tab of the GUI, add the filters you just created and assign them to the channels you want them to affect.

5. Select "Apply and save", refresh your browser, and you should have it.

Here is what it looks like:

Shortcuts.png
 
Last edited:
  • Like
Reactions: Wirrunna
Yes.

- name: "Bass (dB)"
config_elements:
- path: ["filters", "Bass", "parameters", "gain"]
range_from: -12
range_to: 12
step: 0.5
- name: "Bass (freq)"
config_elements:
- path: ["filters", "Bass", "parameters", "freq"]
range_from: 10
range_to: 200
step: 1
- name: "Bass (Q)"
config_elements:
- path: ["filters", "Bass", "parameters", "q"]
range_from: 0.1
range_to: 15
step: 0.1

EDIT: You may need to adjust your tabs/spaces above. They get removed when I post.

EDIT 2: I forgot to change "Midrange" to "Bass" in the path statements above. It is corrected now.

Shortcuts.png
 
Last edited:
Thought I'd share for anyone interested in multichannel audio interfaces for use with CamillaDSP: I've been running a Focusrite Scarlett 18i20 gen 4 for about 2 weeks now and it's been excellent. I bought it to replace a Topping DM7 (to get mic input for measurements and rack-mount). I can't tell the difference from a sound quality perspective, and it's been very easy to use (even has on-off click suppression).

It's class compliant so no issues running via ALSA on Linux. There's also a Linux config utility here https://github.com/geoffreybennett/alsa-scarlett-gui (semi-supported by Focusrite!), though I've had trouble getting it running so far - going to upgrade to 6.14 kernel soon to have another go.
 
I recently downloaded a binary copy of camilladsp V3 for my recently-acquired RPi5. Reading through this thread revealed that there are many compiler options if you download and compile the source code: so my question is --- what compiler options were used to create the binaries? I don't want to knock myself out over some weird problem that turns out to be due to the choice of option(s) used to create the binary. For my initial trials I will use my plain-vanilla UCA202 for the ADC/DAC. It's 16-bit with a maximum sampling rate of 48KHz. Not ideal but....it's what I've got.

For now I am after room and speaker equalization for a DML based audio system. If that works I will add a subwoofer to the system, the panels pretty much cut out below 100Hz. Of course, the UCA202 won't be good for that since it only has 2 outputs but I'm taking an incremental approach so I don't spend any more money than absolutely necessary.

Thanks.
Mark
 
I recently downloaded a binary copy of camilladsp V3 for my recently-acquired RPi5.
I followed this tutorial, as have many. It is very good.

https://github.com/mdsimon2/RPi-CamillaDSP

The only difference is that I used Raspberry Pi OS Lite instead of Ubuntu. It is lighter on resources and does not include a lot of packages that Ubuntu installs that you will not need if only using your RPi for DSP.

To install Raspberry Pi OS Lite, in the Raspberry Pi imager, after you select "CHOOSE OS", then select "Raspberry Pi OS (Other)", then select "Raspberry Pi OS Lite (64-bit).
 
  • Like
Reactions: Mark'51
I followed this tutorial, as have many. It is very good.

https://github.com/mdsimon2/RPi-CamillaDSP

The only difference is that I used Raspberry Pi OS Lite instead of Ubuntu. It is lighter on resources and does not include a lot of packages that Ubuntu installs that you will not need if only using your RPi for DSP.

To install Raspberry Pi OS Lite, in the Raspberry Pi imager, after you select "CHOOSE OS", then select "Raspberry Pi OS (Other)", then select "Raspberry Pi OS Lite (64-bit).
I think that tutorial is also using Rpi OS. mdsimon abandoned Ubuntu a time ago if my memory serves me right.
 
  • Like
Reactions: mdsimon2
lick on the link and check it out. Your memory is not serving you correctly or, perhaps, he changed and then changed back, I don't know. 🙂

When I migrated to GitHub (05/2024), I added instructions for Raspberry Pi OS -> https://www.audiosciencereview.com/...amilladsp-tutorial.29656/page-90#post-1983838.

Earlier this year (01/2025) I removed all references to Ubuntu Server and now the tutorial only covers Raspberry Pi OS -> https://www.audiosciencereview.com/...milladsp-tutorial.29656/page-115#post-2211585.

Michael
 
  • Like
Reactions: bambadoo
When I migrated to GitHub (05/2024), I added instructions for Raspberry Pi OS -> https://www.audiosciencereview.com/...amilladsp-tutorial.29656/page-90#post-1983838.

Earlier this year (01/2025) I removed all references to Ubuntu Server and now the tutorial only covers Raspberry Pi OS -> https://www.audiosciencereview.com/...milladsp-tutorial.29656/page-115#post-2211585.

Michael
I now see. The references to Ubuntu is for are using it to run the Imager and to SSH into the Pi, not for the installation of Ubuntu onto the Pi. My mistake.

Ubuntu 2.png
Ubuntu.png
 
  • Like
Reactions: bambadoo
There's probably a far more elegant way to do this, but here's my Python script for converting Multi-Sub Optimizer's JSON export to a YML file that can be imported to CamillaGUI using the Import Config function. In the script you define the name, channel and type of the filters you want in your Camilla config, and the script will attempt to match that to filters in the JSON file. The resulting YML file contains only the filter definitions, ie you will still need to allocate them to a pipeline. CamillaGUI's Import Config function allows you to select which items will get imported to the currently open config, and will overwrite existing filters with the imported ones - no more updating hundreds of text fields by hand if you're experimenting with different MSO results.

Obviously, make sure you have a backup of your existing configs, and verify the results visually before testing on a live system. It should be fairly modifiable to suit other configs and filter types that are supported by both MSO and CDSP, this was just the minimum to get going with my current system.

Example usage: python3 MSO_JSONConverter.py filters.json

Python:
import json
import sys
import os 

output_yaml_file = 'filters.yml'

#Channel to filter name mapping definitions
channel_map = {
   'Mains': ['L', 'R'], #Common filters for mains
   'Mains_L': ['L'], #Specifc filters connected to L (exported JSON has removed -ve delay from common sub and added to Mains invidually rather than Mains common)
   'Sub': ['SL', 'SR'], #Common filters for sub (to add RL, RR to match 4 subs)
   'Sub1': ['SL'], #Individual filters for sub1
   'Sub2': ['SR'], #Individual filters for sub2
   'Sub3': ['RL'], #Individual filters for sub3
   'Sub4': ['RR'], #Individual filters for sub4
}

#Filter type definitions
filter_map = {
   'PeakingEQ': ['  {name}:', '    description: null', '    parameters:', '      freq: {fc}', '      gain: {gain}', '      q: {q}', '      type: Peaking', '    type: Biquad'],
   'ComplexHighPass': ['  {name}:', '    description: null', '    parameters:', '      freq: {fc}', '      order: {order}', '      type: LinkwitzRileyHighpass', '    type: BiquadCombo'],
   'ComplexLowPass': ['  {name}:', '    description: null', '    parameters:', '      freq: {fc}', '      order: {order}', '      type: LinkwitzRileyLowpass', '    type: BiquadCombo'],
   'APF_VarQ': ['  {name}:', '    description: null', '    parameters:', '      freq: {fc}', '      q: {q}', '      type: Allpass', '    type: Biquad'],
   'Delay': ['  {name}:', '    description: null', '    parameters:', '      delay: {delay_val}', '      subsample: false', '      unit: ms', '    type: Delay'],
   'Gain': ['  {name}:', '    description: null', '    parameters:', '      gain: {gain_val}', '      inverted: false', '      mute: false', '      scale: dB', '    type: Gain']
}

#Destination filters to map to
destination_map = { #CDSP filter name: [channel_map, filter_map]
    'Bass_HighPass': ['Mains', 'ComplexHighPass'],
    'Mains_Delay': ['Mains_L', 'Delay'],
    'Sub_Delay': ['Sub', 'Delay'],
    'Sub_Gain': ['Sub', 'Gain'],
    'Sub_LowPass': ['Sub', 'ComplexLowPass'],
    'Sub_EQ_1': ['Sub', 'PeakingEQ'],
    'Sub_EQ_2': ['Sub', 'PeakingEQ'],
    'Sub_EQ_3': ['Sub', 'PeakingEQ'],
    'Sub_EQ_4': ['Sub', 'PeakingEQ'],
    'Sub_EQ_5': ['Sub', 'PeakingEQ'],
    'Sub_EQ_6': ['Sub', 'PeakingEQ'],
    'Sub_EQ_7': ['Sub', 'PeakingEQ'],
    'Sub_EQ_8': ['Sub', 'PeakingEQ'],
    'Sub1_AP_1': ['Sub1', 'APF_VarQ'],
    'Sub1_AP_2': ['Sub1', 'APF_VarQ'],
    'Sub1_EQ_1': ['Sub1', 'PeakingEQ'],
    'Sub1_EQ_2': ['Sub1', 'PeakingEQ'],
    'Sub1_EQ_3': ['Sub1', 'PeakingEQ'],
    'Sub1_EQ_4': ['Sub1', 'PeakingEQ'],
    'Sub1_EQ_5': ['Sub1', 'PeakingEQ'],
    'Sub1_EQ_6': ['Sub1', 'PeakingEQ'],
    'Sub1_EQ_7': ['Sub1', 'PeakingEQ'],
    'Sub1_EQ_8': ['Sub1', 'PeakingEQ'],
    'Sub2_AP_1': ['Sub2', 'APF_VarQ'],
    'Sub2_AP_2': ['Sub2', 'APF_VarQ'],
    'Sub2_EQ_1': ['Sub2', 'PeakingEQ'],
    'Sub2_EQ_2': ['Sub2', 'PeakingEQ'],
    'Sub2_EQ_3': ['Sub2', 'PeakingEQ'],
    'Sub2_EQ_4': ['Sub2', 'PeakingEQ'],
    'Sub2_EQ_5': ['Sub2', 'PeakingEQ'],
    'Sub2_EQ_6': ['Sub2', 'PeakingEQ'],
    'Sub2_EQ_7': ['Sub2', 'PeakingEQ'],
    'Sub2_EQ_8': ['Sub2', 'PeakingEQ'],
    'Sub3_AP_1': ['Sub3', 'APF_VarQ'],
    'Sub3_AP_2': ['Sub3', 'APF_VarQ'],
    'Sub3_EQ_1': ['Sub3', 'PeakingEQ'],
    'Sub3_EQ_2': ['Sub3', 'PeakingEQ'],
    'Sub3_EQ_3': ['Sub3', 'PeakingEQ'],
    'Sub3_EQ_4': ['Sub3', 'PeakingEQ'],
    'Sub3_EQ_5': ['Sub3', 'PeakingEQ'],
    'Sub3_EQ_6': ['Sub3', 'PeakingEQ'],
    'Sub3_EQ_7': ['Sub3', 'PeakingEQ'],
    'Sub3_EQ_8': ['Sub3', 'PeakingEQ'],
    'Sub4_AP_1': ['Sub4', 'APF_VarQ'],
    'Sub4_AP_2': ['Sub4', 'APF_VarQ'],
    'Sub4_EQ_1': ['Sub4', 'PeakingEQ'],
    'Sub4_EQ_2': ['Sub4', 'PeakingEQ'],
    'Sub4_EQ_3': ['Sub4', 'PeakingEQ'],
    'Sub4_EQ_4': ['Sub4', 'PeakingEQ'],
    'Sub4_EQ_5': ['Sub4', 'PeakingEQ'],
    'Sub4_EQ_6': ['Sub4', 'PeakingEQ'],
    'Sub4_EQ_7': ['Sub4', 'PeakingEQ'],
    'Sub4_EQ_8': ['Sub4', 'PeakingEQ'],
    'Mains_EQ_1': ['Mains', 'PeakingEQ'],
    'Mains_EQ_2': ['Mains', 'PeakingEQ'],
    'Mains_EQ_3': ['Mains', 'PeakingEQ'],
    'Mains_EQ_4': ['Mains', 'PeakingEQ'],
    'Mains_EQ_5': ['Mains', 'PeakingEQ'],
    'Mains_EQ_6': ['Mains', 'PeakingEQ'],
    'Mains_EQ_7': ['Mains', 'PeakingEQ'],
    'Mains_EQ_8': ['Mains', 'PeakingEQ'],
}

manual_checks = {
   'Sub_Delay': 'If Sub_Delay was negative in MSO, this has been exported by MSO as positive delay to Mains instead'
}

def load_json_file(file_path):
  if not os.path.exists(file_path):
      print(f"Error: File not found at '{file_path}'")
      return None

  try:
    # Open the JSON file for reading ('r' mode)
    # Using 'with' ensures the file is automatically closed even if errors occur
    with open(file_path, 'r', encoding='utf-8') as f:
      # Load the JSON data from the file object
      data = json.load(f)
      print(f"Successfully loaded JSON data from '{file_path}'")
      return data
  except json.JSONDecodeError as e:
    # Handle errors if the file is not valid JSON
    print(f"Error decoding JSON from '{file_path}': {e}")
    return None
  except IOError as e:
    # Handle potential file reading errors (e.g., permissions)
    print(f"Error reading file '{file_path}': {e}")
    return None
  except Exception as e:
    print(f"An unexpected error occurred: {e}")
    return None

def create_filter_text(dest_type, name, fc, q, gain, gain_val, delay_val, order):
    template_lines = filter_map.get(dest_type)

    if template_lines is None:
        return f"# Error: No template found in filter_map for type '{dest_type}'"

    # Prepare substitution dictionary. Use 'N/A' or 'null' for missing values.
    # Ensure keys here match the placeholders in filter_map strings exactly.
    subs = {
        'name': name if name is not None else 'null',
        'fc': fc if fc is not None else 'null',
        'q': q if q is not None else 'null',
        'gain': gain if gain is not None else 'null',
        'gain_val': gain_val if gain_val is not None else 'null',
        'delay_val': delay_val if delay_val is not None else 'null',
        'order': order if order is not None else 'null' # Make sure Order is expected for the type
    }

    output_lines = []
    if isinstance(template_lines, list) and len(template_lines) > 1:
        for line_template in template_lines:
            try:
                # Use str.format() with keyword arguments (**subs)
                output_lines.append(line_template.format(**subs))
            except KeyError as e:
                # Handle cases where a placeholder exists in the template
                # but wasn't provided in the 'subs' dictionary.
                output_lines.append(f"# Warning: Missing value for placeholder {e} in template line: '{line_template}'")
            except Exception as e:
                 output_lines.append(f"# Error formatting line: '{line_template}' - {e}")
    else:
        return f"# Error: Invalid template format for type '{dest_type}' in filter_map."

    return "\n".join(output_lines)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Error: No JSON file specified.", file=sys.stderr)
        print(f"Usage: python {sys.argv[0]} <path_to_json_file>", file=sys.stderr)
        sys.exit(1)

    json_file_to_load = sys.argv[1]
    loaded_data = load_json_file(json_file_to_load)

    try:
        with open(output_yaml_file, 'w', encoding='utf-8') as outfile:
            print(f"Generating output YAML file: '{output_yaml_file}'", file=sys.stderr)

            if loaded_data is not None:
                if isinstance(loaded_data, dict):
                    filters = loaded_data.get('mso_filters', [])
                    if filters:
                        print('filters:', file=outfile)
                        for key in destination_map:
                            dest_channel = channel_map.get(destination_map[key][0], None)
                            dest_type = destination_map[key][1]

                            for filter in filters:
                                filter_channel = filter.get('chans', None)
                                filter_type = filter.get('_type', None)
                                
                                if filter_channel == dest_channel and filter_type == dest_type: #
                                    name = key
                                    fc = filter.get('fc', None)
                                    q = filter.get('q', None)
                                    gain = filter.get('gain', None)
                                    gain_val = filter.get('gain_val', None)
                                    delay_val = filter.get('delay_val', None)
                                    order = filter.get('order', None)

                                    print(create_filter_text(dest_type, name, fc, q, gain, gain_val, delay_val, order), file=outfile)
                                    filter['chans'] = ['N/A'] #prevent this filter from being matched again
                                    break
                else:
                    print('No mso_filters key found in the JSON data.')

            if manual_checks is not None:
                print("\n***** Note the following manual checks to perform: *****")
                for key in manual_checks:
                    print(manual_checks[key])

            else:
                print("\nFailed to load JSON data.")
                sys.exit(1)

    except IOError as e:
        print(f"Error: Could not open or write to output file '{output_yaml_file}': {e}", file=sys.stderr)
        sys.exit(1)
    except Exception as e:
        print(f"An unexpected error occurred during file writing: {e}", file=sys.stderr)
        sys.exit(1)
 
  • Like
Reactions: HenrikEnquist
Hello

I've been struggling for about a week with a change in filter visualization. I am designing fir filters which until recently worked well.
I am using Moode 9.2.5 with Camilladsp 3.0.0 and REW 5.20.14. It is possible that this behaviour is due to Moode/Camilladsp upgrades but seems unlikely.

Plotting of wav conv files in camilladsp does not match my own plots of my filters or REW. If I create a filter as wav, I can import it into REW and it plots correctly as per my REW filter design. When I plot the same filter in camilladsp, the Fc of the filters is shifted. At 44100, it shifts about double the Fc of the designed filter. Other Fs results in different shift amounts in camilladsp. There may be a complex relationship but I dont know what it is. I also took an old Rephase filter at 48000 and imported that into REW and it plots correctly. But in Camilladsp it shows shifted Fc's. I also exported one of my filters from REW try to rule out that it is my wav generation that is causing the problem. It too shows Fc shifts. I have analyzed my filter odd taps symmetry, impulse alignment, and no joy. There is one more issue I am experiencing with REW which may or may not be related. When I inport a filter that I create into REW, the group delay overlay plot does not appear for many, but not all filters. I have to go into the left measurement files selector and once I click on the filters it immediately shows in the GD overlay. I havent been able to figure out this one either. Does anyone have any suggestions on solving the plotting?
Cheers.



1745778785256.png



1745780133423.png
 
What is the sample rate set to in the devices tab? The filter plot uses that and ignores what the file was made for (same as the CamillaDSP engine does). For raw files that doesn't cause any trouble since they don't contain any info about sample rate or anything, but it can be a little confusing for wavs, where the sample rate is ignored.