Download export/decode.py from Scicom-intl/fcpe-onnx: direct link, hf CLI and curl.
- Browser
- Download file 1.9 kB
-
https://huggingface.co/Scicom-intl/fcpe-onnx/resolve/main/export/decode.py
- Command line
-
hf download hf://Scicom-intl/fcpe-onnx/export/decode.py
-
curl -L -o decode.py https://huggingface.co/Scicom-intl/fcpe-onnx/resolve/main/export/decode.py
1.9 kB
| """Salience -> f0 for the exported pitch models (numpy only). | |
| CREPE and RMVPE share one 360-bin grid: 20 cents per bin from 1997.38 cents (32.70 Hz) re 10 Hz. | |
| FCPE spaces its 360 bins evenly between 32.7 and 1975.5 Hz in cents (~19.8 cents per bin). | |
| Decoding is the models' own: a salience-weighted mean of the 9 bins around the peak, voiced when | |
| the peak clears a threshold. | |
| """ | |
| import numpy as np | |
| CREPE_CENTS = 1997.3794084376191 + 20.0 * np.arange(360) | |
| FCPE_CENTS = np.linspace(1200 * np.log2(32.7 / 10), 1200 * np.log2(1975.5 / 10), 360) | |
| GENERIC_CENTS = (100.0 / 3) * np.arange(384) # PESTO activations, 1/3 semitone (parity only) | |
| CENTS = {"crepe": CREPE_CENTS, "rmvpe": CREPE_CENTS, "fcpe": FCPE_CENTS, "pesto-act": GENERIC_CENTS} | |
| # each model's reference threshold on the peak salience (CREPE: torchcrepe's documented 0.21 | |
| # periodicity, RMVPE: RVC's 0.03, FCPE: torchfcpe's inference default 0.006) | |
| THRESHOLD = {"crepe": 0.21, "rmvpe": 0.03, "fcpe": 0.006, "pesto-act": 0.0} | |
| def local_average_cents(sal: np.ndarray, cents: np.ndarray, width: int = 4) -> np.ndarray: | |
| """sal [T, K] -> cents [T]: salience-weighted mean of the bins within `width` of the peak.""" | |
| t, k = sal.shape | |
| peak = sal.argmax(1) | |
| idx = np.clip(peak[:, None] + np.arange(-width, width + 1)[None, :], 0, k - 1) | |
| w = np.take_along_axis(sal, idx, 1) | |
| w = np.maximum(w, 0) | |
| return (w * cents[idx]).sum(1) / np.maximum(w.sum(1), 1e-12) | |
| def decode(sal: np.ndarray, kind: str, threshold: float | None = None): | |
| """sal [1, T, K] or [T, K] -> (f0 Hz [T], voiced bool [T]). Unvoiced frames get f0 0.""" | |
| s = np.asarray(sal, np.float64) | |
| if s.ndim == 3: | |
| s = s[0] | |
| th = THRESHOLD[kind] if threshold is None else threshold | |
| cents = local_average_cents(s, CENTS[kind]) | |
| voiced = s.max(1) > th | |
| f0 = np.where(voiced, 10.0 * 2 ** (cents / 1200.0), 0.0) | |
| return f0, voiced | |