The Pitch node changed the playback speed, so a sound pitched up was
also shorter and one pitched down longer. A pitch now keeps the
length, takes fractions and reaches further. Gain and the EQ bands
reach further down, and the EQ is renamed so a parametric one can
come later without a mix-up.
- Pitch shifting is the pure function pitchShift in
core/soundfx/pitchShift.ts. It reads the sound faster or slower and
stretches it back by WSOLA: 40 ms frames under a Hann window, each
moved within 12.5 ms to where it continues the frame before it.
A looping sound reads on across its end.
- SoundLibrary plays a pitched copy of the decoded buffer instead of
setting playbackRate. Copies are kept per file, pitch and loop, the
last 32 of them.
- stats().last tells the duration of what was played, so a check can
see that the length is kept.
- Pitch: -64 to +64 semitones, scrubbed in steps of 0.1 (#312).
- Gain: -120 to +24 dB (#313).
- EQ is now labelled "5-Band-EQ", its bands go -60 to +24 dB (#314).
The node type stays SoundFxEq, so saved graphs load unchanged.
Findings:
- A plain granular shifter was tried first and missed the pitch: at
+0.5 semitones it measured 438 Hz instead of 452.9 Hz, because every
grain restarts at the phase of the original. WSOLA measures 452 Hz.
- Far ends: +64 semitones from 440 Hz lands above half the sample rate
and folds back, -64 is below what a 40 ms frame can follow. Nobody
has listened to either.
- Speed: 3 s of a 48 kHz channel take 47 ms at +3 and +64, 171 ms
at -64 (measured in Node).
Tests:
- pitch-shift (new): 7 tests. A 440 Hz sine at +12, -12, +7, +0.5 and
-30.5 semitones is within 2 % of the target and keeps its length.
Level stays at most 1, a loop does not fade at its seam.
- sound-library: 10 tests (+1), soundfx 22, node-catalog 368.
- Counter-check: with the pitched copy switched off the new
sound-library test fails.
- e2e 34 (melektaus-atx): ALL PASSED - 28 checks. Its octave-down
voice is now also checked for the length of the sound.
- Typecheck, ESLint and Prettier clean. The gate did not run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>