Skip to content
Python Basics: Round-Trip PCM Samples with wave

Python Basics: Round-Trip PCM Samples with wave

October 30, 2026

WAV metadata describes how to interpret sample bytes. A tiny in-memory example is enough to check the relationship between channels, sample width, frames, and signed values without producing or playing an audio file.

Prepare a scratch directory

Tested on Linux with CPython 3.12.14. You need Bash, Python, a text editor, and the standard mktemp, rm, and rmdir utilities. No external packages are required. Run without -O so assertions remain enabled.

Keep the same terminal open. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as wave-pcm-demo.py:

import io
import struct
import wave

samples = (0, 1000, -1000, 32767, -32768)
payload = struct.pack("<5h", *samples)
with io.BytesIO() as buffer:
    with wave.open(buffer, "wb") as writer:
        writer.setnchannels(1)
        writer.setsampwidth(2)
        writer.setframerate(8000)
        writer.writeframes(payload)

    buffer.seek(0)
    with wave.open(buffer, "rb") as reader:
        print("Channels:", reader.getnchannels())
        print("Sample bytes:", reader.getsampwidth())
        print("Frames:", reader.getnframes())
        print("Rate:", reader.getframerate())
        first = reader.readframes(2)
        rest = reader.readframes(10)
        print("First samples:", struct.unpack("<2h", first))
        print("Remaining samples:", struct.unpack("<3h", rest))
        assert first + rest == payload
        print("End reached:", reader.readframes(1) == b"")

Run it:

python3 -B wave-pcm-demo.py

Observed output:

Channels: 1
Sample bytes: 2
Frames: 5
Rate: 8000
First samples: (0, 1000)
Remaining samples: (-1000, 32767, -32768)
End reached: True

Read the result

Each mono frame contains one two-byte sample, so five frames occupy ten payload bytes. The < prefix makes the signed 16-bit values explicitly little-endian. Asking for ten remaining frames returns only the three available frames; a subsequent read returns empty bytes.

Apply it carefully

For stereo, one frame contains one sample per channel, so frame count and total sample count differ. wave handles uncompressed PCM rather than decoding arbitrary audio formats. For incoming files, check channels, sample width, compression, and actual returned byte lengths before unpacking. Read large audio in bounded chunks. No sound device is used here.

The official wave reference documents the APIs used here.

Clean up

Remove the saved source, return to the starting directory, and remove the empty practice directory:

rm -- wave-pcm-demo.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on