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

I think that the Pipeline view could show the Channel name without having to open the down pointing arrow menu. By just scrolling down one should be able to find the right section to add a newly created filter without any interaction. When dealing with larger configurations this becomes a sewing issue if not... Preferably placed more to the left than my sketch... Leftmost possible I would say...

filter section name.jpg


//
 
I think that the Pipeline view could show the Channel name without having to open the down pointing arrow menu.
It shows all channel names up to 8 channels. When there are more, it takes too much space, so it switches to a more compact view with the indicator bars (which looks wrong in your screenshot, the selected channel should have a bright green little bar, not black) and the dropdown. It's not easy to make something that works equally well for everything from stereo to big interfaces with 30+ channels...
 
CamillaDSP v3.0.1 is out, fixes the issue where some Alsa devices don't resume after being paused. There is also a new gui that fixes a config import bug.
https://github.com/HEnquist/camilladsp/releases/tag/v3.0.1
https://github.com/HEnquist/camillagui-backend/releases/tag/v3.0.3
https://github.com/HEnquist/camilladsp-setupscripts/releases/tag/v3.0.3

The windows 7 build of the camilladsp binary failed. Doesn't really matter since there are no changes in the windows code in 3.0.1. But it means that v3.0.0 is the last published build that supports windows 7.
 
I suspect I may have missed something fundamental - I am trying to control the CDSP volume from an external volume control (a Fosi VOL20):
  • I have manged to control the ALSA Master Volume Control from the device
  • I have set the 'link_volume_control' parameter to 'Master Playback Volume'
  • I have a chain Toslink -> USB (UR23 SPDIF adaptor) -> CDSP -> Fosi DM7 DAC
Does the system have to be a USB Audio Gadget for this to work? Or have I missed something else?

Background: I have CSDP running on a Debian 6.1.0-32-amd64 headless with the latest CDSP version. An extract from my config is attached.

A web search for 'link_volume_control' yields nothing except Henrik's description "Linking volume control to device volume"
Thanks
 

Attachments

Rx USB TOSLINK capture device does not have a volume control. So no go then?
If you have no luck with that approach, it should be possible to use the Fosi VOL20 to control the CDSP master volume directly. There's an example script here: https://www.diyaudio.com/community/...overs-room-correction-etc.349818/post-7915438

I have mine setup using an old Logitech Harmony remote configured to send keyboard shortcuts which then trigger the script. As you're using a headless system the best way to translate the VOL20's commands to a script might be via evdev, there's a python library here: https://python-evdev.readthedocs.io/en/latest/
 
@Roxymoron: IMO the volume functionality needs to be explained, to avoid confusion.

CDSP can control volume internally, as part of its DSP. The volume control is well-behaved, ramped, can be linked to loudness DSP, etc. The end result of the plain internal volume (when not linked to loudness) is still "just" a division of all samples by a fixed number, and audibility is identical to any other digital volume control in the chain.

Now this volume feature needs to be controlled during CDSP operation. There are several ways:

One option is the CDSP websocket server API which has a function for volume. That's what the python scripts utilize, via the python camilladsp library which implements the websocket client to the CDSP websocket server API.

Another (specialty) option to control the CDSP volume is when CDSP captures from USB audio gadget. That alsa device has a USB UAC2 device on the other side and this UAC2 device can present a volume control feature to the USB host. As a result the USB host can use this "hardware" USB volume control itself. E.g. the mpd config options mixer_type=hardware + mixer_device + mixer_index https://linux.die.net/man/5/mpd.conf . When user asks MPD to change volume (via some mpd client), mpd will send the request (via alsa layer + UAC2 driver) to the UAC2 device, and UAC2 gadget driver will instruct its alsa-device side to change value of its mixer element https://github.com/torvalds/linux/b...ers/usb/gadget/function/u_audio.c#L1363-L1392. If CDSP is configured with "link_volume_control" config string and it finds this control element name among the capture device controls, it will subscribe to changes of values of this element, track its value, and adjust its internal volume feature accordingly. That's why the config name starts with link_ - it really links CDSP to that ctl element.

In your case you have a USB HID volume knob which produces standard multimedia key events when rotating (just like multimedia keys on many PC keyboards). Therefore you need to use the first option - controlling CDSP via websockets. The @fb's post describes just that - monitoring input events for these multimedia keys from your particular input device and running a python script which sends websocket commands to running CDSP to change the volume accordingly. No alsa mixer control is involved here.
 
