Mixing style representation learning via autoregressive FX chain prediction
Project description
StemFX: Learning Mixing Style Representations via Autoregressive FX Chain Prediction on Source-Separated Stems
Yuan-Chiao Cheng, Jui-Te Wu, Brian Chen, Yen-Tung Yeh, Yu-Hua Chen, Yi-Hsuan Yang
The official implementation of the ISMIR 2026 paper "StemFX: Learning Mixing Style Representations via Autoregressive FX Chain Prediction on Source-Separated Stems".
StemFX is a paired encoder–decoder for music mixing:
- BSFiLM Encoder — Band-split CNN with FiLM conditioning + temporal attention pooling. Maps a 4-stem 10-second audio segment to a single L2-normalized 512-d embedding capturing mixing style.
- FX Chain Generator — A 6-layer transformer decoder that, given a pair of (original, target) embeddings, autoregressively emits a per-stem FX chain in multiafx format. The chain can be applied to the original stems to render a new mix in the target style.
The two are trained jointly on a Sep-Aug Pipeline: source-separated FMA stems are randomly remixed across songs and processed through random multilib FX chains; the model learns to recover the chain that explains the observed difference.
Install
pip install stemfx
For training and evaluation:
pip install "stemfx[training,evaluation]"
Separating arbitrary mixtures needs no extra setup. The SCNet source is
bundled, and its pretrained weights (~214 MB) are downloaded from the
upstream project's release on first use and cached under
~/.cache/stemfx (override with STEMFX_CACHE_DIR).
Public API
import stemfx
import soundfile as sf
import torch
model = stemfx.load() # downloads the paper checkpoint from HF Hub
# 1a) Embedding a mixture — separated internally with SCNet.
# The separator checkpoint is fetched and cached on first use.
emb_a = model.embed("song_a.wav") # → (512,) L2-normalized
emb_b = model.embed("song_b.wav")
# 1b) Or pass pre-separated stems directly (skips the separator entirely).
stems = {
name: torch.from_numpy(sf.read(f"{name}.wav", dtype="float32", always_2d=True)[0].T)
for name in ("vocals", "bass", "drums", "other")
}
emb = model.embed(stems)
# To use your own separator checkpoint instead of the default:
model = stemfx.load(scnet_model="path/to/scnet.ckpt",
scnet_config="path/to/scnet.yaml")
# 2) Predicting an FX chain from a pair of embeddings (multiafx-format)
chain = model.transfer(emb_a, emb_b)
# chain == {"vocals": [{"effect": "...", "params": {...}}, ...],
# "bass": [...], "drums": [...], "other": [...]}
# 3) End-to-end audio: render the chain on song_a's stems
result = model.transfer_audio("song_a.wav", "song_b.wav")
print(result.pretty()) # human-readable chain summary
torchaudio.save("transferred.wav", result.mix, result.sample_rate)
TransferResult exposes:
| field | type | description |
|---|---|---|
audio |
dict[str, Tensor] |
per-stem rendered audio at 44.1 kHz |
chain |
dict[str, list[dict]] |
per-stem FX chain (multiafx format) |
mix |
Tensor |
(2, T) downmix of audio |
sample_rate |
int |
always 44100 |
pretty() |
str |
one-line-per-stem chain summary |
Reproducing the paper
1 · Preprocess audio (separate into 4 stems)
python pipeline/preprocess_fma.py \
--input_dir /path/to/fma_full/ \
--output_dir /path/to/fma_separated/ \
--scnet_model third_party/Music-Source-Separation-Training/model_scnet_masked_ep_111_sdr_9.8286.ckpt \
--scnet_config third_party/Music-Source-Separation-Training/configs/config_musdb18_scnet_xl_ihf.yaml \
--device cuda:0
2 · Apply random multilib FX chains (Sep-Aug)
python pipeline/augment_stems.py \
--source /path/to/fma_separated/ \
--output /path/to/fma_separated_augmented/ \
--min_fx 1 --max_fx 10 \
--workers 32
3 · Compute per-stem LUFS and emit valid_tracks.json
Required for cross-song stem mixing during training (mix_stems: true):
python pipeline/compute_stem_lufs.py \
--dataset_dir /path/to/fma_separated_augmented/ \
--threshold -50.0 \
--num_workers 32
4 · Train
python train.py \
--config configs/default.yaml \
data.original_path=/path/to/fma_separated \
data.augmented_path=/path/to/fma_separated_augmented \
train.checkpoint_dir=checkpoints/stemfx_main \
train.log_dir=logs/stemfx_main \
train.device=cuda:0
CLI dotted overrides take precedence over the YAML file. See
configs/default.yaml for the full set of knobs.
Evaluation
Paired FX retrieval
python evaluate/retrieval.py \
--dataset /path/to/MUSDB18_paired_fx_corrected_5/ \
--checkpoint checkpoints/stemfx_main/best_checkpoint.pt \
--device cuda:0
Style transfer (free + forced MRSTFT)
python evaluate/style_transfer.py \
--original_dir /path/to/MUSDB18_normalized/test \
--target_dir /path/to/MUSDB18_styled/test \
--checkpoint checkpoints/stemfx_main/best_checkpoint.pt \
--output_dir outputs/eval_st \
--num_examples 50
Directory layout
stemfx/ # installed Python package
api.py # high-level inference wrapper
encoder.py # BSFiLM Encoder
generator.py # FX Chain Generator
model.py # StemFX nn.Module (encoder + generator)
tokenizer.py # FX-chain tokenization (multiafx vocabulary)
losses.py # FXChainLoss, MultiResolutionSTFTLoss
separator.py # SCNetSeparator (bundled SCNet)
_scnet_infer.py # chunked overlap-add separation
weights.py # checkpoint resolution + download
third_party/ # vendored SCNet source (MIT, see NOTICE)
mixing_features.py # FiLM feature extractor (44.1 kHz)
normalization.py # multiafx parameter ranges
configs/default.yaml # training config (paper main)
data/sepaug_dataset.py # Sep-Aug paired dataset + DataLoader
pipeline/ # data preprocessing scripts
preprocess_fma.py # SCNet separation
augment_stems.py # multilib FX augmentation
compute_stem_lufs.py # per-stem LUFS + valid_tracks.json
evaluate/ # evaluation scripts
retrieval.py # paired FX retrieval
style_transfer.py # free + forced MRSTFT
train.py # config-driven training entry point
tests/ # pytest smoke tests
Citation
@inproceedings{cheng2026stemfx,
title = {{StemFX}: Learning Mixing Style Representations via Autoregressive
{FX} Chain Prediction on Source-Separated Stems},
author = {Cheng, Yuan-Chiao and Wu, Jui-Te and Chen, Brian and
Yeh, Yen-Tung and Chen, Yu-Hua and Yang, Yi-Hsuan},
booktitle = {Proceedings of the 27th International Society for Music
Information Retrieval Conference (ISMIR)},
address = {Abu Dhabi, UAE},
year = {2026},
eprint = {2607.15634},
archivePrefix = {arXiv},
primaryClass = {eess.AS},
}
License
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 stemfx-0.2.0.tar.gz.
File metadata
- Download URL: stemfx-0.2.0.tar.gz
- Upload date:
- Size: 53.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ae354555c1250fc6728c9edcf1c72dd39e8aeb9a5d2458ef3571d8688d196f7
|
|
| MD5 |
c805a5d82356b1583d7a2b07c5ab664a
|
|
| BLAKE2b-256 |
913e1095f00e84fbc9c43e1746941a8e8854a2fa36d92d7e974dd155f091d34a
|
File details
Details for the file stemfx-0.2.0-py3-none-any.whl.
File metadata
- Download URL: stemfx-0.2.0-py3-none-any.whl
- Upload date:
- Size: 54.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53de9e14bc2627943ff3dfa2c1b0d51f0aec678206b7e002406d2eceaece3e3b
|
|
| MD5 |
0c3deff59d405de1002ce3dc28ebadda
|
|
| BLAKE2b-256 |
1e3a04dc4b217d104bfe0af6d1ad28cd6a7f39f320530f6411a2d8d0dfbf0f91
|