Skip to content

Fix: phase vocoder worklet#84

Open
bhj wants to merge 3 commits into
cutterbl:masterfrom
bhj:fix/phase-vocoder-worklet
Open

Fix: phase vocoder worklet#84
bhj wants to merge 3 commits into
cutterbl:masterfrom
bhj:fix/phase-vocoder-worklet

Conversation

@bhj

@bhj bhj commented Jul 12, 2026

Copy link
Copy Markdown

Hi, thanks for all your work. This fixes the issues I heard with the phase vocoder worklet, namely stereo image collapse and lowered volume when engaged. Results sound much better, more-or-less matching the previous best implementation I've heard (https://github.com/olvb/phaze)

Let me know if I missed anything.

Model used: Anthropic Fable

bhj and others added 3 commits July 12, 2026 13:57
…and exact OLA normalization

Four defects in the PhaseVocoder stretch stage combined to collapse the
stereo center and shift output level whenever tempo != 1:

- Inter-channel incoherence: L/R were processed with fully independent
  phase accumulators, destroying the inter-channel phase relationship
  that forms the stereo center. The recursion now runs once on the mid
  (L+R) spectrum — by FFT linearity the sum of the channel spectra, so
  no third FFT — and each channel resynthesizes as the shared mid
  synthesis phase plus its instantaneous offset from the mid, preserving
  the inter-channel difference exactly.

- Unbounded phase accumulation: synthesis phase grew without wrapping in
  float32 storage; high bins lose phase resolution within minutes of
  playback. The accumulator is now wrapped to (-pi, pi] every frame.

- Incorrect OLA normalization: the constant 2/overlapFactor assumed
  single Hann coverage, but the pipeline windows twice (analysis and
  synthesis), and the synthesis hop varies with tempo. Overlap-add is
  now normalized per sample by the accumulated squared-window sum
  (WOLA), exact for any hop including mid-stream tempo changes.

- Warmup phase poisoning: the recursion was seeded from the very first
  hop, when the analysis window is still mostly zeros, freezing the
  startup transient's arbitrary inter-bin phase relationships into the
  accumulators permanently — a constant, tempo-dependent level loss
  (e.g. -1.4 dB at tempo 2.0) even after the fixes above. Frames now
  pass through untouched until the analysis window is fully primed;
  the first fully-valid frame seeds the recursion.

Measured on stationary tones (fftSize 512, overlap 4): steady-state gain
is now 1.0000 at tempos 0.5 through 4.0 for bin-aligned and non-aligned
frequencies (previously 0.68-0.97, and 0.75 at identity). First-frame
output is now exactly normalized rather than attenuated by w^2/2; those
samples are analysis-priming zeros either way.

New DSP-correctness specs cover level at multiple tempos, DC
reproduction, inter-channel identity and fixed-offset preservation,
synthesis-phase boundedness over long runs, and mid-stream tempo
changes. Seven of them fail against the previous implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…revent broadband level loss

With the phase recursion running independently per bin, any Hs != Ha
re-randomizes the local phase structure that makes overlapping frames
of noise-like content sum consistently. The overlap-add then combines
incoherently, which for a Hann window at 75% overlap costs ~3.1 dB on
broadband material (consonants, drums, cymbals, reverb) while pure
tones pass at unity — measured -2.9 to -3.4 dB on white noise through
the stretch stage, matching listener reports of a ~3 dB drop on music.

This applies identity phase locking (Puckette 1995; Laroche & Dolson
1999) on the mid-channel reference: the recursion runs only at spectral
peaks of the mid magnitude, and every bin in a peak's region of
influence (bounded by midpoints between adjacent peaks) rotates rigidly
by that peak's synthesis rotation. Rigid rotation preserves the
analysis frame's local phase structure, keeping noise coherent under
overlap-add, and rotating both channels by the same angle preserves the
inter-channel phase difference exactly.

Phase state is now carried as complex spectra (previous analysis mid
and previous synthesized mid) instead of accumulated angles — complex
storage is inherently wrapped, so unbounded accumulation is impossible
by construction, and atan2/cos/sin run only at peak bins instead of
every bin.

Measured on white noise through the stretch stage at +/-2 and +5
semitone-equivalent tempos: -0.4 to -1.0 dB (previously -2.9 to -3.4).
Tones and harmonic stacks remain at 0.00 dB. New regression specs
assert broadband noise level within 1.5 dB in both stretch directions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…phase reference

A mid-only (L+R) phase reference is degenerate for anti-phase content:
the mid spectrum vanishes, its phase turns noisy, and the recursion's
rotation decoheres the overlap-add — measured -6 dB on an anti-phase
sine and -1.8 to -2.7 dB on anti-phase noise. Exactly out-of-phase
layers occur in real material (stereo-widening and phase tricks in
backing tracks), and the previous per-channel recursion handled them
coherently, so this was a regression of the mid-reference design.

The side (L-R) spectrum is as free as the mid by FFT linearity. Peak
finding now runs on per-bin energy (|mid|^2 + |side|^2, phase-agnostic)
and each peak's recursion uses whichever of mid/side is stronger at
that bin, keeping the reference well-conditioned for any inter-channel
correlation. Rigid region rotation is unchanged — rotating L and R by
the same angle rotates mid and side identically — so the exact
inter-channel phase guarantee is untouched.

Measured after the change: anti-phase sine 0.00 dB (was -6.03),
anti-phase noise -0.4 to -0.6 dB (was -1.8 to -2.7), identical to
regular noise; all previous levels unchanged. New spec asserts level
and exact polarity preservation for purely anti-phase channels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bhj bhj changed the title Fix/phase vocoder worklet Fix: phase vocoder worklet Jul 12, 2026
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