Skip to main content

Batch-render Serum 1 (.fxp) and Serum 2 (.SerumPreset) presets to audio files via DawDreamer.

Project description

serum-render

PyPI Python versions tests license

Batch-render Serum presets to audio files. Supports Serum 1 (.fxp) and Serum 2 (.SerumPreset), on Windows and macOS, using DawDreamer as the headless engine.

One render core shared by the sequential, parallel, and CLI paths, typed jobs, stock-install plugin defaults, and a first-class answer to render reproducibility. Scope is Serum 1 + Serum 2 only, permanently.

How it works

  • Each worker process builds one DawDreamer engine per preset format, once. Jobs hot-swap presets on the matching engine (load_preset for .fxp; convert → load_state for .SerumPreset, decoded by serum2-preset-loader). The audio graph is never rebuilt mid-batch.
  • A loky pool fans jobs out across CPU cores; mixed-format batches dispatch per preset by file suffix.
  • A warmup render at engine build absorbs Serum 2's first-render lazy-load anomaly.

Requirements

  • Windows or macOS, Python 3.11–3.12
  • One or both of:
    • Serum 1 for .fxp presets — the VST2 binary. macOS: /Library/Audio/Plug-Ins/VST/Serum.vst. Windows: C:/Program Files/Common Files/VST3/Serum_x64.dll (the 64-bit VST2 really does live in the VST3 folder). The VST3 build of Serum 1 will not load .fxp correctly.
    • Serum 2 for .SerumPreset presets — the VST3. macOS: /Library/Audio/Plug-Ins/VST3/Serum2.vst3. Windows: C:/Program Files/Common Files/VST3/Serum2.vst3.
  • A valid Serum license on the machine (DawDreamer does not bypass authorization).

Install

pip install serum-render

CLI

If Serum is installed in the standard location, plugin flags are optional — serum-render finds it:

serum-render "~/Documents/Serum Presets/Leads/" ./output/

Explicit plugin paths override the defaults:

serum-render presets/ output/ \
    --serum1 "/Library/Audio/Plug-Ins/VST/Serum.vst" \
    --serum2 "/Library/Audio/Plug-Ins/VST3/Serum2.vst3"

A directory containing both .fxp and .SerumPreset files renders as one mixed batch. Common options:

Flag Default Purpose
--serum1 auto Serum 1 plugin path (VST2 binary). Needed for .fxp input.
--serum2 auto Serum 2 VST3 path. Needed for .SerumPreset input.
--note 48 MIDI note (0–127). Mutually exclusive with --midi.
--velocity 127 MIDI velocity (1–127).
--duration 1.0 Note-on duration (s).
--tail 1.0 Release silence after note-off (s).
--sample-rate 44100 Output sample rate.
--bit-depth 16 16, 24, or 32f.
--format wav wav or npy (raw float32 stereo array).
--filename-template {preset} Vars: {preset} {note} {velocity} {folder} {subpath}.
--midi Render a .mid file instead of a single note.
--workers -1 Parallel workers; -1 = cpu_count - 1.
--skip-existing off Skip presets whose output already exists.
--no-recurse off Don't descend into subdirectories.
--dry-run off Print the render plan and exit.

Run serum-render --help for the full list.

Library API

from serum_render import RenderConfig, Renderer, ParallelRenderer, render_preset

config = RenderConfig(
    serum1_plugin_path="/Library/Audio/Plug-Ins/VST/Serum.vst",
    serum2_plugin_path="/Library/Audio/Plug-Ins/VST3/Serum2.vst3",
    note=48,
    duration=1.0,
    tail=1.0,
)

# Sequential — one engine per format, reused across renders
with Renderer(config) as r:
    audio = r.render("lead.fxp")          # auto-detected as Serum 1
    audio = r.render("pad.SerumPreset")   # auto-detected as Serum 2

# Parallel mixed batch — dict of path -> (2, N) float32 array
with ParallelRenderer(config, workers=-1) as r:
    results = r.render_batch(["a.fxp", "b.fxp", "c.SerumPreset"])

# One-off
audio = render_preset("lead.fxp", config)

Set only the plugin paths for the formats you render; a missing path for a format actually present in the batch raises ValueError naming the field, before any worker boots.

Reproducibility

By default, batch renders are not bit-reproducible: Serum keeps internal DSP state across consecutive renders (LFO phase, envelope residue, lazy-loaded sample data), so a preset rendered mid-batch differs from the same preset rendered alone — measured at 97% of factory presets.

--deterministic (or RenderConfig(deterministic=True)) fixes this: every preset renders in a fresh single-use process, fanned out across your worker count, making batch output bit-identical across runs and render orders. The cost is one plugin load per preset instead of per worker — use it when reproducibility matters (ML datasets, regression baselines), skip it when you just want samples fast.

serum-render presets/ output/ --deterministic

Why a whole process per preset? Serum 2 can be reset by reloading the plugin in place, but Serum 1 keeps state in library-level globals that survive even a full engine rebuild — only process isolation resets both. Probe data and methodology: docs/decisions.md, raw numbers in docs/determinism-probe-2026-07-16.json. Full caveat list: KNOWN_ISSUES.md.

Development

python3.12 -m venv .venv
.venv/bin/pip install -e ".[dev]"

# fast unit tests (no plugin required)
.venv/bin/pytest tests/ --ignore=tests/test_serum1_smoke.py --ignore=tests/test_serum2_smoke.py

# integration smokes (real Serum installs; each half gated independently)
.venv/bin/pytest tests/test_serum1_smoke.py tests/test_serum2_smoke.py \
    --serum1-plugin-path "/Library/Audio/Plug-Ins/VST/Serum.vst" \
    --serum2-plugin-path "/Library/Audio/Plug-Ins/VST3/Serum2.vst3" \
    --serum1-preset-dir  "$HOME/Documents/Serum Presets/Leads/" \
    --serum2-preset-dir  "$HOME/Documents/Serum 2 Presets/Pads/"

Env vars SERUM1_PLUGIN_PATH, SERUM2_PLUGIN_PATH, SERUM1_PRESET_DIR, SERUM2_PRESET_DIR work as flag alternatives.

License

GPL-3.0 (inherited from DawDreamer).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

serum_render-0.1.0.tar.gz (61.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

serum_render-0.1.0-py3-none-any.whl (36.7 kB view details)

Uploaded Python 3

File details

Details for the file serum_render-0.1.0.tar.gz.

File metadata

  • Download URL: serum_render-0.1.0.tar.gz
  • Upload date:
  • Size: 61.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for serum_render-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a0cda0ca1f5ce62b448e7d5db17548e4f9c43793f04f6b56a38d87658edbb885
MD5 b72ab6fd87048bdb1f631d985686c888
BLAKE2b-256 6293df291ea722b4ef13192943e587e2c667ec0673a2e77c0800aee45e1bdd2c

See more details on using hashes here.

Provenance

The following attestation bundles were made for serum_render-0.1.0.tar.gz:

Publisher: publish.yml on wiillownet/serum-render

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file serum_render-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: serum_render-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 36.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for serum_render-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f4d84e4c8a543fabeb37ec58ac82530df599c81fdcc082d40ea433b6e5a9828c
MD5 7b27ed27c858138fa123f5a55d5fd74c
BLAKE2b-256 0264baf6651ebc01fcd85a592f91c80c029925021602053f6a5b7c4f65675e40

See more details on using hashes here.

Provenance

The following attestation bundles were made for serum_render-0.1.0-py3-none-any.whl:

Publisher: publish.yml on wiillownet/serum-render

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page