# wavfile.py (Enhanced) # Date: 20190213_2328 Joseph Ernest # # URL: https://gist.github.com/josephernest/3f22c5ed5dabf1815f16efa8fa53d476 # Source: scipy/io/wavfile.py # # Added: # * read: also returns bitrate, cue markers + cue marker labels (sorted), loops, pitch # See https://web.archive.org/web/20141226210234/http://www.sonicspot.com/guide/wavefiles.html#labl # * read: 24 bit & 32 bit IEEE files support (inspired from wavio_weckesser.py from Warren Weckesser) # * read: added normalized (default False) that returns everything as float in [-1, 1] # * read: added forcestereo that returns a 2-dimensional array even if input is mono # # * write: can write cue markers, cue marker labels, loops, pitch # * write: 24 bit support # * write: can write from a float normalized in [-1, 1] # * write: 20180430_2335: bug fixed when size of data chunk is odd (previously, metadata could become unreadable because of this) # # * removed RIFX support (big-endian) (never seen one in 10+ years of audio production/audio programming), only RIFF (little-endian) are supported # * removed read(..., mmap) # # # Test: # ..\wav\____wavfile_demo.py """ Module to read / write wav files using numpy arrays Functions --------- `read`: Return the sample rate (in samples/sec) and data from a WAV file. `write`: Write a numpy array as a WAV file. """ from __future__ import division, print_function, absolute_import import numpy import struct import warnings import collections from operator import itemgetter class WavFileWarning(UserWarning): pass _ieee = False # assumes file pointer is immediately # after the 'fmt ' id def _read_fmt_chunk(fid): res = struct.unpack(' 16): if (comp == 3): global _ieee _ieee = True # warnings.warn("IEEE format not supported", WavFileWarning) else: warnings.warn("Unfamiliar format bytes", WavFileWarning) if (size > 16): fid.read(size - 16) return size, comp, noc, rate, sbytes, ba, bits # assumes file pointer is immediately # after the 'data' id def _read_data_chunk(fid, noc, bits, normalized=False): size = struct.unpack('> 7) * 255 data = a.view(' 1: data = data.reshape(-1, noc) if bool(size & 1): # if odd number of bytes, move 1 byte further (data chunk is word-aligned) fid.seek(1, 1) if normalized: if bits == 8 or bits == 16 or bits == 24: normfactor = 2 ** (bits - 1) data = numpy.float32(data) * 1.0 / normfactor return data def _skip_unknown_chunk(fid): data = fid.read(4) size = struct.unpack('16, 23=>24 label = fid.read(size - 4) # .rstrip('\x00') # remove the trailing null characters # _cuelabels.append(label) _markersdict[id]['label'] = label # needed to match labels and markers elif chunk_id == b'smpl': str1 = fid.read(40) size, manuf, prod, sampleperiod, midiunitynote, midipitchfraction, smptefmt, smpteoffs, numsampleloops, samplerdata = struct.unpack(' 1.0] = 1.0 data[data < -1.0] = -1.0 a32 = numpy.asarray(data * (2 ** 23 - 1), dtype=numpy.int32) else: a32 = numpy.asarray(data, dtype=numpy.int32) if a32.ndim == 1: a32.shape = a32.shape + (1,) # Convert to a 2D array with a single column. a8 = (a32.reshape(a32.shape + (1,)) >> numpy.array([0, 8, 16])) & 255 # By shifting first 0 bits, then 8, then 16, the resulting output is 24 bit little-endian. data = a8.astype(numpy.uint8) else: if normalized: # default to 32 bit int data[data > 1.0] = 1.0 data[data < -1.0] = -1.0 data = numpy.asarray(data * (2 ** 31 - 1), dtype=numpy.int32) fid = open(filename, 'wb') fid.write(b'RIFF') fid.write(b'\x00\x00\x00\x00') fid.write(b'WAVE') # fmt chunk fid.write(b'fmt ') if data.ndim == 1: noc = 1 else: noc = data.shape[1] bits = data.dtype.itemsize * 8 if bitrate != 24 else 24 sbytes = rate * (bits // 8) * noc ba = noc * (bits // 8) fid.write(struct.pack('' or (data.dtype.byteorder == '=' and sys.byteorder == 'big'): data = data.byteswap() data.tofile(fid) if data.nbytes % 2 == 1: # add an extra padding byte if data.nbytes is odd: https://web.archive.org/web/20141226210234/http://www.sonicspot.com/guide/wavefiles.html#data fid.write('\x00') # Determine file size and place it in correct # position at start of the file. size = fid.tell() fid.seek(4) fid.write(struct.pack('