pitch-garden
pitch-garden provides a unified interface to various pitch (F0) trackers and integrates their outputs by voting. The outputs of all trackers are aligned on a common frame grid, so that they can be directly compared and voted.
Features
- Voting of multiple trackers
- Two-pass tracking with an F0 search range estimated from the first pass
- Parallel execution of the trackers
Requirements
- Python 3.12+
Documentation
- See this page for the reference manual.
Installation
pip install "pitch-garden[all]" # All trackers
# To avoid installing PyTorch, use the following instead.
# pip install "pitch-garden[lite]" # Trackers not requiring PyTorch
Supported Algorithms
| Name | Neural | F0 Range [Hz] | Voicing Threshold | Paper | Source |
|---|---|---|---|---|---|
crepe |
✓ | 32 – 2006 | 0.1 – 0.4 | arXiv | GitHub |
dio |
10 – Nyquist | 0.2 – 0.02 | AES E-Library | GitHub | |
fcn-f0 |
✓ | 30 – 1000 | 0.2 – 0.6 | ISCA Archive | GitHub |
fcpe |
✓ | 33 – 1975 | 0.0 – 0.1 | arXiv | GitHub |
harmof0 |
✓ | 28 – 4371 | 0.5 – 0.9 | arXiv | GitHub |
harvest |
10 – Nyquist | 0.2 – 0.0 | ISCA Archive | GitHub | |
pesto |
✓ | 28 – 3452 | 0.0 – 0.2 | arXiv | GitHub |
pienet |
✓ | 50 – 500 | 0.5 – 1.0 | IEEE Xplore | GitHub |
pyin |
16 – 8000 | IEEE Xplore | GitHub | ||
rapt |
10 – Nyquist | 0.7 – -0.6 | EDU | GitHub | |
reaper |
10 – Nyquist | 1.6 – -0.5 | GitHub | ||
rmvpe |
✓ | 32 – 2005 | 0.0 – 0.3 | arXiv | GitHub |
straight |
40 – Nyquist | ISCA Archive | GitHub | ||
swift-f0 |
✓ | 47 – 2093 | 0.1 – 0.5 | arXiv | GitHub |
swipe |
10 – Nyquist | 0.2 – 0.5 | EDU | GitHub | |
yaapt |
10 – Nyquist | 0.1 – 1.0 | IEEE Xplore | GitHub | |
yin |
16 – 8000 | 0.3 – 0.6 | JASA | GitHub |
The F0 range is the search range that can be specified.
The voicing threshold is shown as a typical range, where moving it toward the right value gives more unvoiced frames.
pesto is not available on Python 3.15 or later, because the pesto-pitch package requires torchaudio, which does not support those versions.
dio, harvest, rapt, reaper, and swipe are based on the implementations in SPTK, and their outputs are compatible with the pitch command of SPTK.
Unsupported Algorithms
| Name | Neural | Paper | Source | Reason |
|---|---|---|---|---|
| Basic Pitch | ✓ | arXiv | GitHub | Designed for polyphonic transcription |
| JDCNet | ✓ | MDPI | GitHub | PyTorch versions are unlicensed |
| Kaldi Pitch | IEEE Xplore | GitHub | No maintained Python implementation | |
| MPM | EDU | Similar to yin |
||
| PENN (FCNF0++) | ✓ | arXiv | GitHub | Did not outperform fcn-f0 in our evaluation |
| Praat (Parselmouth) | EDU | GitHub | Licensed under GPL-3.0 | |
| SPICE | ✓ | arXiv | Kaggle | No PyTorch weights available |
Usage
Input preparation
Audio files, headerless raw files, and NumPy arrays are accepted.
import numpy as np
import pitch_garden as pg
tracker = pg.Tracker("rapt")
# An audio file readable by soundfile, where multiple channels are averaged.
audio = pg.load_audio("speech.wav")
pitch = tracker(audio)
# A headerless raw file with a data type of SPTK x2x, e.g., 16-bit integers.
raw = pg.RawFormat(sample_rate=16000, data_type="s", num_channels=1)
audio = pg.load_audio("speech.raw", raw)
pitch = tracker(audio)
# A NumPy array with its sample rate.
sample_rate = 16000
waveform = np.sin(2 * np.pi * 220 * np.arange(sample_rate) / sample_rate)
pitch = tracker(waveform, sample_rate)
Single algorithm
A Tracker holds an algorithm and its own parameters.
The F0 search range given by fmin and fmax at the creation is the default of the tracker, and can be overridden only for a call by giving fmin and/or fmax to the call, e.g., if the range is known for each file.
The bound not given at the call is taken from the creation, and changing the range does not reload the algorithm.
The trackers supporting GPUs, e.g., crepe, use a GPU if available, which is chosen when the tracker is created and can be changed by tracker.replace(device="cpu").
The output Pitch holds the F0 in Hz on a uniform frame grid regardless of the native frame period of the algorithm, and converts it to other formats on demand.
import pitch_garden as pg
audio = pg.load_audio("speech.wav")
tracker = pg.Tracker("rapt", voicing_threshold=0.0, fmin=60.0, fmax=800.0)
pitch = tracker(audio, frame_period=5.0)
print(pitch)
# Pitch(source='rapt', num_frames=100, num_voiced_frames=..., ...)
print(pitch.f0) # The F0 in Hz as a NumPy array, where unvoiced frames are 0.
# The search range is overridden only for this call, i.e., 80-800 Hz.
pitch = tracker(audio, frame_period=5.0, fmin=80.0)
# The F0 is converted to a NumPy array of another format.
lf0 = pitch.to("log") # log F0 (unvoiced: -1e10)
period = pitch.to("inverse") # Fs/F0 in samples (unvoiced: 0)
# The F0 is converted and saved; the pitch object itself is unchanged.
pitch.tofile("speech.lf0", output_format="log", file_type="double")
Multiple algorithms and voting
A Pipeline runs the trackers in parallel where possible and votes their outputs.
The same algorithm can be used with different parameters by giving different names.
The F0 search range can be given at a call as with Tracker, which overrides the ranges of all trackers supporting it only for the call.
import pitch_garden as pg
audio = pg.load_audio("speech.wav")
trackers = [pg.Tracker("rapt"), pg.Tracker("swift-f0")]
with pg.Pipeline(trackers, voter="median") as pipeline:
result = pipeline(audio, frame_period=5.0)
# The final contour voted from the trackers as a Pitch object
print(result.pitch)
# Pitch(source='median', num_frames=100, num_voiced_frames=..., ...)
# The outputs of the trackers as Pitch objects keyed by the tracker names
for name, pitch in result.each.items():
print(name, pitch)
# rapt Pitch(source='rapt', num_frames=100, ...)
# swift-f0 Pitch(source='swift-f0', num_frames=100, ...)
Two-pass tracking
If the F0 range of the speaker is unknown, the trackers are first run with a wide search range, and then rerun with the range estimated from the distribution of the F0 of the first pass.
This works with a single tracker as well as multiple ones, where the estimated range is shared by all trackers.
The first-pass range is given by TwoPass, and can be overridden only for a call by giving fmin and/or fmax to the call, e.g., if a rough range is known for each file.
Each algorithm has its recommended settings chosen in examples/pitch_benchmark, i.e., whether it is rerun and which pass gives the voicing decisions, which can be overridden by two_pass_policy of each tracker.
import pitch_garden as pg
from pitch_garden.ranges.log_normal import LogNormalRangeEstimator
audio = pg.load_audio("speech.wav")
two_pass = pg.TwoPass(estimator=LogNormalRangeEstimator(), fmin=40.0, fmax=2000.0)
with pg.Pipeline([pg.Tracker("rapt")], two_pass=two_pass) as pipeline:
result = pipeline(audio)
# The estimated range
print(result.f0_range)
# F0Range(fmin=111.96..., fmax=224.08..., num_voiced_frames=..., ...)
# The final contour of the second pass, i.e., the output of the single tracker
print(result.pitch)
# Pitch(source='rapt', num_frames=100, num_voiced_frames=..., ...)
# The result of the first pass, which is of the same type as the final result
print(result.pass1.pitch)
# Pitch(source='rapt', num_frames=100, num_voiced_frames=..., ...)
Command-line interface
The settings are given by a YAML file, and the input and the output directory are given by the command line. The input is an audio file, a directory searched recursively, or a text file listing audio files line by line. See configs/single.yaml for all settings, configs/multiple.yaml for multiple trackers, and configs/two-pass.yaml for the two-pass tracking.
pitch-garden configs/multiple.yaml data/wav -o exp/f0
The main settings for the above use cases are as follows:
trackers:
- algorithm: rapt
- algorithm: swift-f0
voter:
type: median
two_pass:
scope: group # The unit to estimate the range: utterance, group, or global.
group_by: {type: dirname, level: 1}
With scope: group, the range is estimated for each group of files, e.g., each speaker.
The groups are defined by group_by in either of the following ways:
-
{type: dirname, level: 1}groups the files by their directorylevellevels above, wherelevel: 1is the directory containing the files. For example, the files below are grouped intospk1andspk2withlevel: 1, and into a single groupdata/wavwithlevel: 2.data/wav/spk1/a.wav data/wav/spk1/b.wav data/wav/spk2/c.wav -
{type: map, path: data/speaker.map}groups the files by a text file, where each line is the file name without the extension followed by the group name.a spk1 b spk1 c spk2
With scope: utterance and scope: global, the range is estimated for each file and for all files, respectively.
The final contours are saved under <output_dir>/pitch, which are the voted ones for multiple trackers.
For multiple trackers, their outputs are also saved under <output_dir>/trackers/<name> if save_each is enabled or voting is disabled.
For the two-pass tracking, the estimated ranges are saved in <output_dir>/f0_range.yaml.
License
This software is released under the Apache License 2.0.
Metadata
Release files for pitch-garden 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pitch_garden-0.1.0.tar.gz | 73.1 kB | With provenance | Details for pitch_garden-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pitch_garden-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for pitch_garden-0.1.0-py3-none-any.whl |
Total release size: 184.6 kB
Release files / pitch_garden-0.1.0.tar.gz
| Download URL | pitch_garden-0.1.0.tar.gz |
|---|---|
| Size | 73.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1d7bffa113d992c6be5a9035674464b071c41e24458f7ea61330f7c30a412031
|
|
BLAKE2b-256 checksum How to use checksums |
5cfc2558b7b8b8bba8312a3b5e6e9a888bae13683c5d960a02d2907f59b02810
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / pitch_garden-0.1.0-py3-none-any.whl
| Download URL | pitch_garden-0.1.0-py3-none-any.whl |
|---|---|
| Size | 111.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
480207770ab82d7299690865457379b2b897e290d20d69a633ffa29a315e16b7
|
|
BLAKE2b-256 checksum How to use checksums |
7626ee662f8b41a46a4817964e4e4f6768f92d44a74b585e1599ca91850c85aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for pitch-garden 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pitch_garden-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.