import wave as wavefile import struct def interpolate_samples(wave, oldrate, rate): if oldrate == rate: return wave def wave_readsamples(audio): # rewind to the start of the wav file audio.rewind() # read all of the samples from the file (in a packed form) num_frames = audio.getnframes() packed_samples = audio.readframes(num_frames) num_samples = num_frames * audio.getnchannels() # unpack the data into a python list if audio.getsampwidth() == 1: unpacked_samples = struct.unpack(str(num_samples)+'b', packed_samples) unpacked_samples = [x * 256 for x in unpacked_samples] elif audio.getsampwidth() == 2: unpacked_samples = struct.unpack(str(num_samples)+'h', packed_samples) else: raise Exception("Sorry, audiolib only supports 8 or 16 bit audio, but this wav file has %d bit audio" % (8*audio.getsampwidth())) if audio.getnchannels() == 2: unpacked_samples = [(unpacked_samples[i] + unpacked_samples[i+1])/2 for i in range(0, num_samples, 2)] elif not audio.getnchannels() == 1: raise Exception("Sorry, audiolib only supports mono or stereo audio, but this wav file has %d channels" % (audio.getnchannels())) return unpacked_samples wavefile.Wave_read.readsamples = wave_readsamples def plot_wave(samples): import pylab pylab.ylim( -32768, 32767 ) pylab.plot(samples, 'b-') # plot the samples with blue lines pylab.plot(samples, 'b-') # plot the samples with red dots pylab.show() def wave_plot_graph(audio): samples = audio.readsamples() plot_wave(samples) wavefile.Wave_read.plot_wave = wave_plot_graph def read_wave_file(name): audio = wavefile.open(name, "r") num_samples = audio.getnframes() playback_rate = audio.getframerate() if not playback_rate == 44100: print "Warning: file had a non-standard playback rate (%d samples/sec instead of 44100 samples/sec)..." % playback_rate print "It will sound much too fast or slow when playing it back at 44100 samples/sec." duration = float(num_samples) / playback_rate print "Opened '%s': %f sec of audio (%d samples at %d samples/sec)" % (name, duration, num_samples, playback_rate) samples = audio.readsamples() audio.close() return samples # The following code for playing raw audio on Windows in pure Python (e.g. without pygame or other libraries) # was adapted by kwalsh, based very closely on code from here: # https://bitbucket.org/techtonik/audiosocket/src/bfbd201b498d/audiosocket.py """ Implementation of Raw Audio Socket server spec in pure Python http://code.google.com/p/rainforce/wiki/RawAudioSocket Public domain work by anatoly techtonik Use MIT License if public domain doesn't make sense for you. Change History: 0.1 - proof of concept, loads and plays entire data file in one turn, uses predefined sleep interval of one second to avoid 100% CPU usage when checking if playback is complete 0.2 - loads data piece by piece, plays with noticeable lags due to the absence of buffering, 100% CPU usage, because sleep interval is undefined 0.3 - organize code into AudioWriter class 0.4 - playback lag is killed by double buffering, still 100% CPU usage because of constant polling to check for processed blocks """ import sys DEBUG = False def debug(msg): if DEBUG: print "debug: %s" % msg #-- CHAPTER 1: CONTINUOUS SOUND PLAYBACK WITH WINDOWS WINMM LIBRARY -- # # Based on tutorial "Playing Audio in Windows using waveOut Interface" # by David Overton import ctypes from ctypes import wintypes winmm = ctypes.windll.winmm # --- define necessary data structures from mmsystem.h HWAVEOUT = wintypes.HANDLE WAVE_FORMAT_PCM = 0x1 WAVE_MAPPER = -1 MMSYSERR_NOERROR = 0 class WAVEFORMATEX(ctypes.Structure): _fields_ = [ ('wFormatTag', wintypes.WORD), # 0x0001 WAVE_FORMAT_PCM. PCM audio # 0xFFFE The format is specified in the WAVEFORMATEXTENSIBLE.SubFormat # Other values are in mmreg.h ('nChannels', wintypes.WORD), ('SamplesPerSec', wintypes.DWORD), ('AvgBytesPerSec', wintypes.DWORD), # for WAVE_FORMAT_PCM is the product of nSamplesPerSec and nBlockAlign ('nBlockAlign', wintypes.WORD), # for WAVE_FORMAT_PCM is the product of nChannels and wBitsPerSample # divided by 8 (bits per byte) ('wBitsPerSample', wintypes.WORD), # for WAVE_FORMAT_PCM should be equal to 8 or 16 ('cbSize', wintypes.WORD)] # extra format information size, should be 0 # Data must be processed in pieces that are multiple of # nBlockAlign bytes of data at a time. Written and read # data from a device must always start at the beginning # of a block. Playback of PCM data can not be started in # the middle of a sample on a non-block-aligned boundary. CALLBACK_NULL = 0 PVOID = wintypes.HANDLE WAVERR_BASE = 32 WAVERR_STILLPLAYING = WAVERR_BASE + 1 class WAVEHDR(ctypes.Structure): _fields_ = [ ('lpData', wintypes.LPSTR), # pointer to waveform buffer ('dwBufferLength', wintypes.DWORD), # in bytes ('dwBytesRecorded', wintypes.DWORD), # when used in input ('dwUser', wintypes.DWORD), # user data ('dwFlags', wintypes.DWORD), # various WHDR_* flags set by Windows ('dwLoops', wintypes.DWORD), # times to loop, for output buffers only ('lpNext', PVOID), # reserved, struct wavehdr_tag *lpNext ('reserved', wintypes.DWORD)] # reserved # The lpData, dwBufferLength, and dwFlags members must be set before calling # the waveInPrepareHeader or waveOutPrepareHeader function. (For either # function, the dwFlags member must be set to zero.) WHDR_DONE = 1 # Set by the device driver for finished buffers # --- /define ---------------------------------------- # -- Notes on double buffering scheme to avoid lags -- # # Windows maintains a queue of blocks sheduled for playback. # Any block passed through the waveOutPrepareHeader function # is inserted into the queue with waveOutWrite. class AudioWriter(object): def __init__(self, nChannels, nSamplesPerSecond): if nChannels not in [1, 2]: raise Exception("nChannels must be either 1 (for mono) or 2 (for stereo)") if nSamplesPerSecond not in [8000, 11025, 22050, 44100]: print "warning: nSamplesPerSecond is typically 8000, 11025, 22050, or 44100, but you are using " + nSamplesPerSecond nBitsPerSample = 16 self.hwaveout = HWAVEOUT() self.wavefx = WAVEFORMATEX( WAVE_FORMAT_PCM, nChannels, nSamplesPerSecond, nSamplesPerSecond * nBitsPerSample/1 * nChannels, # avgBytesPerSecond nBitsPerSample/8 * nChannels, # nBlockAlign nBitsPerSample, 0 ) # For gapless playback, we schedule two audio blocks at a time, each # block with its own header self.headers = [WAVEHDR(), WAVEHDR()] #: configurable size of chunks (data blocks) read from input stream self.CHUNKSIZE = 100 * 2**10 def open(self): """ 1. Open default wave device, tune it for the incoming data flow """ ret = winmm.waveOutOpen( ctypes.byref(self.hwaveout), # buffer to receive a handle identifying # the open waveform-audio output device WAVE_MAPPER, # constant to point to default wave device ctypes.byref(self.wavefx), # identifier for data format sent for device 0, # DWORD_PTR dwCallback - callback function 0, # DWORD_PTR dwCallbackInstance - user instance data for callback CALLBACK_NULL # DWORD fdwOpen - flag for opening the device ) if ret != MMSYSERR_NOERROR: sys.exit('Error opening default waveform audio device (WAVE_MAPPER)') #print "Default Wave Audio output device is opened successfully" def _schedule_block(self, data, header): """Schedule PCM audio data block for playback. header parameter references free WAVEHDR structure to be used for scheduling.""" header.dwBufferLength = len(data) header.lpData = data # Prepare block for playback if winmm.waveOutPrepareHeader( self.hwaveout, ctypes.byref(header), ctypes.sizeof(header) ) != MMSYSERR_NOERROR: sys.exit('Error: waveOutPrepareHeader failed') # Write block, returns immediately unless a synchronous driver is # used (not often) if winmm.waveOutWrite( self.hwaveout, ctypes.byref(header), ctypes.sizeof(header) ) != MMSYSERR_NOERROR: sys.exit('Error: waveOutWrite failed') def play(self, stream): """Read PCM audio blocks from stream and write to the output device""" blocknum = len(self.headers) #: number of audio data blocks to be queued curblock = 0 #: start with block 0 stopping = False #: stopping playback when no input while True: freeids = [x for x in xrange(blocknum) if self.headers[x].dwFlags in (0, WHDR_DONE)] if (len(freeids) == blocknum) and stopping: break debug("empty blocks %s" % freeids) # Fill audio queue for i in freeids: if stopping: break debug("scheduling block %d" % i) data = stream.read(self.CHUNKSIZE) if len(data) == 0: stopping = True break self._schedule_block(data, self.headers[i]) debug("waiting for block %d" % curblock) while True: # unpreparing the header fails until the block is played ret = winmm.waveOutUnprepareHeader( self.hwaveout, ctypes.byref(self.headers[curblock]), ctypes.sizeof(self.headers[curblock]) ) if ret == WAVERR_STILLPLAYING: continue if ret != MMSYSERR_NOERROR: sys.exit('Error: waveOutUnprepareHeader failed with code 0x%x' % ret) break # Switch waiting pointer to the next block curblock = (curblock + 1) % len(self.headers) def close(self): """ x. Close Sound Device """ winmm.waveOutClose(self.hwaveout) #print "Default Wave Audio output device is closed" import StringIO def play_wave(samples, rate=44100): n = len(samples) for i in range(0, n): if samples[i] < -32768 or samples[i] > 32767: print "Warning: Some samples were out of the allowed range (-32768..32767)" samples = [max(min(x, 32767), -32768) for x in samples] break packed_samples = struct.pack(str(n)+'h', *samples) output = AudioWriter(1, rate) output.open() output.play(StringIO.StringIO(packed_samples)) output.close()