Skip to content

AudioFX Plugin API

mzuelch edited this page Jan 25, 2026 · 2 revisions

This page documents the stable interface for post-processing effects.

Source pointers

  • patchbay_desktop_gui/audiofx/base.py – plugin + parameter schema
  • patchbay_desktop_gui/audiofx/manager.py – discovery loader
  • patchbay_desktop_gui/gui/main_window.py – plugin search paths + persistence layout
  • plugins/*.py – built-in plugins + _template_gain.py

1) Plugin contract

Interface overview (UML-style)

classDiagram
  class ParamKind {
    <<enum>>
    FLOAT
    INT
    BOOL
    CHOICE
  }

  class ParamSpec {
    +str param_id
    +str label
    +ParamKind kind
    +Any default
    +float min_value
    +float max_value
    +float step
    +str fmt
    +list~str~ choices
  }

  class AudioEffectPluginBase {
    +str plugin_id
    +str display_name
    +List~ParamSpec~ params
    +apply(audio, sample_rate, params) np.ndarray
  }

  class PluginLoadError {
    +str path
    +str message
  }

  class AudioFxManager {
    +Dict~str,AudioEffectPluginBase~ plugins
    +List~PluginLoadError~ load_errors
    +register(plugin, source)
    +discover(user_dirs)
    +list_plugins()
  }

  ParamSpec --> ParamKind
  AudioEffectPluginBase --> ParamSpec
  AudioFxManager o--> AudioEffectPluginBase
  AudioFxManager o--> PluginLoadError
Loading

This reflects the actual code in patchbay_desktop_gui/audiofx/base.py and patchbay_desktop_gui/audiofx/manager.py.

A plugin is a single .py file that exports a module-level variable:

  • PLUGIN: an instance of a subclass of AudioEffectPluginBase

Example:

import numpy as np
from typing import Any, Dict
from patchbay_desktop_gui.audiofx.base import AudioEffectPluginBase, ParamSpec

class Gain(AudioEffectPluginBase):
    plugin_id = "gain"
    display_name = "Gain"
    params = [
        ParamSpec.float("gain_db", "Gain (dB)", default=0.0, min_value=-24.0, max_value=24.0, step=0.5, fmt="%.1f"),
    ]

    def apply(self, audio: np.ndarray, sample_rate: int, params: Dict[str, Any]) -> np.ndarray:
        g_db = float(params.get("gain_db", 0.0))
        g = 10.0 ** (g_db / 20.0)
        return (audio.astype(np.float32, copy=False) * np.float32(g)).astype(np.float32, copy=False)

PLUGIN = Gain()

Key requirements

  • plugin_id must be unique across all loaded plugins.
  • apply(...) must be deterministic and must not modify its input array in place.
  • Audio is passed as float PCM np.ndarray with shape (N, C).

2) Parameter schema (ParamSpec)

The GUI builds an editor from params: list[ParamSpec].

Supported kinds (ParamKind):

  • float, int, bool, choice

Best practice:

  • Keep param_id stable forever (settings persistence relies on it)
  • Provide min/max/step for numeric parameters to make UI editing safe
  • Prefer human-friendly labels and sensible defaults

3) Discovery and isolation

Plugins are loaded from files and imported under a unique module name to avoid clashes:

  • importlib.util.spec_from_file_location(...)
  • unique module name via UUID (no shadowing of third-party packages)

Files starting with _ are ignored.

Duplicate plugin_id values are rejected (first plugin wins; duplicates are recorded as load errors).


4) Plugin search paths

The DearPyGui frontend searches:

  1. %APPDATA%\CatSynth\PATCHBAY\plugins
  2. ./plugins (current working directory)
  3. plugins/ shipped with the source tree / installed package (if present)
  4. extra directories from env var PATCHBAY_AUDIOFX_PLUGIN_DIRS (Windows: semicolon-separated)

5) Parameter persistence (per stream)

Settings are stored under:

settings["audiofx"]["input" | "target" | "residual"][plugin_id][param_id] = value

This means you can use different settings per stream (e.g., normalize input but compress target).


6) Debugging plugin load issues

When a plugin fails to load, the manager records a PluginLoadError(path, message).

Common causes:

  • PLUGIN variable missing
  • PLUGIN not an instance of AudioEffectPluginBase
  • import error (missing dependency, syntax error)

Last updated: 2026-01-24

Clone this wiki locally