File size: 2,315 Bytes
799027f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | """Interpolate f0 through unvoiced frames exactly as training did.
The conformer's `f0_key` is `f0_interp`, which is pYAAPT's own interpolation
of the raw pitch track -- not a straight line through the gap. It median
filters, fits a shape-preserving PCHIP spline over the voiced frames, fills
the zeros from that, median filters again, and extends the first and last
voiced values outward.
Nothing here reimplements any of that. The real `pYAAPT.PitchObj` is used,
with the same monkey-patch the training pipeline applies, so every constant
and helper comes from the library rather than from a copy that can drift.
This is for contours built here -- drawn strokes with gaps where the pen was
lifted. It cannot reproduce a stored `f0_interp` from a stored raw `f0`: the
training pipeline interpolates first and *then* truncates (`f0 = f0[6:]`,
then both arrays cut to the shorter), so the original interpolation saw
neighbouring frames that no longer exist. Differences of up to ~14 Hz appear
at the end of the contour for exactly that reason. Where a stored
`f0_interp` exists, use it rather than recomputing.
"""
from __future__ import annotations
import numpy as np
def _blank_pitch_obj(f0: np.ndarray):
"""A real PitchObj carrying `f0`, without running pitch tracking on audio.
PitchObj.__init__ wants a SignalObj to analyse. Interpolation only reads
`samp_values` and `nframes` (plus class constants and `edges_finder`), so
the object is built uninitialised and those two fields are set directly --
every other attribute and method is the library's own.
"""
from amfm_decompy import pYAAPT
obj = pYAAPT.PitchObj.__new__(pYAAPT.PitchObj)
obj.samp_values = np.asarray(f0, dtype=np.float64)
obj.nframes = len(obj.samp_values)
return obj
def interpolate_f0(f0: np.ndarray) -> np.ndarray:
"""Raw f0 (zeros at unvoiced frames) -> the contour the model expects.
Returns the input unchanged when there is nothing to interpolate.
"""
f0 = np.asarray(f0, dtype=np.float64)
if f0.size == 0:
return f0
if not (f0 == 0).any():
return f0 # already continuous
from ._yaapt_patch import patched_interpolate
obj = _blank_pitch_obj(f0)
patched_interpolate(obj)
return np.asarray(obj.samp_interp, dtype=np.float64)
|