Last edited:
I'm looking for help, please, if possible. I'm not exactly a Linux/Raspberry Pi noob but I'm failing miserably with Raspberry Pi 5 and HifiBerry DAC8x.

I have Camilla and DAC8x working well in so far as I can play audio from any source through the DAC8x. I can play audio from audio files with Strawberry player through CamillaDSP and out through the DAC8x via an ALSA loopback, also works great.

What I can't seem to work out is how to get Pipewire to connect to the ALSA loopback to play audio from, for example, Firefox browser through CDSP to DAC8x. I've tried a few configurations and haven't found a working one. I should say I've removed PulseAudio from the system entirely, I can re-install if I have to but I don't think that's the problem since I couldn't make it work before I removed it.

I think I'll be directed here
https://github.com/HEnquist/camilladsp-config
but I tried this solution for Pipewire and didn't get any audio output at all afterwards. I suspect the solution will be here somewhere but I don't understand the configuration sufficiently to make suitable alterations. This is my unsuccessful pipewire.conf.
Code:
# Daemon config file for PipeWire version "1.2.7" #
#
# Copy and edit this file in /etc/pipewire for system-wide changes
# or in ~/.config/pipewire for local changes.
#
# It is also possible to place a file with an updated section in
# /etc/pipewire/pipewire.conf.d/ for system-wide changes or in
# ~/.config/pipewire/pipewire.conf.d/ for local changes.
#
#See https://github.com/HEnquist/camilladsp-config/blob/master/README.md for changes I made to this file

context.properties = {
    ## Configure properties in the system.
    #library.name.system                   = support/libspa-support
    #context.data-loop.library.name.system = support/libspa-support
    #support.dbus                          = true
    #link.max-buffers                      = 64
    link.max-buffers                       = 16                       # version < 3 clients can't handle more
    #mem.warn-mlock                        = false
    #mem.allow-mlock                       = true
    #mem.mlock-all                         = false
    #clock.power-of-two-quantum            = true
    #log.level                             = 2
    #cpu.zero.denormals                    = false

    #loop.rt-prio = -1            # -1 = use module-rt prio, 0 disable rt
    #loop.class = data.rt
    #thread.affinity = [ 0 1 ]    # optional array of CPUs
    #context.num-data-loops = 1   # -1 = num-cpus, 0 = no data loops
    #
    #context.data-loops = [
    #    {   loop.rt-prio = -1
    #        loop.class = [ data.rt audio.rt ]
    #        #library.name.system = support/libspa-support
    #        thread.name = data-loop.0
    #        #thread.affinity = [ 0 1 ]    # optional array of CPUs
    #    }
    #]

    core.daemon = true              # listening for socket connections
    core.name   = pipewire-0        # core name and socket name

    ## Properties for the DSP configuration.
    default.clock.rate          = 44100        # H. Enquist addition
    #default.clock.allowed-rates = [ 48000 ]
    #default.clock.quantum       = 1024
    #default.clock.min-quantum   = 32
    #default.clock.max-quantum   = 2048
    #default.clock.quantum-limit = 8192
    #default.clock.quantum-floor = 4
    #default.video.width         = 640
    #default.video.height        = 480
    #default.video.rate.num      = 25
    #default.video.rate.denom    = 1
    #
    #settings.check-quantum      = false
    #settings.check-rate         = false

    # keys checked below to disable module loading
    module.x11.bell = true
    # enables autoloading of access module, when disabled an alternative
    # access module needs to be loaded.
    module.access = true
    # enables autoloading of module-jackdbus-detect
    module.jackdbus-detect = true
}

context.properties.rules = [
    {   matches = [ { cpu.vm.name = !null } ]
        actions = {
            update-props = {
                # These overrides are only applied when running in a vm.
                default.clock.min-quantum = 1024
        }
        }
    }
]

context.spa-libs = {
    #<factory-name regex> = <library-name>
    #
    # Used to find spa factory names. It maps an spa factory name
    # regular expression to a library name that should contain
    # that factory.
    #
    audio.convert.* = audioconvert/libspa-audioconvert
    avb.*           = avb/libspa-avb
    api.alsa.*      = alsa/libspa-alsa
    api.v4l2.*      = v4l2/libspa-v4l2
    api.libcamera.* = libcamera/libspa-libcamera
    api.bluez5.*    = bluez5/libspa-bluez5
    api.vulkan.*    = vulkan/libspa-vulkan
    api.jack.*      = jack/libspa-jack
    support.*       = support/libspa-support
    video.convert.* = videoconvert/libspa-videoconvert
    #videotestsrc   = videotestsrc/libspa-videotestsrc
    #audiotestsrc   = audiotestsrc/libspa-audiotestsrc
}

