detly
detly

Reputation: 30322

How do I write a 24-bit WAV file in Python?

I want to generate a 24-bit WAV-format audio file using Python 2.7 from an array of floating point values between -1 and 1. I can't use scipy.io.wavfile.write because it only supports 16 or 32 bits. The documentation for Python's own wave module doesn't specify what format of data it takes.

So is it possible to do this in Python?

Upvotes: 16

Views: 15331

Answers (8)

Warren Weckesser
Warren Weckesser

Reputation: 114781

Another option is available in wavio (also on PyPI: https://pypi.python.org/pypi/wavio), a small module I created as a work-around to the problem of scipy not yet supporting 24 bit WAV files. The file wavio.py contains the function write, which writes a numpy array to a WAV file. To write a 24-bit file, use the argument sampwidth=3. The only dependency of wavio is numpy; wavio uses the standard library wave to deal with the WAV file format.

For example,

import numpy as np
import wavio
rate = 22050             # samples per second
T = 3                    # sample duration (seconds)
f = 440.0                # sound frequency (Hz)
t = np.linspace(0, T, T*rate, endpoint=False)
sig = np.sin(2 * np.pi * f * t)
wavio.write("sine24.wav", sig, rate, sampwidth=3)

Upvotes: 4

Matti Jokipii
Matti Jokipii

Reputation: 589

The solution from @detly works very well.

Calling writeframes once for each sample frame introduces huge overhead and makes the original solution very slow. Computing to an array and writing the data in a single call may yield better performance.

This is what i'm using:

import wave
from contextlib import closing
import struct
import numpy as np

INT24_FAC = (2**23)-1

def wavwrite_24(filename, fs, data):
    data_as_bytes = np.array(list(struct.pack('<i', x)[0:3] for x in (INT24_FAC * data).astype(int)))
    with closing(wave.open(filename, 'wb')) as wavwriter:
        wavwriter.setnchannels(1)
        wavwriter.setsampwidth(3)
        wavwriter.setframerate(fs)
        wavwriter.writeframes(data_as_bytes)

In my case the input data is an efficient numpy array if that makes any difference.

Upvotes: 0

Raza
Raza

Reputation: 189

Make use of ffmpeg to interchange between wav codecs, below is a sample code

command = "ffmpeg -i input.wav -ar 22050 output.wav"
subprocess.call(command, shell=True)

Upvotes: 0

Basj
Basj

Reputation: 46371

Here is an updated version of scipy.io.wavfile that adds:

  • 24 bit .wav files support for read/write,
  • access to cue markers,
  • cue marker labels,
  • some other metadata like pitch (if defined), etc.

wavfile.py (enhanced)

Upvotes: 0

Matthias
Matthias

Reputation: 4894

I already submitted an answer to this question 2 years ago, where I recommended scikits.audiolab.

In the meantime, the situation has changed and now there is a library available which is much easier to use and much easier to install, it even comes with its own copy of the libsndfile library for Windows and OSX (on Linux it's easy to install anyway): PySoundFile!

If you have CFFI and NumPy installed, you can install PySoundFile simply by running

pip install soundfile --user

Writing a 24-bit WAV file is easy:

import soundfile as sf
sf.write('my_24bit_file.wav', my_audio_data, 44100, 'PCM_24')

In this example, my_audio_data has to be a NumPy array with dtype 'float64', 'float32', 'int32' or 'int16'.

BTW, I made an overview page where I tried to compare many available Python libraries for reading/writing sound files.

Upvotes: 7

Matthias
Matthias

Reputation: 4894

You should try scikits.audiolab:

import numpy as np
from scikits.audiolab import Sndfile, Format

sig = np.array([0, 1, 0, -1, 0], dtype=np.float32)
f = Sndfile('test_pcm24.wav', 'w', Format('wav', 'pcm24'), 1, 44100)
f.write_frames(sig)
f.close()  # use contextlib.closing in real code

And to read it again:

f = Sndfile('test_pcm24.wav')
sig = f.read_frames(f.nframes, dtype=np.float32)
f.close()  # use contextlib.closing in real code

scikits.audiolab uses libsndfile, so in addition to WAV files, you can also use FLAC, OGG and some more file formats.

Upvotes: 2

detly
detly

Reputation: 30322

Using the wave module, the Wave_write.writeframes function expects WAV data to be packed into a 3-byte string in little-endian format. The following code does the trick:

import wave
from contextlib import closing
import struct

def wavwrite_24(fname, fs, data):
    data_as_bytes = (struct.pack('<i', int(samp*(2**23-1))) for samp in data)
    with closing(wave.open(fname, 'wb')) as wavwriter:
        wavwriter.setnchannels(1)
        wavwriter.setsampwidth(3)
        wavwriter.setframerate(fs)
        for data_bytes in data_as_bytes:
            wavwriter.writeframes(data_bytes[0:3])

Upvotes: 4

Roland Smith
Roland Smith

Reputation: 43495

Try the wave module:

In [1]: import wave

In [2]: w = wave.open('foo.wav', 'w') # open for writing

In [3]: w.setsampwidth(3) # 3 bytes/sample

Python can only pack integers in 2 and 4 bite sizes. So you can use a numpy array with a dtype on int32, and use a list comprehension to get 3/4 of the bytes of each integer:

In [14]: d = np.array([1,2,3,4], dtype=np.int32)

In [15]: d
Out[15]: array([1, 2, 3, 4], dtype=int32)

In [16]: [d.data[i:i+3] for i in range(0,len(d)*d.dtype.itemsize, d.dtype.itemsize)]
Out[16]: ['\x01\x00\x00', '\x02\x00\x00', '\x03\x00\x00', '\x04\x00\x00']

Upvotes: 3

Related Questions