theallelectricsmartgrid

Phase Vocoder

The phase-vocoder path is implemented by Resynthesizer (private/src/Resynthesis.hpp) and driven by GrainManager (private/src/DelayLine.hpp) inside the Quad Delay.

Why it is needed

The Quad Delay uses a moveable read head in warped time. Without correction, variable read speed causes strong pitch shifts.
The phase vocoder decouples time-stretch from perceived pitch so delay-time warping can happen while keeping spectral pitch stable.

Processing flow (code-level)

GrainManager::Process() launches a new grain every Resynthesizer::GetGrainLaunchSamples() samples, i.e. every hop H = N / 4 (x_hopDenom = 4).

For each launched grain:

  1. Read analysis buffers
    • Current frame: from warped-time read position (startTime).
    • Previous frame: from startTime - H.
    • Both are Hann-windowed in Grain::Start(...).
  2. Prime previous analysis
    • Resynthesizer::PrimeAnalysis(previousWaveTable) stores previous bin phases/magnitudes.
  3. Analyze current frame
    • DFT::Transform(buffer)
    • ProcessPhases() computes:
      • m_analysisMagnitudes[bin]
      • m_analysisPhase[bin]
      • instantaneous frequency m_omegaInstantaneous[bin] via phase-deviation formula:
        • subtract expected omega_bin * H
        • wrap with principal argument
        • add correction back to omega_bin
  4. PVDR phase-relationship build
    • PVDR::Analyze() builds leader/follower bin relationships.
  5. Synthesis phase update
    • Oscillator::FixupPhases(detune, pvdr) advances synthesis phase per PVDR result.
  6. Synthesize shifted/unison spectrum
    • Oscillator::Synthesize(...) writes complex bins with shift/unison gains.
  7. Inverse transform
    • InverseTransform(...) creates the grain waveform and grain->Start() schedules playback.

This is the “phase vocoder done right” principle in practice: phase propagation is controlled by measured inter-frame phase advance, not by naive phase reuse.

How PVDR is used

PVDR is the core phase-relationship tracker that prevents incoherent bin drift.

During synthesis:

Synthesis implementation

Each grain is synthesized by 3 oscillators (x_numOscillators = 3), each maintaining its own m_synthesisPhase[].

For each oscillator, Synthesize() mixes two shift layers (Oscillator::x_numShifts = 2):

Per-bin magnitude source is m_synthesisMagnitudes[bin].m_output, which is slewed to reduce abrupt spectral jumps.

Pitch shifting details

Pitch shift is ratio-based (Q rational values), selected upstream in QuadDelayInputSetter from musical intervals.

Oscillator::SynthesizeSingleShift():

So the shift is not “bin-by-bin naive remap”; it preserves PVDR structural relationships where possible.

Unison details

Unison is implemented as 3-oscillator energy-distributed layering:

This keeps output level controlled while adding symmetric spread.

In addition, delay modulation can inject per-channel sample offsets (sampleOffset) before analysis grain capture, which contributes to stereo/quad width and motion.

Notes on exposed controls

From QuadDelayInputSetter into Resynthesizer::Input:

m_slewDown exists in the input struct but the active code path currently applies SetSlewUp(...) during grain start.

Quad Delay integration

In the delay path:

This enables: