There’s an abundance of third-party tools and libraries for manipulating and analyzing audio WAV files in Python. At the same time, the language ships with the little-known wave module in its standard library, offering a quick and straightforward way to read and write such files. Knowing Python’s wave module can help you dip your toes into digital audio processing.
If topics like audio analysis, sound editing, or music synthesis get you excited, then you’re in for a treat, as you’re about to get a taste of them!
In this tutorial, you’ll learn how to:
- Read and write WAV files using pure Python
- Handle the 24-bit PCM encoding of audio samples
- Interpret and plot the underlying amplitude levels
- Record online audio streams like Internet radio stations
- Animate visualizations in the time and frequency domains
- Synthesize sounds and apply special effects
Although not required, you’ll get the most out of this tutorial if you’re familiar with NumPy and Matplotlib, which greatly simplify working with audio data. Additionally, knowing about numeric arrays in Python will help you better understand the underlying data representation in computer memory.
Click the link below to access the bonus materials, where you’ll find sample audio files for practice, as well as the complete source code of all the examples demonstrated in this tutorial:
Get Your Code: Click here to download the free sample code that shows you how to read and write WAV files in Python.
You can also take the quiz to test your knowledge and see how much you’ve learned:
Take the Quiz: Test your knowledge with our interactive “Reading and Writing WAV Files in Python” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Reading and Writing WAV Files in PythonIn this quiz, you can test your knowledge of handling WAV audio files in Python with the wave module. By applying what you've learned, you'll demonstrate your ability to synthesize sounds, analyze and visualize waveforms, create dynamic spectrograms, and enhance audio with special effects.
Understand the WAV File Format
In the early nineties, Microsoft and IBM jointly developed the Waveform Audio File Format, often abbreviated as WAVE or WAV, which stems from the file’s extension (.wav). Despite its older age in computer terms, the format remains relevant today. There are several good reasons for its wide adoption, including:
- Simplicity: The WAV file format has a straightforward structure, making it relatively uncomplicated to decode in software and understand by humans.
- Portability: Many software systems and hardware platforms support the WAV file format as standard, making it suitable for data exchange.
- High Fidelity: Because most WAV files contain raw, uncompressed audio data, they’re perfect for applications that require the highest possible sound quality, such as with music production or audio editing. On the flipside, WAV files take up significant storage space compared to lossy compression formats like MP3.
It’s worth noting that WAV files are specialized kinds of the Resource Interchange File Format (RIFF), which is a container format for audio and video streams. Other popular file formats based on RIFF include AVI and MIDI. RIFF itself is an extension of an even older IFF format originally developed by Electronic Arts to store video game resources.
Before diving in, you’ll deconstruct the WAV file format itself to better understand its structure and how it represents sounds. Feel free to jump ahead if you just want to see how to use the wave module in Python.
The Waveform Part of WAV
What you perceive as sound is a disturbance of pressure traveling through a physical medium, such as air or water. At the most fundamental level, every sound is a wave that you can describe using three attributes:
- Amplitude is the measure of the sound wave’s strength, which you perceive as loudness.
- Frequency is the reciprocal of the wavelength or the number of oscillations per second, which corresponds to the pitch.
- Phase is the point in the wave cycle at which the wave starts, not registered by the human ear directly.
The word waveform, which appears in the WAV file format’s name, refers to the graphical depiction of the audio signal’s shape. If you’ve ever opened a sound file using audio editing software, such as Audacity, then you’ve likely seen a visualization of the file’s content that looked something like this:

That’s your audio waveform, illustrating how the amplitude changes over time.
The vertical axis represents the amplitude at any given point in time. The midpoint of the graph, which is a horizontal line passing through the center, represents the baseline amplitude or the point of silence. Any deviation from this equilibrium corresponds to a higher positive or negative amplitude, which you experience as a louder sound.
As you move from left to right along the graph’s horizontal scale, which is the timeline, you’re essentially moving forward in time through your audio track.
Having such a view can help you visually inspect the characteristics of your audio file. The series of the amplitude’s peaks and valleys reflect the volume changes. Therefore, you can leverage the waveform to identify parts where certain sounds occur or find quiet sections that may need editing.
Coming up next, you’ll learn how WAV files store these amplitude levels in digital form.
The Structure of a WAV File
The WAV audio file format is a binary format that exhibits the following structure on disk:

As you can see, a WAV file begins with a header comprised of metadata, which describes how to interpret the sequence of audio frames that follow. Each frame consists of channels that correspond to loudspeakers, such as left and right or front and rear. In turn, every channel contains an encoded audio sample, which is a numeric value representing the relative amplitude level at a given point in time.
The most important parameters that you’ll find in a WAV header are:
- Encoding: The digital representation of a sampled audio signal. Available encoding types include uncompressed linear Pulse-Code Modulation (PCM) and a few compressed formats like ADPCM, A-Law, or μ-Law.
- Channels: The number of channels in each frame, which is usually equal to one for mono and two for stereo audio tracks, but could be more for surround sound recordings.
- Frame Rate: The number of frames per second, also known as the sampling rate or the sampling frequency measured in hertz. It affects the range of representable frequencies, which has an impact on the perceived sound quality.
- Bit Depth: The number of bits per audio sample, which determines the maximum number of distinctive amplitude levels. The higher the bit depth, the greater the dynamic range of the encoded signal, making subtle nuances in the sound audible.
Python’s wave module supports only the Pulse-Code Modulation (PCM) encoding, which is by far the most popular, meaning that you can usually ignore other formats. Moreover, Python is limited to integer data types, while PCM doesn’t stop there, defining several bit depths to choose from, including floating-point ones:
| Data Type | Signed | Bits | Min Value | Max Value |
|---|---|---|---|---|
| Integer | No | 8 | 0 | 255 |
| Integer | Yes | 16 | -32,768 | 32,767 |
| Integer | Yes | 24 | -8,388,608 | 8,388,607 |
| Integer | Yes | 32 | -2,147,483,648 | 2,147,483,647 |
| Floating-Point | Yes | 32 | ≈ -3.40282 × 1038 | ≈ 3.40282 × 1038 |
| Floating-Point | Yes | 64 | ≈ -1.79769 × 10308 | ≈ 1.79769 × 10308 |
In practice, the floating-point data types are overkill for most uses, as they require more storage while providing little return on investment. You probably won’t miss them unless you need extra dynamic range for truly professional sound editing.
Note: Despite dealing with an integer flavor of the PCM encoding, you’ll often want to represent the underlying audio samples as floating-point numbers internally in your source code. This will let you handle various bit depths uniformly and streamline the math behind audio processing.
After reading an audio sample into Python, you’ll typically normalize its value so that it always falls between -1.0 and 1.0 on the scale, regardless of the original range of PCM values. Then, before writing it back to a WAV file, you’ll convert and clamp the value to make it fit the desired range.
The 8-bit integer encoding is the only one relying on unsigned numbers. In contrast, all remaining data types allow both positive and negative sample values.
Another important detail you should take into account when reading audio samples is the byte order. The WAV file format specifies that multi-byte values are stored in little-endian or start with the least significant byte first. Fortunately, you don’t typically need to worry about it when you use the wave module to read or write audio data in Python. Still, there might be edge cases when you do!
The 8-bit, 16-bit, and 32-bit integers have standard representations in the C programming language, which the default CPython interpreter builds on. However, the 24-bit integer is an outlier without a corresponding built-in C data type. Exotic bit depths like that aren’t unheard of in music production, as they help strike a balance between size and quality. Later, you’ll learn how to emulate them in Python.
To faithfully represent music, most WAV files use stereo PCM encoding with 16-bit signed integers sampled at 44.1 kHz or 44,100 frames per second. These parameters correspond to the standard CD-quality audio. Coincidentally, such a sampling frequency is roughly double the highest frequency that most humans can hear. According to the Nyquist–Shannon sampling theorem, that’s sufficient to capture sounds in digital form without distortion.
Now that you know what’s inside a WAV file, it’s time to load one into Python!
Get to Know Python’s wave Module
The wave module takes care of reading and writing WAV files but is otherwise pretty minimal. It was implemented in about five hundred lines of pure Python code, not counting the comments. Perhaps most importantly, you can’t use it for audio playback. To play a sound in Python, you’ll need to install a separate library.
Note: Although the wave module itself doesn’t support audio playback, you can still listen to your WAV files as you work through this tutorial. Use the media player that came with your operating system or any third-party application such as VLC.
As mentioned earlier, the wave module only supports four integer-based, uncompressed PCM encoding bit depths:
- 8-bit unsigned integer
- 16-bit signed integer
- 24-bit signed integer
- 32-bit signed integer
To experiment with Python’s wave module, you can download the supporting materials, which include a few sample WAV files encoded in these formats.
Read WAV Metadata and Audio Frames
If you haven’t already grabbed the bonus materials, then you can download a sample recording of a Bongo drum directly from Wikimedia Commons to get started. It’s a mono audio sampled at 44.1 kHz and encoded in the 16-bit PCM format. The file is in the public domain, so you can freely use it for personal or commercial purposes without any restrictions.
To load this file into Python, import the wave module and call its open() function with a string indicating the path to your WAV file as the function’s argument:
>>> import wave
>>> with wave.open("Bongo_sound.wav") as wav_file:
... print(wav_file)
...
<wave.Wave_read object at 0x7fc07b2ab950>
When you don’t pass any additional arguments, the wave.open() function, which is the only function that’s part of the module’s public interface, opens the given file for reading and returns a Wave_read instance. You can use that object to retrieve information stored in the WAV file’s header and read the encoded audio frames:
>>> with wave.open("Bongo_sound.wav") as wav_file:
... metadata = wav_file.getparams()
... frames = wav_file.readframes(metadata.nframes)
...
>>> metadata
_wave_params(
nchannels=1,
sampwidth=2,
framerate=44100,
nframes=212419,
comptype='NONE',
compname='not compressed'
)
>>> frames
b'\x01\x00\xfe\xff\x02\x00\xfe\xff\x01\x00\x01\x00\xfe\xff\x02\x00...'
>>> len(frames)
424838
You can conveniently get all parameters of your WAV file into a named tuple with descriptive attributes like .nchannels or .framerate. Alternatively, you can call the individual methods, such as .getnchannels(), on the Wave_read object to cherry-pick the specific pieces of metadata that you’re interested in.
The underlying audio frames get exposed to you as an unprocessed bytes instance, which is a really long sequence of unsigned byte values. Unfortunately, you can’t do much beyond what you’ve seen here because the wave module merely returns the raw bytes without providing any help in their interpretation.
Your sample recording of the Bongo drum, which is less than five seconds long and only uses one channel, comprises nearly half a million bytes! To make sense of them, you must know the encoding format and manually decode those bytes into integer numbers.
According to the metadata you’ve just obtained, there’s only one channel in each frame, and each audio sample occupies two bytes or sixteen bits. Therefore, you can conclude that your file’s been encoded using the PCM format with 16-bit signed integers. Such a representation would correspond to the signed short data type in C, which doesn’t exist in Python.
While Python doesn’t directly support the required data type, you can use the array module to declare an array of signed short numbers and pass your bytes object as input. In this case, you want to use the lowercase letter "h" as the array’s type code to tell Python how to interpret the frames’ bytes:
>>> import array
>>> pcm_samples = array.array("h", frames)
>>> len(pcm_samples)
212419
Notice that the resulting array has exactly half the number of elements than your original sequence of bytes. That’s because each number in the array represents a 16-bit or two-byte audio sample.
Another option at your fingertips is the struct module, which lets you unpack a sequence of bytes into a tuple of numbers according to the specified format string:
>>> import struct
>>> format_string = "<" + "h" * (len(frames) // 2)
>>> pcm_samples = struct.unpack(format_string, frames)
>>> len(pcm_samples)
212419
The less-than symbol (<) in the format string explicitly indicates little-endian as the byte order of each two-byte audio sample (h). In contrast, an array implicitly assumes your platform’s byte order, which means that you may need to call its .byteswap() method when necessary.
Finally, you can use NumPy as an efficient and powerful alternative to Python’s standard library modules, especially if you already work with numerical data:
>>> import numpy as np
>>> pcm_samples = np.frombuffer(frames, dtype="<h")
>>> normalized_amplitudes = pcm_samples / (2 ** 15)
By using a NumPy array to store your PCM samples, you can leverage its element-wise vectorized operations to normalize the amplitude of the encoded audio signal. The highest magnitude of an amplitude stored on a signed short integer is negative 32,768 or -215. Dividing each sample by 215 scales them to a right-open interval between -1.0 and 1.0, which is convenient for audio processing tasks.
That brings you to a point where you can finally start performing interesting tasks on your audio data in Python, such as plotting the waveform or applying special effects. Before you do, however, you should learn how to save your work to a WAV file.
Write Your First WAV File in Python
Knowing how to use the wave module in Python opens up exciting possibilities, such as sound synthesis. Wouldn’t it be great to compose your own music or sound effects from scratch and listen to them? Now, you can do just that!
Mathematically, you can represent any complex sound as the sum of sufficiently many sinusoidal waves of different frequencies, amplitudes, and phases. By mixing them in the right proportions, you can recreate the unique timbre of different musical instruments playing the same note. Later, you may combine a few musical notes into chords and use them to form interesting melodies.
Here’s the general formula for calculating the amplitude A(t) at time instant t of a sine wave with frequency f and phase shift φ, whose maximum amplitude is A:

When you scale your amplitudes to a range of values between -1.0 and 1.0, then you can disregard the