context.modules = [
    #{ name = <module-name>
    #    ( args  = { <key> = <value> ... } )
    #    ( flags = [ ( ifexists ) ( nofail ) ] )
    #    ( condition = [ { <key> = <value> ... } ... ] )
    #}
    #
    # Loads a module with the given parameters.
    # If ifexists is given, the module is ignored when it is not found.
    # If nofail is given, module initialization failures are ignored.
    # If condition is given, the module is loaded only when the context
    # properties all match the match rules.
    #

    # Uses realtime scheduling to boost the audio thread priorities. This uses
    # RTKit if the user doesn't have permission to use regular realtime
    # scheduling. You can also clamp utilisation values to improve scheduling
    # on embedded and heterogeneous systems, e.g. Arm big.LITTLE devices.
    { name = libpipewire-module-rt
        args = {
            nice.level    = -11
            rt.prio       = 88
            #rt.time.soft = -1
            #rt.time.hard = -1
            #uclamp.min = 0
            #uclamp.max = 1024
        }
        flags = [ ifexists nofail ]
    }

    # The native communication protocol.
    { name = libpipewire-module-protocol-native
        args = {
            # List of server Unix sockets, and optionally permissions
            #sockets = [ { name = "pipewire-0" }, { name = "pipewire-0-manager" } ]
        }
    }

    # The profile module. Allows application to access profiler
    # and performance data. It provides an interface that is used
    # by pw-top and pw-profiler.
    { name = libpipewire-module-profiler }

    # Allows applications to create metadata objects. It creates
    # a factory for Metadata objects.
    { name = libpipewire-module-metadata }

    # Creates a factory for making devices that run in the
    # context of the PipeWire server.
    { name = libpipewire-module-spa-device-factory }

    # Creates a factory for making nodes that run in the
    # context of the PipeWire server.
    { name = libpipewire-module-spa-node-factory }

    # Allows creating nodes that run in the context of the
    # client. Is used by all clients that want to provide
    # data to PipeWire.
    { name = libpipewire-module-client-node }

    # Allows creating devices that run in the context of the
    # client. Is used by the session manager.
    { name = libpipewire-module-client-device }

    # The portal module monitors the PID of the portal process
    # and tags connections with the same PID as portal
    # connections.
    { name = libpipewire-module-portal
        flags = [ ifexists nofail ]
    }

    # The access module can perform access checks and block
    # new clients.
    { name = libpipewire-module-access
        args = {
            # Socket-specific access permissions
            #access.socket = { pipewire-0 = "default", pipewire-0-manager = "unrestricted" }

            # Deprecated legacy mode (not socket-based),
            # for now enabled by default if access.socket is not specified
            #access.legacy = true
        }
        condition = [ { module.access = true } ]
    }

    # Makes a factory for wrapping nodes in an adapter with a
    # converter and resampler.
    { name = libpipewire-module-adapter }

    # Makes a factory for creating links between ports.
    { name = libpipewire-module-link-factory }

    # Provides factories to make session manager objects.
    { name = libpipewire-module-session-manager }

    # Use libcanberra to play X11 Bell
    { name = libpipewire-module-x11-bell
        args = {
            #sink.name = "@DEFAULT_SINK@"
            #sample.name = "bell-window-system"
            #x11.display = null
            #x11.xauthority = null
        }
        flags = [ ifexists nofail ]
        condition = [ { module.x11.bell = true } ]
    }
    { name = libpipewire-module-jackdbus-detect
        args = {
            #jack.library     = libjack.so.0
            #jack.server      = null
            #jack.client-name = PipeWire
            #jack.connect     = true
            #tunnel.mode      = duplex  # source|sink|duplex
            source.props = {
                #audio.channels = 2
        #midi.ports = 1
                #audio.position = [ FL FR ]
                # extra sink properties
            }
            sink.props = {
                #audio.channels = 2
        #midi.ports = 1
                #audio.position = [ FL FR ]
                # extra sink properties
            }
        }
        flags = [ ifexists nofail ]
        condition = [ { module.jackdbus-detect = true } ]
    }
]

