123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- # 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()
|