# wavio.py # Author: Warren Weckesser # License: BSD 3-Clause (http://opensource.org/licenses/BSD-3-Clause) # Synopsis: A Python module for reading and writing 24 bit WAV files. # Github: github.com/WarrenWeckesser/wavio import wave as _wave import numpy as _np def _wav2array(nchannels, sampwidth, data): """data must be the string containing the bytes from the wav file.""" num_samples, remainder = divmod(len(data), sampwidth * nchannels) if remainder > 0: raise ValueError('The length of data is not a multiple of ' 'sampwidth * num_channels.') if sampwidth > 4: raise ValueError("sampwidth must not be greater than 4.") if sampwidth == 3: a = _np.empty((num_samples, nchannels, 4), dtype=_np.uint8) raw_bytes = _np.fromstring(data, dtype=_np.uint8) a[:, :, :sampwidth] = raw_bytes.reshape(-1, nchannels, sampwidth) a[:, :, sampwidth:] = (a[:, :, sampwidth - 1:sampwidth] >> 7) * 255 result = a.view('<i4').reshape(a.shape[:-1]) else: # 8 bit samples are stored as unsigned ints; others as signed ints. dt_char = 'u' if sampwidth == 1 else 'i' a = _np.fromstring(data, dtype='<%s%d' % (dt_char, sampwidth)) result = a.reshape(-1, nchannels) return result def readwav(file): """ Read a WAV file. Parameters ---------- file : string or file object Either the name of a file or an open file pointer. Return Values ------------- rate : float The sampling frequency (i.e. frame rate) sampwidth : float The sample width, in bytes. E.g. for a 24 bit WAV file, sampwidth is 3. data : numpy array The array containing the data. The shape of the array is (num_samples, num_channels). num_channels is the number of audio channels (1 for mono, 2 for stereo). Notes ----- This function uses the `wave` module of the Python standard libary to read the WAV file, so it has the same limitations as that library. In particular, the function does not read compressed WAV files. """ wav = _wave.open(file) rate = wav.getframerate() nchannels = wav.getnchannels() sampwidth = wav.getsampwidth() nframes = wav.getnframes() data = wav.readframes(nframes) wav.close() array = _wav2array(nchannels, sampwidth, data) return rate, sampwidth, array def writewav24(filename, rate, data): """ Create a 24 bit wav file. Parameters ---------- filename : string Name of the file to create. rate : float The sampling frequency (i.e. frame rate) of the data. data : array-like collection of integer or floating point values data must be "array-like", either 1- or 2-dimensional. If it is 2-d, the rows are the frames (i.e. samples) and the columns are the channels. Notes ----- The data is assumed to be signed, and the values are assumed to be within the range of a 24 bit integer. Floating point values are converted to integers. The data is not rescaled or normalized before writing it to the file. Example ------- Create a 3 second 440 Hz sine wave. >>> 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) >>> x = (2**23 - 1) * np.sin(2 * np.pi * f * t) >>> writewav24("sine24.wav", rate, x) """ a32 = _np.asarray(data, dtype=_np.int32) if a32.ndim == 1: # Convert to a 2D array with a single column. a32.shape = a32.shape + (1,) # By shifting first 0 bits, then 8, then 16, the resulting output # is 24 bit little-endian. a8 = (a32.reshape(a32.shape + (1,)) >> _np.array([0, 8, 16])) & 255 wavdata = a8.astype(_np.uint8).tostring() w = _wave.open(filename, 'wb') w.setnchannels(a32.shape[1]) w.setsampwidth(3) w.setframerate(rate) w.writeframes(wavdata) w.close()