context.objects = [
    #{ factory = <factory-name>
    #    ( args  = { <key> = <value> ... } )
    #    ( flags = [ ( nofail ) ] )
    #    ( condition = [ { <key> = <value> ... } ... ] )
    #}
    #
    # Creates an object from a PipeWire factory with the given parameters.
    # If nofail is given, errors are ignored (and no object is created).
    # If condition is given, the object is created only when the context properties
    # all match the match rules.
    #
    # Following block of code suggested H. Enquist
{   factory = adapter
        args = {
            factory.name            = api.alsa.pcm.sink
            node.name               = "alsa-sink"
            node.description        = "Alsa Loopback"
            media.class             = "Audio/Sink"
            api.alsa.path           = "hw:Loopback,0,0"
            #api.alsa.period-size   = 1024
            #api.alsa.headroom      = 0
            #api.alsa.disable-mmap  = false
            #api.alsa.disable-batch = false
            audio.format           = "S32LE"
            audio.rate             = 44100
            audio.channels         = 2
            #audio.position         = "FL,FR"
        }
    }
    # Ends H. Enquist code

    #{ factory = spa-node-factory   args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc "Spa:Pod:Object:Param:Props:patternType" = 1 } }
    #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] }
    #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } }
    #{ factory = spa-node-factory   args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } }
    #{ factory = adapter            args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc } }
    #{ factory = spa-node-factory   args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } }

    # A default dummy driver. This handles nodes marked with the "node.always-process"
    # property when no other driver is currently active. JACK clients need this.
    { factory = spa-node-factory
        args = {
            factory.name    = support.node.driver
            node.name       = Dummy-Driver
            node.group      = pipewire.dummy
            node.sync-group  = sync.dummy
            priority.driver = 200000
            #clock.id       = monotonic # realtime | tai | monotonic-raw | boottime
            #clock.name     = "clock.system.monotonic"
        }
    }
    { factory = spa-node-factory
        args = {
            factory.name    = support.node.driver
            node.name       = Freewheel-Driver
            priority.driver = 190000
            node.group      = pipewire.freewheel
            node.sync-group  = sync.dummy
            node.freewheel  = true
            #freewheel.wait = 10
        }
    }

    # This creates a new Source node. It will have input ports
    # that you can link, to provide audio for this source.
    #{ factory = adapter
    #    args = {
    #        factory.name     = support.null-audio-sink
    #        node.name        = "my-mic"
    #        node.description = "Microphone"
    #        media.class      = "Audio/Source/Virtual"
    #        audio.position   = "FL,FR"
    #        monitor.passthrough = true
    #    }
    #}

    # This creates a single PCM source device for the given
    # alsa device path hw:0. You can change source to sink
    # to make a sink in the same way.
    #{ factory = adapter
    #    args = {
    #        factory.name           = api.alsa.pcm.source
    #        node.name              = "alsa-source"
    #        node.description       = "PCM Source"
    #        media.class            = "Audio/Source"
    #        api.alsa.path          = "hw:0"
    #        api.alsa.period-size   = 1024
    #        api.alsa.headroom      = 0
    #        api.alsa.disable-mmap  = false
    #        api.alsa.disable-batch = false
    #        audio.format           = "S16LE"
    #        audio.rate             = 48000
    #        audio.channels         = 2
    #        audio.position         = "FL,FR"
    #    }
    #}

    # Use the metadata factory to create metadata and some default values.
    #{ factory = metadata
    #    args = {
    #        metadata.name = my-metadata
    #        metadata.values = [
    #            { key = default.audio.sink   value = { name = somesink } }
    #            { key = default.audio.source value = { name = somesource } }
    #        ]
    #    }
    #}
]

context.exec = [
    #{   path = <program-name>
    #    ( args = "<arguments>" | [ <arg1> <arg2> ... ] )
    #    ( condition = [ { <key> = <value> ... } ... ] )
    #}
    #
    # Execute the given program with arguments.
    # If condition is given, the program is executed only when the context
    # properties all match the match rules.
    #
    # You can optionally start the session manager here,
    # but it is better to start it as a systemd service.
    # Run the session manager with -h for options.
    #
    #{ path = "/usr/bin/pipewire-media-session" args = ""
    #  condition = [ { exec.session-manager = null } { exec.session-manager = true } ] }
    #
    # You can optionally start the pulseaudio-server here as well
    # but it is better to start it as a systemd service.
    # It can be interesting to start another daemon here that listens
    # on another address with the -a option (eg. -a tcp:4713).
    #
    #{ path = "/usr/bin/pipewire" args = [ "-c" "pipewire-pulse.conf" ]
    #  condition = [ { exec.pipewire-pulse = null } { exec.pipewire-pulse = true } ] }
]