crates/veilvoice-core/src/pitch.rs
veilvoice-core · 286 lines · read the source here · or on GitHub
Monophonic fundamental-frequency tracker (decimated YIN).
Accent neutralisation needs to know the speaker's current f0 so the intonation contour can be replaced with a canonical one (see crate::accent).
Two constraints shape this implementation:
- The STFT frame is too short to resolve f0 directly. At the default 1024-point FFT / 48 kHz the bin spacing is ~47 Hz, so a spectral peak-pick cannot tell 100 Hz from 140 Hz. This tracker therefore works in the time domain over its own rolling history, which may be longer than one STFT frame without adding any output latency, because the window still ends at the current frame, so it stays causal.
- It must be cheap enough for an audio callback. The signal is decimated to ~8 kHz first (pitch lives in the low harmonics), which cuts the difference-function cost by the square of the decimation factor. At the default settings it costs on the order of 8 M flops/s, well under 1 % of one core, and allocates nothing after construction.
The algorithm is YIN's cumulative mean normalised difference function (de Cheveigné & Kawahara, 2002) with parabolic interpolation, minus the optimisations that only matter for offline accuracy.
In plain words
This works out how high or low somebody is speaking, moment by moment.
It is needed for the accent work: to replace the rise and fall of somebody's voice with a flatter, more ordinary pattern, you first have to know what the rise and fall currently is.
It is built to be quick rather than perfect, because it has to keep up with a live conversation. When it is not sure, it says so instead of guessing, and the accent work simply leaves that moment alone.
WHAT THIS FILE CONTAINS
286 lines defining 4 functions (3 public), 2 types and 6 constants. Everything below is read out of the source, so it cannot disagree with the code.
The types it owns.
struct PitchEstimateline 53 · One f0 measurement.struct PitchTrackerline 63 · Rolling, allocation-free f0 tracker.
What happens when it runs. These are the ways in: public, and nothing else in this file calls them, so they are what an outside caller reaches first.
PitchTracker::newline 81 · Build a tracker for input at sample_rate hertz.PitchTracker::pushline 103 · Feed new input samples (anti-aliased and decimated internally).PitchTracker::estimateline 123 · Estimate f0 over the newest history.
reachesparabolic
WHAT CALLS WHAT
The functions this file defines, and the calls between them. An edge means the callee's name appears, called, inside the caller's body. This is a syntactic reading, not a type-resolved one.
The same graph as Mermaid source
%%{init: {"theme":"base","themeVariables":{"background":"#1a1b26","primaryColor":"#1f2335","primaryTextColor":"#c0caf5","primaryBorderColor":"#7aa2f7","secondaryColor":"#16161e","tertiaryColor":"#16161e","lineColor":"#737aa2","textColor":"#c0caf5","mainBkg":"#1f2335","nodeBorder":"#7aa2f7","clusterBkg":"#16161e","clusterBorder":"#2f3549","fontFamily":"ui-monospace, SFMono-Regular, Consolas, monospace","fontSize":"14px"}}}%%
flowchart TD
n_new(["PitchTracker::new<br/>line 81"])
n_push(["PitchTracker::push<br/>line 103"])
n_estimate(["PitchTracker::estimate<br/>line 123"])
n_parabolic["PitchTracker::parabolic<br/>line 185"]
n_estimate --> n_parabolic
click n_new href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-core/src/pitch.rs#L81" "open the source"
click n_push href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-core/src/pitch.rs#L103" "open the source"
click n_estimate href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-core/src/pitch.rs#L123" "open the source"
click n_parabolic href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-core/src/pitch.rs#L185" "open the source"
classDef entry fill:#1f2335,stroke:#7aa2f7,color:#c0caf5
class n_new,n_push,n_estimate entry
classDef helper fill:#1f2335,stroke:#bb9af7,color:#c0caf5
class n_parabolic helper
This site loads no third-party script, so it cannot run Mermaid; the diagram above is the same nodes and edges drawn by the generator instead. GitHub renders the source below directly.
ITEMS
| Item | Line | Documentation |
|---|---|---|
F0_MIN_HZ const | 38 | Lowest fundamental the tracker will report, in hertz. |
F0_MAX_HZ const | 40 | Highest fundamental the tracker will report, in hertz. |
DECIMATED_HZ const | 42 | Target sample rate after decimation, in hertz. |
WINDOW const | 45 | Analysis window length in decimated samples (~40 ms at 8 kHz, at least two periods of the lowest supported f0). |
YIN_THRESHOLD const | 47 | d'(tau) below this counts as a confident voiced period. |
SILENCE_RMS const | 49 | Frames quieter than this (RMS) are treated as unvoiced regardless. |
PitchEstimate pub struct | 53 | One f0 measurement. |
PitchTracker pub struct | 63 | Rolling, allocation-free f0 tracker. |
PitchTracker::new pub fn | 81 | Build a tracker for input at sample_rate hertz. |
PitchTracker::push pub fn | 103 | Feed new input samples (anti-aliased and decimated internally). |
PitchTracker::estimate pub fn | 123 | Estimate f0 over the newest history. |
PitchTracker::parabolic fn | 185 | Sub-sample refinement of the minimum at tau by fitting a parabola through its two neighbours. |