PipeWire: PipeWire Module: Filter-Chain

PipeWire 0.3.51

PipeWire Module: Filter-Chain

The filter-chain allows you to create an arbitrary processing graph from LADSPA, LV2 and builtin filters.

This filter can be made into a virtual sink/source or between any 2 nodes in the graph.

The filter chain is built with 2 streams, a capture stream providing the input to the filter chain and a playback stream sending out the filtered stream to the next nodes in the graph.

Because both ends of the filter-chain are built with streams, the session manager can manage the configuration and connection with the sinks and sources automatically.

## Module Options

- `node.description`: a human readable name for the filter chain
- `filter.graph = []`: a description of the filter graph to run, see below
- `capture.props = {}`: properties to be passed to the input stream
- `playback.props = {}`: properties to be passed to the output stream

## Filter graph description

The general structure of the graph description is as follows:

filter.graph = {
nodes = [
{
type = <ladspa | lv2 | builtin>
name = <name>
plugin = <plugin>
label = <label>
config = {
<configkey> = <value> ...
}
control = {
<controlname|controlindex> = <value> ...
}
}
...
]
links = [
{ output = <portname> input = <portname> }
...
]
inputs = [ <portname> ... ]
outputs = [ <portname> ... ]
}

### Nodes

Nodes describe the processing filters in the graph. Use a tool like lv2ls or listplugins to get a list of available plugins, labels and the port names.

- `type` is one of `ladspa`, `lv2` or `builtin`
- `name` is the name for this node, you might need this later to refer to this node and its ports when setting controls or making links.
- `plugin` is the type specific plugin name.
  - For LADSPA plugins it will append `.so` to find the shared object with that name in the LADSPA plugin path.
  - For LV2, this is the plugin URI obtained with lv2ls.
  - For builtin this is ignored
- `label` is the type specific filter inside the plugin.
  - For LADSPA this is the label
  - For LV2 this is unused
  - For builtin this is the name of the filter to use
- `config` contains a filter specific configuration section. The convolver plugin needs this.
- `control` contains the initial values for the control ports of the filter.

### Links

Links can be made between ports of nodes. The `portname` is given as `<node_name>:<port_name>`.

You can tee the output of filters to multiple other filters. You need to use a mixer if you want the output of multiple filters to go into one filter input port.

links can be omited when the graph has just 1 filter.

### Inputs and Outputs

These are the entry and exit ports into the graph definition. Their number defines the number of channels used by the filter-chain.

The `<portname>` can be `null` when a channel is to be ignored.

Each input/output in the graph can only be linked to one filter input/output. You need to use the copy builtin filter if the stream signal needs to be routed to multiple filters. You need to use the mixer builtin plugin if multiple graph outputs need to go to one output stream.

inputs and outputs can be omitted, in which case the filter-chain will use all inputs from the first filter and all outputs from the last filter node. The graph will then be duplicated as many times to match the number of input/output channels of the streams.

## Builtin filters

There are some useful builtin filters available. You select them with the label of the filter node.

### Mixer

Use the `mixer` plugin if you have multiple input signals that need to be mixed together.

The mixer plugin has up to 8 input ports labeled "In 1" to "In 8" and each with a gain control labeled "Gain 1" to "Gain 8". There is an output port labeled "Out". Unused input ports will be ignoded and not cause overhead.

### Copy

Use the `copy` plugin if you need to copy a stream input signal to multiple filters.

It has one input port "In" and one output port "Out".

### Biquads

Biquads can be used to do all kinds of filtering. They are also used when creating equalizers.

All biquad filters have an input port "In" and an output port "Out". They have a "Freq", "Q" and "Gain" control. Their meaning depends on the particular biquad that is used. The following labels can be used:

- `bq_lowpass` a lowpass filter.
- `bq_highpass` a highpass filter.
- `bq_bandpass` a bandpass filter.
- `bq_lowshelf` a low shelf filter.
- `bq_highshelf` a high shelf filter.
- `bq_peaking` a peaking filter.
- `bq_notch` a notch filter.
- `bq_allpass` an allpass filter.

### Convolver

The convolver can be used to apply an impulse response to a signal. It is usually used for reverbs or virtual surround. The convolver is implemented with a fast FFT implementation.

