Python module to handle WAVE audio files. Drop-in replacement for Pythons wave.py
  • Python 99.7%
  • Makefile 0.3%
Find a file
Michiel Beijen b98ff87437
All checks were successful
ci/woodpecker/push/test/1 Pipeline was successful
ci/woodpecker/push/test/2 Pipeline was successful
ci/woodpecker/push/test/3 Pipeline was successful
ci/woodpecker/push/test/4 Pipeline was successful
ci/woodpecker/push/typing Pipeline was successful
ci/woodpecker/push/test/5 Pipeline was successful
Merge pull request 'Fix fact chunk generation' (#2) from fix-fact-chunk into main
Reviewed-on: #2
2026-03-08 18:21:13 +01:00
.woodpecker Add Woodpecker CI 2026-03-08 13:11:22 +01:00
examples examples: add custom chunk and multichannel demo artifacts 2026-03-08 14:47:46 +01:00
specs docs: add WAVE references and implementation notes 2026-03-08 14:47:46 +01:00
src Fix: use effective output format for fact logic 2026-03-08 13:14:58 +01:00
test Fix: use effective output format for fact logic 2026-03-08 13:14:58 +01:00
.gitignore Convert to module 2026-02-03 08:38:13 +01:00
CHANGES.md Update CHANGES, version bump 2026-02-19 08:13:19 +01:00
CUSTOM_CHUNKS.md Add support for adding/setting custom chunks 2026-02-14 16:41:51 +01:00
DEVELOPING.md docs: add WAVE references and implementation notes 2026-03-08 14:47:46 +01:00
header_image.png add header image 2026-02-12 22:59:48 +01:00
LICENSE.txt README: improve clarification on PSF2 license 2026-02-04 21:08:14 +01:00
Makefile Add Makefile as task runner 2026-02-03 08:38:13 +01:00
pyproject.toml Update for new ty version 2026-03-08 10:20:52 +01:00
README.md docs: add WAVE references and implementation notes 2026-03-08 14:47:46 +01:00
VERSIONING.md Release 0.3.0: Add changelog and dynamic versioning 2026-02-14 17:13:01 +01:00

newwave - drop-in replacement for wave.py

"Enjoy the silence"

newwave header

newwave is a drop-in replacement for Python's standard library wave module, which is used for reading and writing WAVE audio files.

It backports features from newer Python versions to older ones, so you can use the latest wave improvements on Python 3.10 and later.

Features

Features only in newwave:

  • IEEE float wave file support via getformat()/setformat() and WaveFormat enum (GH-60729, PR #102574)
  • WAVEFORMATEXTENSIBLE write support for >2 channels, >16-bit, and custom channel masks
  • ChannelMask IntFlag enum for discoverable speaker position configuration
  • LIST INFO metadata support (setmetadata(), gettag(), etc.)
  • Convenience wave.read() and wave.write() functions for simpler API
  • Public wave_params named tuple with format field for typed parameter handling (pending merge in python GH-94992)
  • Fix for Wave_write not writing pad byte for odd-sized data chunks (GH-117716)
  • Fix for setframerate() accepting values like 0.5 that round to invalid framerate 0 (GH-132445)

Features backported from newer Python versions:

  • Support for bytes and path-like objects in wave.open() (GH-140951, in Python since 3.15)
  • Fix for Wave_write emitting unraisable exception when open() raises (GH-136529, in Python since 3.15)
  • Removal of deprecated setmark, getmark, getmarkers interfaces (GH-105098, in Python since 3.15)
  • Support for reading WAVE_FORMAT_EXTENSIBLE files (GH-96777, in Python since 3.12)

Installation

pip install newwave

or with uv:

uv add newwave

Requires Python 3.10 or later.

Usage

Simply replace your wave import with newwave:

# Before
import wave

# After
import newwave as wave

Reading a WAVE file

import newwave as wave

# Using the convenience function
with wave.read('input.wav') as w:
    params = w.getparams()
    print(f"{params.nchannels} channels, {params.framerate} Hz, {params.sampwidth * 8}-bit")
    data = w.readframes(w.getnframes())

# Or using wave.open() (compatible with stdlib)
with wave.open('input.wav', 'rb') as w:
    data = w.readframes(w.getnframes())

Writing a WAVE file

import newwave as wave

# Using the convenience function (all params are keyword-only)
with wave.write('output.wav', channels=2, sampwidth=2, framerate=44100) as w:
    w.writeframes(audio_data)

# With IEEE float format
with wave.write('float.wav', channels=1, sampwidth=4, framerate=48000,
                format=wave.WaveFormat.IEEE_FLOAT) as w:
    w.writeframes(float_audio_data)

# Multichannel (>2 channels or >16-bit auto-uses WAVEFORMATEXTENSIBLE)
with wave.write('surround.wav', channels=6, sampwidth=3, framerate=48000,
                channel_mask=wave.ChannelMask.SURROUND_5_1) as w:
    w.writeframes(surround_audio_data)

# Or using wave.open() (compatible with stdlib)
with wave.open('output.wav', 'wb') as w:
    w.setnchannels(2)
    w.setsampwidth(2)
    w.setframerate(44100)
    w.writeframes(audio_data)

Metadata

newwave supports LIST INFO metadata for tagging audio files using the standard RIFF INFO chunk specification. Only the 13 predefined tags are supported:

title, artist, album, tracknumber, date, genre, comment, copyright, software, engineer, technician, source, subject, keywords

Attempting to use custom tag names will raise a ValueError to prevent silent data loss. For details on each tag, see the RIFF Interchange File Format Specification (Microsoft).

import newwave as wave

# Writing metadata
with wave.write('tagged.wav', channels=2, sampwidth=2, framerate=44100) as w:
    w.setmetadata({
        'title': 'My Song',
        'artist': 'The Artist',
        'album': 'The Album',
        'date': '2026',
        'genre': 'Electronic',
        'comment': 'Created with newwave',
    })
    w.writeframes(audio_data)

# Reading metadata
with wave.read('tagged.wav') as r:
    print(r.gettag('title'))    # 'My Song'
    print(r.getmetadata())       # {'title': 'My Song', 'artist': ...}

Encoding note: Metadata is written as UTF-8. Modern tools (ffprobe, Audacity) handle this correctly, but some legacy tools (mediainfo) may display non-ASCII characters incorrectly. For maximum compatibility with older software, use ASCII-only metadata.

Custom RIFF Chunks

Beyond the 13 standard LIST INFO tags, newwave supports arbitrary custom RIFF chunks for storing application-specific data. Custom chunks can be used to:

  • Store encoding parameters and processing history
  • Preserve metadata through FLAC encode/decode cycles
  • Store binary data (plugin state, MIDI, markers, etc.)
  • Attach application-specific information to WAVE files

Custom chunks are written after the audio data and LIST INFO metadata. Each chunk has a 4-byte identifier and arbitrary byte data.

import json
import newwave as wave

# Writing custom chunks
with wave.write('custom.wav', channels=2, sampwidth=2, framerate=44100) as w:
    w.writeframes(audio_data)

    # Store application metadata as JSON
    metadata = {'encoder': 'my-app/1.0', 'quality': 'lossless'}
    w.addchunk(b'APP\x00', json.dumps(metadata).encode('utf-8'))

    # Store binary data (e.g., processing parameters)
    w.addchunk(b'PROC', struct.pack('<ff', 0.5, 1.2))

# Reading custom chunks
with wave.read('custom.wav') as r:
    chunks = r.getchunks()  # dict of all custom chunks

    if b'APP\x00' in chunks:
        metadata = json.loads(chunks[b'APP\x00'].decode('utf-8'))
        print(f"Encoded by: {metadata['encoder']}")

For detailed examples and FLAC integration patterns, see CUSTOM_CHUNKS.md and examples/custom_chunks.py.

Examples

The examples/ directory contains runnable scripts demonstrating various features:

Run any example with:

PYTHONPATH=src python examples/metadata.py

API Reference

Functions

wave.read(filename) -> Wave_read

Open a WAVE file for reading. Returns a context manager.

wave.write(filename, *, channels=, sampwidth=, framerate=, format=WaveFormat.PCM, channel_mask=None) -> Wave_write

Open a WAVE file for writing. All parameters except filename are keyword-only. Returns a context manager.

  • channels: Number of audio channels (1-18 for standard layouts)
  • sampwidth: Sample width in bytes (1=8-bit, 2=16-bit, 3=24-bit, 4=32-bit)
  • framerate: Sample rate in Hz
  • format: WaveFormat.PCM or WaveFormat.IEEE_FLOAT
  • channel_mask: Speaker positions (int or ChannelMask). Use 0 for data channels without speaker assignment (e.g., lab equipment, sensor data).

Classes

Wave_read

Reader for WAVE files. Key methods:

Method Returns Description
getnchannels() int Number of audio channels
getsampwidth() int Sample width in bytes
getframerate() int Sample rate in Hz
getnframes() int Total number of frames
getformat() WaveFormat Format type (PCM, IEEE_FLOAT, EXTENSIBLE)
getparams() wave_params Named tuple with all parameters
readframes(n) bytes Read up to n frames
rewind() None Seek to start of audio data
setpos(pos) None Seek to frame position
tell() int Current frame position
getmetadata() dict All LIST INFO tags as a dictionary
gettag(tag) str/None Single tag value (tag: MetadataTag or str)
getchunk(chunk_id) bytes/None Custom RIFF chunk data or None if not found
getchunks() dict All custom chunks as {chunk_id: data}

Wave_write

Writer for WAVE files. Key methods:

Method Description
writeframes(data) Write audio frames and update header
writeframesraw(data) Write frames without updating header
tell() Return number of frames written
close() Finalize header and close file
setmetadata(dict) Set all metadata tags at once (keys: MetadataTag or str)
settag(tag, value) Set a single metadata tag (tag: MetadataTag or str)
gettag(tag) Get a metadata tag value, or None if not set
addchunk(chunk_id, data) Add a custom RIFF chunk (4-byte chunk_id, bytes data)
getchunk(chunk_id) Get custom chunk data or None if not found

When using wave.open() instead of wave.write(), use these setters before writing: setnchannels(), setsampwidth(), setframerate(), setformat(), setchannelmask().

Enums

WaveFormat (IntEnum)

Value Constant Description
0x0001 PCM Standard integer samples
0x0003 IEEE_FLOAT 32/64-bit floating point
0xFFFE EXTENSIBLE Extended format with channel mask

EXTENSIBLE is automatically used when channels > 2, sampwidth > 2, or channel_mask is set.

ChannelMask (IntFlag)

Individual speaker positions:

Flag Bit Description
FRONT_LEFT 0x1 Front left speaker
FRONT_RIGHT 0x2 Front right speaker
FRONT_CENTER 0x4 Front center speaker
LOW_FREQUENCY 0x8 LFE / subwoofer
BACK_LEFT 0x10 Back left speaker
BACK_RIGHT 0x20 Back right speaker
SIDE_LEFT 0x200 Side left speaker
SIDE_RIGHT 0x400 Side right speaker

Common configurations:

Preset Value Channels Layout
MONO 0x04 1 Front Center
STEREO 0x03 2 FL, FR
QUAD 0x33 4 FL, FR, BL, BR
SURROUND_5_1 0x3F 6 FL, FR, FC, LFE, BL, BR
SURROUND_7_1 0x63F 8 5.1 + SL, SR

Use channel_mask=0 for generic data channels without speaker assignment (e.g., scientific instruments, data loggers).

MetadataTag (Enum)

Standard LIST INFO metadata tags. Use for IDE autocomplete or strings for convenience:

import newwave as wave

# Using enum (recommended for IDE autocomplete)
with wave.write('output.wav', channels=2, sampwidth=2, framerate=44100) as w:
    w.settag(wave.MetadataTag.TITLE, 'My Song')
    w.settag(wave.MetadataTag.ARTIST, 'The Artist')
    # ...

# Using strings (still works)
with wave.write('output.wav', channels=2, sampwidth=2, framerate=44100) as w:
    w.settag('title', 'My Song')
    w.settag('artist', 'The Artist')
    # ...

Available tags in MetadataTag:

Enum Member Tag Name INFO Chunk Description
TITLE title INAM Track/song title
ARTIST artist IART Artist/performer
ALBUM album IPRD Album/product name
TRACKNUMBER tracknumber ITRK Track number
DATE date ICRD Creation date (YYYY or YYYY-MM-DD)
GENRE genre IGNR Genre
COMMENT comment ICMT Comments
COPYRIGHT copyright ICOP Copyright notice
SOFTWARE software ISFT Software used to create
ENGINEER engineer IENG Engineer
TECHNICIAN technician ITCH Technician
SOURCE source ISRC Source
SUBJECT subject ISBJ Subject
KEYWORDS keywords IKEY Keywords

Unknown tag names raise ValueError.

Relationship to CPython

newwave is not a fork in spirit, but a compatibility and incubation layer for Python’s standard-library wave module.

The goals of this project are to:

  • Backport features from newer Python releases to older supported versions.
  • Provide fixes for correctness issues in wave.py, such as malformed headers or incorrect frame padding.
  • Experiment with and stabilize enhancements (e.g. higher bit depths, extended formats) before proposing them upstream to CPython.

Where practical, fixes and features developed in newwave are intended to be upstreamed to CPython. Some enhancements may remain in newwave if they are not suitable for inclusion in the standard library due to backward-compatibility, policy, or maintenance constraints.

newwave aims to remain a drop-in replacement for wave, preserving the public API and behavior of the standard library wherever possible.

Authors

Because wave.py and its tests and supporting structure are taken from cpython, there is a long list of authors for this module. I used git-filter-repo to extract this module. This means that in the git repository, you can actually see the 'git blame' history just as you could on cpython. But python is very old at this point and in the first ten years or so many patches were sent in via email lists and so generating a full list of authors for this module from source control is not going to be working. So please consider the list of authors: everyone you find in source control for this repository, plus possibly everyone mentioned in cpython's AUTHORS!

The following contributors have patches in this repository that have not yet been merged to cpython:

  • Lionel Koenig - IEEE floating-point wave file support
  • Brendan Fahy - Public wave_params namedtuple API
  • Michiel W. Beijen - Write support for WAVEFORMATEXTENSIBLE, API improvements, bug fixes, and all other work on this project

License

Because the 'wave' library from Python stdlib is the starting point from this module, the license is the same as Python, meaning: PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2. The full license text from Python is distributed in LICENSE.txt in this release.

Getting help and discussion

For bugs, compatibility issues, feature ideas, or discussions related to this project and its relationship to CPython’s wave module, please open an issue on Codeberg. This is the preferred place so information stays discoverable.

Development

See DEVELOPING.md

Make WAVEs!