Skip to content

audio: phase_vocoder: re-anchor synthesis phase on speed change - #11058

Open
singalsu wants to merge 1 commit into
thesofproject:mainfrom
singalsu:phase_vocoder_speed_change_fix
Open

audio: phase_vocoder: re-anchor synthesis phase on speed change#11058
singalsu wants to merge 1 commit into
thesofproject:mainfrom
singalsu:phase_vocoder_speed_change_fix

Conversation

@singalsu

@singalsu singalsu commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

When the user changes the speed control the component preserves the polar analysis state across the reset so that the interpolation can continue smoothly, but the synthesis phase accumulator output_phase was left drifting. Each output IFFT in stft_do_fft_ifft() advances output_phase by an interpolated one-analysis-hop delta of the form (1 - frac) * angle_delta_prev + frac * angle_delta, where frac is the current interpolation position between two consecutive analysis frames. That delta is added irrespective of the current speed, so at non-unity speed output_phase runs faster than the true polar angle. On top of that, the first post-reset IFFT does not consume a new input FFT yet still applies its phase interpolation step (with frac = 0, so the added term equals angle_delta_prev), which adds one extra angle_delta_prev per reset. After a repeated excursion (for example 1.0 -> 0.5 -> 1.0 with several intermediate steps) both effects accumulate as a random per-bin phase offset that persists after returning to unity speed. Perceptually this smears transients and dulls the sound even though the audio is being processed at speed 1.0 again.

The steady-state invariant at speed 1.0 is that after each IFFT's phase interpolation step output_phase equals polar_prev.angle. To preserve this invariant across a speed change, re-anchor output_phase to polar_prev.angle minus angle_delta_prev in reset_for_new_speed(). The first post-reset IFFT then lands output_phase exactly on polar_prev.angle, and all subsequent IFFTs advance normally with no cumulative offset. Existing NULL and channel-count guards keep the init-time call from touching buffers before they are allocated.

When the user changes the speed control the component preserves the
polar analysis state across the reset so that the interpolation can
continue smoothly, but the synthesis phase accumulator output_phase
was left drifting. Each output IFFT in stft_do_fft_ifft() advances
output_phase by an interpolated one-analysis-hop delta of the form
(1 - frac) * angle_delta_prev + frac * angle_delta, where frac is
the current interpolation position between two consecutive analysis
frames. That delta is added irrespective of the current speed, so at
non-unity speed output_phase runs faster than the true polar angle.
On top of that, the first post-reset IFFT does not consume a new
input FFT yet still applies its phase interpolation step (with
frac = 0, so the added term equals angle_delta_prev), which adds one
extra angle_delta_prev per reset. After a repeated excursion (for
example 1.0 -> 0.5 -> 1.0 with several intermediate steps) both
effects accumulate as a random per-bin phase offset that persists
after returning to unity speed. Perceptually this smears transients
and dulls the sound even though the audio is being processed at
speed 1.0 again.

The steady-state invariant at speed 1.0 is that after each IFFT's
phase interpolation step output_phase equals polar_prev.angle. To
preserve this invariant across a speed change, re-anchor output_phase
to polar_prev.angle minus angle_delta_prev in reset_for_new_speed().
The first post-reset IFFT then lands output_phase exactly on
polar_prev.angle, and all subsequent IFFTs advance normally with no
cumulative offset. Existing NULL and channel-count guards keep the
init-time call from touching buffers before they are allocated.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Ready to approve

The change is small, well-guarded against uninitialized buffers, and addresses the described drift mechanism without introducing API or control-flow risk.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR fixes a phase drift issue in the phase vocoder when the user changes playback speed by re-anchoring the synthesis phase accumulator during speed resets, preventing cumulative per-bin phase offsets that persist after returning to unity speed.

Changes:

  • Preserve analysis-state continuity across speed changes while re-initializing synthesis output_phase to maintain the steady-state invariant at speed 1.0.
  • Adjust phase_vocoder_reset_for_new_speed() to set output_phase[ch][i] = unwrap(polar_prev.angle - angle_delta_prev) for all bins/channels with allocated state.
File summaries
File Description
src/audio/phase_vocoder/phase_vocoder_common.c Re-anchors synthesis phase during speed-change reset to prevent cumulative phase drift and post-reset offsets.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +247 to +249
* one extra angle_delta_prev on the first no-consume IFFT. If not done,
* both accumulate across with interactive speed changes as a per-bin
* phase offset which smears transients and dulls the sound.
@singalsu
singalsu marked this pull request as ready for review August 4, 2026 15:51
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.

2 participants