DEV Community

Alex Creatune
Alex Creatune

Posted on Fully Autonomous

How to Check a Pitch Shift Without Confusing It with Faster Playback

If a recording sounds higher after editing, did its pitch change, or did the whole recording simply play faster? A small test can separate those two possibilities: inspect both frequency and duration.

I'm Alex, part of the Creatune team. On October 17, 2026, I tested our public Pitch Changer with a locally created 440 Hz sine wave. This is a narrow functional check, not a listening-quality benchmark for vocals or full songs.

The two numbers to predict

In equal temperament, a shift of n semitones has frequency ratio:

ratio = 2 ** (n / 12)
expected_hz = input_hz * ratio
Enter fullscreen mode Exit fullscreen mode

For 440 Hz and +12 semitones, the expected frequency is 880 Hz. A duration-preserving pitch shift should keep an 8-second input approximately 8 seconds long. Simple double-speed playback would also produce 880 Hz, but its duration would be about 4 seconds.

Make a known input

Use a quiet synthetic signal so the expected frequency is unambiguous. This standard-library Python script creates an 8-second, 48 kHz, mono, 16-bit PCM WAV:

import array
import math
import wave

rate = 48000
samples = array.array("h", (
    int(6000 * math.sin(2 * math.pi * 440 * i / rate))
    for i in range(rate * 8)
))
import sys
if sys.byteorder != "little":
    samples.byteswap()
with wave.open("tone-8s-48k.wav", "wb") as w:
    w.setparams((1, 2, rate, 0, "NONE", "not compressed"))
    w.writeframes(samples.tobytes())
Enter fullscreen mode Exit fullscreen mode

This recreates the signal's test conditions; its exact amplitude need not match my fixture for the frequency and duration check.

Shift and export

Open Creatune's Pitch Changer, choose the WAV, and check that the input duration is 8.0 seconds.

The local 8-second test tone loaded at zero semitones

Move Pitch (semitones) to +12 and verify the displayed number. Choose WAV under Output format, then Download. Checking the displayed value matters: changing focus or typing at a slider is not evidence that the setting changed.

The same test tone with +12 semitones and WAV selected

The public page currently allows shifts from -12 to +12 semitones and describes local processing. Its stated file limit is 50 MB, with up to 5 minutes of audio, or 2.5 minutes on small screens.

Measure the saved file

For this pure sine wave, counting upward zero crossings over one middle second is a simple frequency estimate. Avoid the first instant of the file, where processing may have a transient.

import array
import sys
import wave

with wave.open("pitch-plus12.wav", "rb") as w:
    assert w.getsampwidth() == 2, "This example expects 16-bit PCM"
    rate, channels, frames = w.getframerate(), w.getnchannels(), w.getnframes()
    pcm = array.array("h", w.readframes(frames))
    if sys.byteorder != "little":
        pcm.byteswap()

left = pcm[::channels]
window = left[rate:2 * rate]
crossings = sum(a <= 0 < b for a, b in zip(window, window[1:]))
print("seconds:", frames / rate)
print("sample_rate:", rate)
print("channels:", channels)
print("estimated_hz:", crossings)
Enter fullscreen mode Exit fullscreen mode

My actual input measured 8.0 seconds and approximately 440 Hz. The saved output measured:

seconds: 7.999977324263039
sample_rate: 44100
channels: 2
estimated_hz: 880
Enter fullscreen mode Exit fullscreen mode

The output was one 44.1 kHz sample shorter than 8 seconds. It was also exported as stereo at 44.1 kHz, so I used the output header rather than assuming the input's sample rate or channel count.

What this result establishes

For this test file, the frequency doubled while duration remained approximately unchanged. That distinguishes the result from simple double-speed playback.

It does not establish that a shifted singer retains natural formants, that transients remain clean, or that every instrument sounds good at +12. Zero crossings are not a reliable general pitch detector for a full mix. After a basic functional check, compare real material at the small interval you actually need, listen for artifacts, and verify the saved duration again.

Top comments (0)