Quick answer: pitch_scale is resampling, not real pitch shifting. It changes speed as a side effect. For pitch-without-speed changes, route the player through an audio bus with an AudioEffectPitchShift effect.

Here is how to fix Godot AudioStreamPlayer pitch_scale affecting speed. You have a dialogue line and want a taller character to sound deeper without changing how long it takes to play. You drop pitch_scale to 0.8. The voice is deeper but now the line takes 25% longer to play, throwing off your timing. You want one thing (pitch) but the API gives you two (pitch + speed) coupled together. That is because pitch_scale is not pitch shifting — it is resampling, and these are fundamentally different audio operations.

The Symptom

Changing AudioStreamPlayer.pitch_scale to values other than 1.0 shifts pitch and speed together. pitch_scale = 0.5 plays the stream at half speed and one octave lower. pitch_scale = 2.0 plays it at double speed one octave higher. This matches how cassette tapes or vinyl records behave when you change their playback speed — correct behavior for some uses, wrong for others.

Affected uses: varied footstep sounds (want pitch variation, not variable footstep timing), dialogue variation, musical instruments triggered at different pitches, dynamic music layers that should stay in sync.

What Causes This

pitch_scale is resampling. Godot’s pitch_scale changes how quickly samples from the audio stream are read. Reading faster makes the audio higher-pitched and shorter; reading slower makes it lower-pitched and longer. The math is the simplest possible: effective_sample_rate = original_sample_rate * pitch_scale.

This is computationally cheap (no DSP required) and matches how physical audio equipment works. It is the correct default behavior for many uses — especially retro games where “sped-up audio” is a stylistic choice.

Real pitch shifting is expensive. Changing pitch without affecting speed requires a phase vocoder or similar time-stretching DSP algorithm. Godot implements this only through the AudioEffectPitchShift effect on an audio bus, not as a built-in per-player property. The effect has CPU cost and can introduce audio artifacts (warbling, chorus effects), which is why it is opt-in.

The Fix

Step 1: Decide which behavior you actually want. If you want stylized variation (footsteps, bullet impacts, UI sounds) where small pitch changes feel natural, pitch_scale in the range 0.9–1.1 is fine. The tiny speed change is imperceptible. If you need larger pitch changes with correct timing (dialogue, music layers), use the bus effect.

Step 2: Set up an AudioEffectPitchShift bus. Open the Audio panel (bottom-left tabs). Create a new bus named “Voice” or similar. Add an AudioEffectPitchShift effect to it. Set the effect’s pitch_scale initially to 1.0.

# In the editor Audio panel:
# 1. Click the [+] to add a bus
# 2. Name it "Voice"
# 3. Click the dropdown on the bus and Add Effect > Pitch Shift
# 4. Set the bus Send to Master

Then route your AudioStreamPlayer to that bus:

extends AudioStreamPlayer

func _ready():
    bus = "Voice"

func play_with_pitch(pitch: float):
    # Get the effect and set its pitch
    var bus_idx = AudioServer.get_bus_index("Voice")
    var effect = AudioServer.get_bus_effect(bus_idx, 0) as AudioEffectPitchShift
    effect.pitch_scale = pitch

    play()

Now pitch changes affect only the pitch, not playback speed. Voice lines take the same duration regardless of pitch setting.

Step 3: Consider per-player buses for independent pitch control. A single bus applies the same pitch to everything routed to it. For multiple simultaneous dialogues or instruments that each need different pitches, create a bus per character or instrument family. This is heavier but gives per-source control.

For highly dynamic needs (every procedural footstep needs its own pitch), a bus-per-source pattern does not scale. In that case, use pitch_scale directly and accept the speed coupling — which is usually imperceptible for short sounds anyway.

Step 4: For music layers, sync with AudioStreamSynchronized. If you need music tracks in different pitches to stay sample-synchronized, use AudioStreamSynchronized in Godot 4.2+. It plays multiple streams in lockstep, and you can route each to a bus with its own pitch shift effect.

Pitch Variation for SFX

A common pattern is randomizing pitch slightly on repeated sounds (footsteps, weapon fire) to avoid monotony. For this, pitch_scale is ideal — you want the small speed change:

func play_footstep():
    pitch_scale = randf_range(0.9, 1.1)
    play()

The 10% speed variation is inaudible for a 100ms footstep but the pitch variation breaks the “machine gun sound” of repeated identical samples.

3D Audio and Doppler

AudioStreamPlayer3D has a doppler_tracking property that applies Doppler pitch shift based on relative velocity between the emitter and listener. This is separate from pitch_scale — both apply, multiplicatively. If you manually set pitch_scale = 1.2 and Doppler also shifts up by 1.1x, the total shift is 1.32x.

For realistic 3D audio, leave pitch_scale = 1.0 and let Doppler do the work. For stylized effects, override with explicit pitch_scale and disable Doppler.

Performance

AudioEffectPitchShift costs roughly 5–15% of one CPU core per active bus at typical pitch ranges. Adding it to every voice line in a crowd scene is not viable. For wide pitch ranges on many sources, consider pre-rendering pitched variants offline and picking the right one at runtime — zero runtime cost, perfect quality.

“pitch_scale is cheap and wrong. AudioEffectPitchShift is correct and expensive. Pick based on how much the pitch change matters for timing.”

Related Issues

For tween-based audio fades, see Godot Tween Chain Stopping Midway. For broader audio mixing patterns, Unity AudioMixer Snapshot covers analogous patterns in Unity.

Resampling is fine for SFX. Real pitch shift for voice and music. Know which you need.