uhhh tests (that was fast huh)
the commit message says it all. 961 lines added. 10 test files. plus a WAV encoder, a bunch of bug fixes, and the entire player got an upgrade. in one sitting.
tests
jest config, ts-jest, ESM mode. ten test files covering everything that exists:
-
envelope.test.ts: attack/release shape, mixBuffers offset and gain, edge cases -
filter.test.ts: FormantFilter resonator, FormantCascade -
noise.test.ts: NoiseSource output -
oscillator.test.ts: LFGlottalSource waveform shape -
wav.test.ts: encodeWav RIFF header -
english.test.ts: lexicon hits and fallback -
japanese.test.ts: hiragana, romaji, special cases -
renderer.test.ts: renderNote output shape, sample count, non-zero output -
stream.test.ts: streamScore chunk boundaries, mixChunks -
voices/index.test.ts: buildVoice, scaleVoice parameter ranges
writing tests found bugs. writing tests always finds bugs.
the bugs tests found
OW was missing. the English phoneme table had every ARPABET vowel except OW. “HELLO” ends with OW. the demo word was literally broken and I didn’t notice because the fallback produced silence instead of crashing. added it. F1=470 F2=1000 F3=2400.
anti-resonator formula was wrong. the pole radius was hardcoded to 0.99 with a random 0.95 frequency offset. now it derives the pole from bw * 1.5 like a real anti-resonator should. nasals sound less terrible.
jitter was per-sample. it was recalculating a random f0 every single sample, which made the pitch wobble chaotically instead of naturally. moved it to recalculate once per glottal cycle. much more realistic.
noise fadeout could go negative. phDur - segPos - 1 can be negative at the boundary. clamped to 0.
gain normalization could divide by zero. clamped overallPeak to 1e-6 and gain to max 100.
buildVoice spread order was wrong. ...overrides was before the sub-objects, so the glottal/formant/vibrato defaults always overwrote user values. flipped the order.
WAV encoder
(i’ve written manual WAV encoders before, I just copy-pasted that, it’s not that bad tbh)
62 lines. encodeWav() takes AudioChunks and writes a proper RIFF/WAVE file. 16-bit PCM, little-endian, handles mono and stereo. float-to-int16 conversion with clamping. exported from the barrel file.
player upgrades
setVolume() API. volume parameter on play(). progress events actually fire now (the type existed but was never emitted). stop does a 50ms gain ramp to zero before closing the AudioContext so it doesn’t click. scheduling uses Math.max(ctx.currentTime + 0.01, ...) to prevent scheduling in the past if rendering falls behind.
stream tempo handling
streamScore() was using a single currentTempo for the whole note. now it has tempoAt() for point lookups and noteSampleDuration() that integrates across tempo changes within a note. a note that spans a tempo change gets the right duration now.
also added a cascade reset between phonemes in the renderer (was carrying filter state across phoneme boundaries causing ringing), added a missing phoneme console.warn so you can actually debug G2P failures, and fixed the Japanese romaji parser to skip spaces instead of treating them as unknown consonants.
the TODO list is getting shorter. slowly but surely.
…and i forgot to update it oops
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.