Skip to main content

FCPE TTA and pYIN ensemble pitch extraction for singing voice.

Project description

中文|English

Ensemble Pitch Extractor

License: MIT Python 3.12+

Ensemble Pitch Extractor is a singing-voice F0 extractor that combines FCPE test-time augmentation with a pYIN high-frequency candidate in a dynamic programming decoder. It is designed for ordinary singing, high notes, and whistle-register material where a single extractor may fail.

The package provides:

  • a Python API for extracting F0 from waveforms or audio files;
  • a command-line interface that saves .npy F0 tracks and optional .png diagnostic plots;
  • an ensemble decoder that selects a smooth candidate path instead of averaging incompatible F0 estimates.

Demonstration

Audio samples sourced from the internet.

Chest-to-Whistle Large Vibrato
Noisy High Notes Low Notes

Installation

pip install ensemble-pitch-extractor

For local development:

uv sync
uv run ensemble-pitch-extractor --help

Python 3.12 or newer is required. To enable diagnostic plots, install matplotlib separately:

pip install matplotlib

See examples/plotting.py for the plotting functions.

Command Line

Extract F0 from one audio file:

ensemble-pitch-extractor input.wav -o f0_out

This writes:

f0_out/input.npy

The .npy file is a one-dimensional float32 array in Hz. Unvoiced frames are stored as 0.

Save a plot of F0 overlaid on a mel spectrogram:

ensemble-pitch-extractor input.wav -o f0_out --plot

Process all supported audio files in a directory:

ensemble-pitch-extractor audio_dir -o f0_out --plot

CUDA is auto-detected by default. To force a specific device:

ensemble-pitch-extractor audio_dir -o f0_out --device cpu

Control GPU memory with --max-batch-length (default 480000 samples ≈ 30s):

ensemble-pitch-extractor audio_dir -o f0_out --max-batch-length 200000

Disable pYIN and use FCPE TTA only:

ensemble-pitch-extractor input.wav -o f0_out --no-pyin

Useful options:

--f0-min 80
--f0-max 4000
--max-batch-length 480000
--device auto
--pyin-priority-min-f0 1300
--pyin-fcpe-close-semitones 1.0
--interp-uv
--recursive

Python API

from ensemble_pitch_extractor import extract_f0_from_file, load_model

model = load_model()  # auto-detects CUDA, or pass device="cpu"
f0, times, *_ = extract_f0_from_file(
    "input.wav", model=model, f0_min=80, f0_max=4000,
)
# f0: Hz, shape (frames,)   times: seconds, shape (frames,)

For audio already in memory:

import librosa
from ensemble_pitch_extractor import extract_f0, load_model

model = load_model()
sr = model.get_model_sr()
audio, _ = librosa.load("input.wav", sr=sr, mono=True)
f0 = extract_f0(audio, sr, model, f0_min=80, f0_max=4000)

For torch tensor input (padded batch or concatenated, supports GPU):

import torch
from ensemble_pitch_extractor import extract_f0_from_tensor, load_model

model = load_model("cuda")

# padded batch: (batch=4, samples) with fixed-length clips
wav = torch.randn(4, 16000, device="cuda")
f0 = extract_f0_from_tensor(wav, sr=16000, model=model)  # (4, frames)

# concatenated: clips of different lengths, no padding waste
wavs = [torch.randn(8000, device="cuda"), torch.randn(12000, device="cuda")]
lengths = [len(w) for w in wavs]
concat = torch.cat(wavs)
f0 = extract_f0_from_tensor(concat, sr=16000, model=model, lengths=lengths,
                            max_batch_length=20000)  # (2, max_frames), NaN padded

Method Overview

The decoder treats each extractor output as a candidate trajectory. Current candidates are:

FCPE key shift = 0
FCPE key shift = -12
FCPE key shift = +12
pYIN

For an FCPE candidate with key shift $s$, the model output is mapped back to the original pitch space before fusion:

$$ \hat f_{t,s} = \frac{f_{t,s}}{2^{s/12}} . $$

pYIN is included as an ultra-high frequency candidate. By default it only searches 1300–4000 Hz, and frames below 1300 Hz are discarded. This prevents pYIN from replacing FCPE in normal ranges where FCPE usually captures finer detail.

All candidates are converted to MIDI note space:

$$ n_{t,k}=69+12\log_2\frac{f_{t,k}}{440}. $$

The final path is selected by dynamic programming:

$$ \pi^*=\arg\min_\pi \sum_t U_t(\pi_t)+\sum_{t=1}^{T-1} C_t(\pi_{t-1},\pi_t). $$

Here $U_t(k)$ is a per-frame candidate prior and $C_t(i,k)$ is a transition cost. This formulation avoids averaging octave errors, half-frequency errors, and algorithm-specific mistakes into spurious intermediate pitches.

Heuristics as Priors

The implementation uses the following structured priors:

  • MIDI-space costs make equal musical intervals comparable across frequency ranges.
  • UV penalty discourages fragmented voiced/unvoiced paths.
  • Octave-aware jump cost allows one-, two-, and three-octave transitions, which are important for chest-to-whistle jumps.
  • FCPE +12 receives a low-pitch prior below E2.
  • FCPE -12 receives a high-pitch prior above D5.
  • pYIN receives a high-frequency prior only when it is above 1300 Hz and more than one semitone away from every FCPE candidate.
  • RMS energy gating removes false voiced output during silence after decoding.

The default candidate order is FCPE 0, FCPE -12, FCPE +12, pYIN, so that exact ties prefer FCPE over pYIN.

Build and Publish

uv lock --python 3.12
uv build
uv publish

With a PyPI token:

uv publish --token "pypi-..."

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

ensemble_pitch_extractor-0.1.1.tar.gz (22.6 kB view details)

Uploaded Source

Built Distribution

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

ensemble_pitch_extractor-0.1.1-py3-none-any.whl (20.2 kB view details)

Uploaded Python 3

File details

Details for the file ensemble_pitch_extractor-0.1.1.tar.gz.

File metadata

File hashes

Hashes for ensemble_pitch_extractor-0.1.1.tar.gz
Algorithm Hash digest
SHA256 22df05601dbba55dbfa840bce177986632176b9667a80263bc8a2c2747d99164
MD5 e54a65818d0201da50d0920ad90d2653
BLAKE2b-256 75324d82bcacbf0925a26ff14f71ea54d088fa9bcf3850240cd299c216782751

See more details on using hashes here.

File details

Details for the file ensemble_pitch_extractor-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for ensemble_pitch_extractor-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 71134a9c0b286647f2df2dbda02274bc5c8e4dc15497450e3634108e6ad11578
MD5 df0d7c0d3b8f5c860ebb19d289dee225
BLAKE2b-256 e87186e290c974b6a654b5c5f5a1cb5bd211188db2ce11e7d3f02fc6662b68eb

See more details on using hashes here.

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