8000 Tune: shift a voice's formants with a FORM control by mparrett · Pull Request #43 · nooga/slab · GitHub
[go: up one dir, main page]

Skip to content

Tune: shift a voice's formants with a FORM control - #43

Open
mparrett wants to merge 2 commits into
nooga:masterfrom
mparrett:tune-formant
Open

mparrett wants to merge 2 commits into
nooga:masterfrom
mparrett:tune-formant

Conversation

@mparrett
@mparrett mparrett commented Nov 7, 2026
Copy link
Copy Markdown
Contributor

Tune keeps a voice's formants where they were, which is right for correction but leaves no way to change a voice's timbre. The usual recipe for a synthetic, GLaDOS-style voice is a hard snap (SPEED 0), flattened vibrato, the formants moved up a little, and a room reverb. Slab already had everything except the formant shift, and this PR adds it as FORM (±12 semitones) on the tune row.

How. TD-PSOLA reads each grain at the source's own speed. FORM reads it at φ = 2^(FORM/12) times that speed instead, so a grain's two source periods fit into 2·period/φ output samples. That moves the grain's spectrum, which is the voice's envelope, by φ. The grains stay a target period apart, so the pitch doesn't change. Read at another speed, neighbouring grains no longer line up, so they are normalized by energy (sqrt(hop/half)) rather than by amplitude. A formant down lengthens the grains, so on low voices it is held back to what fits in TUNE_REACH (at −12, below about 94 Hz). Unsung grains move too, so consonants match the vowels.

Saved files. A clip's "tune" gets "formant": n, which is left out at 0, so existing projects save byte-for-byte the same. slabkit: audio(…, tune="A", formant=3). docs/30 §Tune and the CHANGELOG are updated.

Tested on macOS (Apple Silicon) with zig 0.17.0. zig build test passes, including two new stretcher tests:

  • On a synthetic voice with a formant at 800 Hz, the envelope's peak harmonic moves to where 800·φ falls (+7 → harmonic 6, −7 → 3, +7 with a −2 st correction → 7). The pitch holds within 0.6%, and the level stays within 1 dB across ten FORM and pitch combinations, including FORM −12 with a −12 st transpose and FORM −6 either side of a −6 st pitch change.
  • A voice below 47 Hz renders at FORM −12 exactly as at FORM 0, since there is no room left to lengthen its grains.

End to end (slabkit, then the saved project, then slab --render) on a synthetic vowel, chromatic, SPEED 0:

FORM f0 spectral centroid RMS
0 182.5 Hz 1283 Hz −20.0 dBFS
+5 183.2 Hz 1528 Hz −20.8 dBFS
−5 181.8 Hz 1057 Hz −19.2 dBFS

Not checked: the FORM control in the running app's UI. It uses the same dragNum as SPEED and HUMAN on the tune row's own plate.

🤖 Generated with Claude Code

mparrett and others added 2 commits November 7, 2026 02:17
PSOLA reads each grain at base·φ, φ = 2^(FORM/12), so two source
periods land in 2·period/φ output samples: the grain's spectrum, the
voice's envelope, moves by φ while the hop keeps the pitch. Grains that
no longer overlap (formant up) and the extra overlap of a formant down
are gain-matched by energy, keeping the level within 1 dB.

Saved as "formant" in a clip's tune (omitted at 0); slabkit
audio(..., formant=); a FORM drag on the tune row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… §Tune)

The formant-down gain was sqrt(1/φ) on top of the overlap norm, right
only without a pitch change: with TRANSPOSE or a correction it added up
to 3 dB, and jumped as the pitch change crossed the formant's. Read at
another speed, grains add as energy, so the norm is sqrt(hop/half)
whenever FORM isn't 0.

A formant down lengthens the grain past TUNE_REACH on ordinary low
voices (under ~94 Hz at -12), cutting it short of two periods; φ is now
held to what fits, and never pushed up for voices already past it.

The ratio is worked out once a block, not once a grain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0