The convolver has an input port "In" and an output port "Out". It requires a config section in the node declaration in this format:

filter.graph = {
nodes = [
{
type = builtin
name = ...
label = convolver
config = {
blocksize = ...
tailsize = ...
gain = ...
delay = ...
filename = ...
offset = ...
length = ...
channel = ...
}
...
}
}
...
}

- `blocksize` specifies the size of the blocks to use in the FFT. It is a value between 64 and 256. When not specified, this value is computed automatically from the number of samples in the file.
- `tailsize` specifies the size of the tail blocks to use in the FFT.
- `gain` the overall gain to apply to the IR file.
- `delay` The extra delay (in samples) to add to the IR.
- `filename` The IR to load or create. Possible values are:
  - `/hilbert` creates a hilbert function that can be used to phase shift the signal by +/-90 degrees. The `length` will be used as the number of coefficients.
  - `/dirac` creates a Dirac function that can be used as gain.
  - A filename to load as the IR. This needs to be a file format supported by sndfile.
- `offset` The sample offset in the file as the start of the IR.
- `length` The number of samples to use as the IR.
- `channel` The channel to use from the file as the IR.

### Delay

The delay can be used to delay a signal in time.

The delay has an input port "In" and an output port "Out". It also has a "Delay (s)" control port. It requires a config section in the node declaration in this format:

filter.graph = {
nodes = [
{
type = builtin
name = ...
label = delay
config = {
"max-delay" = ...
}
control = {
"Delay (s)" = ...
}
...
}
}
...
}

- `max-delay` the maximum delay in seconds. The "Delay (s)" parameter will be clamped to this value.

## General options

Options with well-known behavior. Most options can be added to the global configuration or the individual streams:

- PW_KEY_REMOTE_NAME
- PW_KEY_AUDIO_RATE
- PW_KEY_AUDIO_CHANNELS
- SPA_KEY_AUDIO_POSITION
- PW_KEY_MEDIA_NAME
- PW_KEY_NODE_LATENCY
- PW_KEY_NODE_DESCRIPTION
- PW_KEY_NODE_GROUP
- PW_KEY_NODE_LINK_GROUP
- PW_KEY_NODE_VIRTUAL

Stream only properties:

- PW_KEY_MEDIA_CLASS
- PW_KEY_NODE_NAME

## Example configuration of a virtual source

This example uses the rnnoise LADSPA plugin to create a new virtual source.

context.modules = [
{ name = libpipewire-module-filter-chain
args = {
node.description = "Noise Canceling source"
media.name = "Noise Canceling source"
filter.graph = {
nodes = [
{
type = ladspa
name = rnnoise
plugin = ladspa/librnnoise_ladspa
label = noise_suppressor_stereo
control = {
"VAD Threshold (%)" 50.0
}
}
]
}
capture.props = {
node.name = "capture.rnnoise_source"
node.passive = true
}
playback.props = {
node.name = "rnnoise_source"
media.class = Audio/Source
}
}
}
]

## Example configuration of a Dolby Surround encoder virtual Sink

This example uses the ladpsa surrounf encoder to encode a 5.1 signal to a stereo Dolby Surround signal.

\code{.unparsed}
context.modules = [
{ name = libpipewire-module-filter-chain
args = {
node.description = "Dolby Surround Sink"
media.name = "Dolby Surround Sink"
filter.graph = {
nodes = [
{
type = builtin
name = mixer
label = mixer
control = { "Gain 1" = 0.5 "Gain 2" = 0.5 }
}
{
type = ladspa
name = enc
plugin = surround_encoder_1401
label = surroundEncoder
}
]
links = [
{ output = "mixer:Out" input = "enc:S" }
]
inputs = [ "enc:L" "enc:R" "enc:C" null "mixer:In 1" "mixer:In 2" ]
outputs = [ "enc:Lt" "enc:Rt" ]
}
capture.props = {
node.name = "effect_input.dolby_surround"
media.class = Audio/Sink
audio.channels = 6
audio.position = [ FL FR FC LFE SL SR ]
}
playback.props = {
node.name = "effect_output.dolby_surround"
node.passive = true
audio.channels = 2
audio.position = [ FL FR ]
}
}
}
]