You are browsing as a guest. Sign up (or log in) to start making projects!

3h 10m 33s logged

567,211 lines of “code”

so about that line count. I added the CMU Pronouncing Dictionary as a git submodule. 134,000+ English words with their ARPABET pronunciations. the cmudict repo is… large. like, genuinely enormous. the diff says 567k lines and it’s almost entirely a text file of words and their phoneme sequences.

the actual code changes across these four commits? probably 280 lines. the submodule? 567,131 lines. software engineering.


the real G2P

Build/data/cmudict is now a submodule pointing to cmusphinx/cmudict. this is THE pronunciation dictionary. the one that every TTS and speech recognition system uses. “BEAUTIFUL” -> “B Y UW1 T AH0 F AH0 L”. “SQUIRREL” -> “S K W ER1 AH0 L”. every word, every pronunciation variant, every stress marker.

the 100-word hand-rolled dictionary was cute. this is the real thing. (chunk size warning in vite went from 500 to 13000 because bundling the dictionary is… yeah.)


glottal pulse fix

the LF glottal source had an audible click on every single cycle. the return phase (after the glottis closes) was a bare exponential decay: -exp(-epsilon * x * period). the problem: it starts at -1 with non-zero slope, so at the boundary between cycles there’s a discontinuity. click. every. cycle.

fix: multiply by sin(pi * x). the sine term makes the slope match the open phase at t=te (C1 continuity) and reach 0 at t=tc. smooth transition. no click. this is how the actual LF model paper describes it but I’d skipped the sine modulation the first time around because I thought it was just cosmetic. it was not cosmetic.

also killed the 0.8/0.2 smoothing filter. it was supposed to prevent clicks but was actually just low-passing the glottal pulse and adding phase distortion. the DC blocker handles drift on its own. coefficient went from 0.995 to 0.99 for faster response.


renderer performance

formant coefficient updates were happening every sample. that’s setFormants() with trig functions 44,100 times per second. formants don’t actually change that fast. throttled to every 5ms (221 samples at 44.1k). no audible difference, meaningful CPU savings.

cascade reset came back at phoneme boundaries. I removed it two commits ago thinking filter state carryover was smoother. it is smoother, but it also causes wideband clicks when the coefficients jump. the reset is the right call.


aspiration lowpass

aspiration noise on vowels was full-spectrum white noise. real breathy voice has most of its noise energy below 4kHz. added a one-pole lowpass at 4kHz on the aspiration path. sounds warmer and less harsh. also re-added parallel filter resets at phoneme boundaries (matched the cascade reset fix).


signal quality tests

new test category: signal quality. a long 8-beat note must stay in [-1, 1], be finite throughout, and not be dominated by white noise. the “not white noise” check uses a crude HF proxy: ratio of RMS(sample-to-sample difference) to RMS(signal) in the steady state portion. pure white noise scores ~1.4, a clean tonal signal scores ~0.2. test asserts between 0.05 and 0.8.

wrote these tests because the glottal fix could have easily introduced NaN propagation or DC drift and I wanted to catch it automatically.

0
3

Comments 0

No comments yet. Be the first!