Batch-render Serum 1 (.fxp) and Serum 2 (.SerumPreset) presets to audio files via DawDreamer.
Project description
serum-render
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_presetfor.fxp;convert → load_statefor.SerumPreset, decoded byserum2-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
.fxppresets — 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.fxpcorrectly. - Serum 2 for
.SerumPresetpresets — the VST3. macOS:/Library/Audio/Plug-Ins/VST3/Serum2.vst3. Windows:C:/Program Files/Common Files/VST3/Serum2.vst3.
- Serum 1 for
- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0cda0ca1f5ce62b448e7d5db17548e4f9c43793f04f6b56a38d87658edbb885
|
|
| MD5 |
b72ab6fd87048bdb1f631d985686c888
|
|
| BLAKE2b-256 |
6293df291ea722b4ef13192943e587e2c667ec0673a2e77c0800aee45e1bdd2c
|
Provenance
The following attestation bundles were made for serum_render-0.1.0.tar.gz:
Publisher:
publish.yml on wiillownet/serum-render
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
serum_render-0.1.0.tar.gz -
Subject digest:
a0cda0ca1f5ce62b448e7d5db17548e4f9c43793f04f6b56a38d87658edbb885 - Sigstore transparency entry: 2187948373
- Sigstore integration time:
-
Permalink:
wiillownet/serum-render@653a997ec4dedf94c182675a0b3a827c2fd384a5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/wiillownet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@653a997ec4dedf94c182675a0b3a827c2fd384a5 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4d84e4c8a543fabeb37ec58ac82530df599c81fdcc082d40ea433b6e5a9828c
|
|
| MD5 |
7b27ed27c858138fa123f5a55d5fd74c
|
|
| BLAKE2b-256 |
0264baf6651ebc01fcd85a592f91c80c029925021602053f6a5b7c4f65675e40
|
Provenance
The following attestation bundles were made for serum_render-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on wiillownet/serum-render
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
serum_render-0.1.0-py3-none-any.whl -
Subject digest:
f4d84e4c8a543fabeb37ec58ac82530df599c81fdcc082d40ea433b6e5a9828c - Sigstore transparency entry: 2187948403
- Sigstore integration time:
-
Permalink:
wiillownet/serum-render@653a997ec4dedf94c182675a0b3a827c2fd384a5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/wiillownet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@653a997ec4dedf94c182675a0b3a827c2fd384a5 -
Trigger Event:
release
-
Statement type: