PrettyPitch

A singing F0 model trained without scores. There are no note onsets and no note pitches in the training data — only singing and its phoneme labels. The pitch target for every nucleus phoneme is estimated from the sung F0 itself, so a new voice needs nothing but audio and a phoneme alignment.

GitHub Repository

Everything below is synthesized from a MusicXML score in this repository, with the bundled weights: the F0 model and the consonant-length model from PrettyPitch, the acoustic model from LeapSinger, and the NHVSing vocoder. One command per clip: python -m svs.render examples/<score>.musicxml --spk_id 2. The F0 and the mel are reproducible from the seed; only the vocoder's noise component differs between runs.

Three singers, one score

The same MusicXML, rendered for each of the three bundled speakers. The speaker id goes to all three models — consonant lengths, F0 and mel — so the timing changes with the voice, not just the timbre. Natsume is transposed down 5 semitones to sit in his range; nothing else differs between the columns.

Score Oniku Kurumi Natsume Yuuri (−5) Namine Ritsu
Kirakira Boshi きらきら星 · 120 BPM · 26 s
Furusato ふるさと · 120 BPM · 26 s
Haru no Ogawa 春の小川 · 100 BPM · 38 s

What the models produced

Every clip above, drawn from the same render. The pale boxes are the notes once the consonants have been pulled forward; the line is the generated F0; the strip underneath is the phoneme timeline, and the panel behind the F0 is shaded the same way (green = vowel, blue = consonant, grey = rest).
The F0 is dotted where the frame is unvoiced. It is not missing there: the model produces a continuous F0 and a separate voiced/unvoiced channel, and the acoustic model and the vocoder both receive the continuous curve. The vocoder uses the voicing decision to gate its harmonic excitation, which is why the dotted stretches are silent in pitch but still carry the contour into the next note.

notes, F0 and durations for Kirakira Boshi sung by Oniku Kurumi
Kirakira Boshi (きらきら星) — Oniku Kurumi
notes, F0 and durations for Kirakira Boshi sung by Natsume Yuuri
Kirakira Boshi (きらきら星) — Natsume Yuuri
notes, F0 and durations for Kirakira Boshi sung by Namine Ritsu
Kirakira Boshi (きらきら星) — Namine Ritsu
notes, F0 and durations for Furusato sung by Oniku Kurumi
Furusato (ふるさと) — Oniku Kurumi
notes, F0 and durations for Furusato sung by Natsume Yuuri
Furusato (ふるさと) — Natsume Yuuri
notes, F0 and durations for Furusato sung by Namine Ritsu
Furusato (ふるさと) — Namine Ritsu
notes, F0 and durations for Haru no Ogawa sung by Oniku Kurumi
Haru no Ogawa (春の小川) — Oniku Kurumi
notes, F0 and durations for Haru no Ogawa sung by Natsume Yuuri
Haru no Ogawa (春の小川) — Natsume Yuuri
notes, F0 and durations for Haru no Ogawa sung by Namine Ritsu
Haru no Ogawa (春の小川) — Namine Ritsu

How fast is too fast

One score — the scat chorus of Ievan polkka — at five tempos, sung by Namine Ritsu. Only the tempo changes; the notes and the lyrics are identical. At 400 BPM an eighth note lasts 75 ms, which is the vowel floor, so the whole line is past the point where the written durations fit.

Tempo Eighth note Shortest consonant Audio
200 BPM150 ms23.0 ms
250 BPM120 ms22.8 ms
300 BPM100 ms22.7 ms
350 BPM86 ms30.0 ms
400 BPM75 ms30.0 ms

No consonant falls below 20 ms at any tempo, and the shortest one is longer at 350 and 400 BPM than at 300. Once the floors stop fitting, the span is shared between the phonemes that compete for it by weight, and with the default weights that means the vowel and each consonant come out the same length — so a plosive is not the one that gets crushed.

the generated phoneme durations at two tempos
The durations behind the first and last clips above: the same ten syllables at 200 and at 400 BPM, drawn on the same millisecond scale. At 200 BPM the consonants sit at their own lengths and the vowels take what is left. At 400 BPM nothing fits any more, so consonants and vowels come out at the same length — which is what keeps the line articulate instead of turning into a run of vowels.

Two knobs at synthesis time

The model does not output one pitch curve. It outputs an expression layer and a vibrato layer separately, so either can be scaled when you synthesize, without retraining and without touching the other. Below: the first phrase of Furusato sung by Namine Ritsu, with both knobs swept over ×0.0, ×0.5, ×1.0, ×1.5 and ×2.0 — 25 clips. At ×0.0 the layer is gone entirely: no expression means the score's pitch with nothing added, no vibrato means a steady tone. Same seed throughout, so the only difference is the scaling.

vibrato ×0.0 vibrato ×0.5 vibrato ×1.0 vibrato ×1.5 vibrato ×2.0
expression ×0.0
expression ×0.5
expression ×1.0
expression ×1.5
expression ×2.0
expression and vibrato scaled independently
Why the knobs are separate. Above: the F0 with the expression scaled — the scoops into notes, the transitions and the note tails all grow together, while the pitch of the notes themselves stays put. Below: the vibrato channel on its own, in cents. Overlaying all 25 combinations on one axis hides this: vibrato here is about ±40 cents at ×1.0, small next to the semitone-scale gestures above it.

Both are arguments to one call: render_phrase(ctx, phrase, plain_scale=1.5, vib_scale=0.5). They also accept a per-frame array instead of a number, so the strength can change over the course of a phrase — notebooks/03_f0_control.ipynb ramps the vibrato from nothing to double across a song.

What the F0 model produces

F0 is a sum of three layers in the log domain, plus a voiced/unvoiced channel. The baseline is built deterministically from the note pitches; the model generates the two layers on top of it, so the pitch follows the score while the expression is learned.

baseline, plain and vibrato layers of the F0
The first phrase of Furusato, sung by Namine Ritsu. Each panel adds one layer: the baseline staircase, then the expression, then the vibrato.

The repository's guide/ directory explains each stage, and the notebooks show how to change the expression and the vibrato at synthesis time, either as one number for the whole phrase or as a value that moves through it.