WAV files carry audio samples along with the sample rate, channel layout, and numeric dtype that downstream signal code needs. scipy.io.wavfile fits small Python audio workflows where samples should move into a NumPy array, be adjusted, and return to an uncompressed WAV file.
wavfile.read() returns the sample rate and an array. A mono WAV loads as a one-dimensional array, while stereo and other multichannel files load as rows of samples with one column per channel.
wavfile.write() chooses the WAV sample format from the array dtype, so dtype conversion is part of the export decision. A short generated stereo tone keeps the round trip easy to inspect because it remains int16 data, reduces amplitude in integer space, writes a second file, and reloads the result.
Related: Compute an FFT with SciPy
Related: Create a spectrogram with SciPy
Related: Resample a signal with SciPy
Steps to read and write WAV files with SciPy:
- Create a script named wav_file_read_write.py.
- wav_file_read_write.py
import numpy as np from scipy.io import wavfile sample_rate = 8000 seconds = 0.02 time = np.arange(int(sample_rate * seconds)) / sample_rate left = 0.4 * np.sin(2 * np.pi * 440 * time) right = 0.4 * np.sin(2 * np.pi * 660 * time) stereo = np.column_stack((left, right)) pcm = np.round(stereo * np.iinfo(np.int16).max).astype(np.int16) wavfile.write("source.wav", sample_rate, pcm) rate, data = wavfile.read("source.wav") quiet = (data.astype(np.int32) // 2).astype(np.int16) wavfile.write("quiet.wav", rate, quiet) check_rate, check = wavfile.read("quiet.wav") print(f"source rate: {rate}") print(f"source dtype: {data.dtype}") print(f"source shape: {data.shape}") print(f"source sample row 1: {data[1].tolist()}") print(f"written rate: {check_rate}") print(f"written dtype: {check.dtype}") print(f"written shape: {check.shape}") print(f"written sample row 1: {check[1].tolist()}") print(f"amplitude reduced: {np.max(np.abs(check)) < np.max(np.abs(data))}") print(f"round trip exact: {np.array_equal(check, quiet)}")
The 2-D array shape is (samples, channels). The int16 dtype makes wavfile.write() create a 16-bit PCM WAV file.
- Run the script.
$ python3 wav_file_read_write.py source rate: 8000 source dtype: int16 source shape: (160, 2) source sample row 1: [4440, 6494] written rate: 8000 written dtype: int16 written shape: (160, 2) written sample row 1: [2220, 3247] amplitude reduced: True round trip exact: True
The reloaded file keeps the same sample rate, dtype, and stereo shape. The sample row is half the original amplitude, and the final equality check confirms that the written WAV reloaded as the same array that was passed to wavfile.write().
- Read an existing WAV file from the project.
rate, data = wavfile.read("recording.wav") print(rate, data.dtype, data.shape)
Inspect data.dtype before scaling values. wavfile.read() returns 8-bit PCM as uint8, 16-bit PCM as int16, and 24-bit PCM in a left-justified int32 array.
- Convert floating-point processing output before writing 16-bit PCM.
processed = np.clip(processed_float, -1.0, 1.0) processed_pcm = np.round(processed * np.iinfo(np.int16).max).astype(np.int16)
wavfile.write() uses the dtype it receives. Passing float32 writes floating-point WAV data, while passing int16 writes 16-bit PCM.
- Preserve the sample rate when writing processed audio.
wavfile.write("processed.wav", rate, processed_pcm)
The rate argument is samples per second. Reusing the input rate keeps playback speed unchanged unless the audio has been resampled separately.
Related: Resample a signal with SciPy - Remove the demo files after adapting the pattern.
$ rm wav_file_read_write.py source.wav quiet.wav
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.