Documentation

Chain and presets

Chain v1 is an ordered serial list. Each item uses a unique optional ID, semantic type, enabled flag, channel selector, semantic parameters, and optional asset references. Unknown fields are rejected. EffeTune app presets with pipeline or plugins are a different contract and require the explicit legacy importer.

The importer accepts app presets whose effects form one ordered serial path. It rejects branched or multi-bus routing because flattening it into Chain v1 would change the acoustic result. To migrate such a preset, first move or copy the desired effects into one serial path in the app and export it again, or reproduce the branching in the host around separate Chains.

App presets containing FIR Crossover, 5Band FIR PEQ, Group Delay EQ, Room EQ, or IR Reverb cannot be imported in v0.1: the library drives these five effects from a caller-supplied precomputed impulse-response asset instead of the app’s filter-design parameters, so those parameters have no conversion and the importer rejects the node with an EffectError naming the effect. Construct such an effect directly and supply its asset.

Visual editor to code

The example below exports an asset-free stereo app pipeline, imports it explicitly, and processes a fixed stereo input through the canonical Chain. The import report identifies editor-only metadata that is intentionally discarded; unsupported fields are not silently dropped.

Input exported by the visual editor:

{
  "name": "DSP docs Volume",
  "plugins": [
    {
      "nm": "Volume",
      "en": true,
      "ch": "A",
      "vl": -6
    }
  ]
}

Golden long-format export with Date.now() frozen to 1710000000000:

{
  "name": "DSP docs Volume",
  "pipeline": [
    {
      "name": "Volume",
      "enabled": true,
      "parameters": {
        "vl": -6
      },
      "channel": "A"
    }
  ],
  "timestamp": 1710000000000
}

Import and process that exact golden:

import json
import os
from pathlib import Path

import numpy as np
import effetune as et

exported = json.loads(Path(os.environ["DSP_VISUAL_GOLDEN"]).read_text("utf-8"))
chain, report = et.Chain.from_legacy_preset(exported)
assert isinstance(report.warnings, tuple)
audio = np.ones((2, 256), dtype=np.float32) * np.float32(0.25)
output = chain(audio, 48_000, seed=0)
expected = audio * np.float32(10 ** (-6 / 20))
assert output.shape == audio.shape
assert np.allclose(output, expected, rtol=1e-5, atol=1e-6)