Module fastaudio @file fastaudio.pyx Python imports C header imports System string.h stdio.h stdlib.h math.h Python python.h sndfile mysndfile.h portaudio portaudio.h Python imports Data Structures Global Data Global C Functions fifoNew() fifoConsume() fifoAppend() fifoDestroy() PortAudio Callback Shared structure Callback Function Python Functions closestRate() getInfo() Lookup Tables class stream attributes __new__ __dealloc__ open() close() start() stop() abort() isActive() hits() read() write() getSampleRate() resample() readwav amplitude() @language python """ fastaudio.pyx A faster, more reliable version of pyPortAudio, written specifically for use with PSST II. """ # NOTE - This code is best viewed and edited with the Leo metastructural editor # http://leo.sourceforge.net @others import types import sys import os @others @others cdef extern from "string.h": cdef void *memset(void *s, int c, int n) cdef void *memcpy(void *dest, void *src, int n) cdef extern from "stdio.h": int printf(char *format,...) cdef extern from "stdlib.h": void *malloc(int size) void *realloc(void *ptr, int size) void free(void *ptr) cdef extern from "math.h": double fabs(double x) @others # Python-specific imports cdef extern from "Python.h": object PyString_FromStringAndSize(char *, int) @others # sndfile-specifics cdef extern from "mysndfile.h": ctypedef void SNDFILE cdef enum SF_CONSTANTS: SFM_READ ctypedef struct SF_INFO: long frames int samplerate int channels int format int sections int seekable cdef SNDFILE *sf_open(char *path, int mode, SF_INFO *sfinfo) cdef int sf_perror(SNDFILE *sndfile) cdef int sf_error_str(SNDFILE *sndfile, char *str, int len) cdef long sf_readf_short(SNDFILE *sndfile, short *ptr, long frames) #cdef long sf_readf_float(SNDFILE *sndfile, float *ptr, long frames) cdef int sf_close(SNDFILE *sndfile) @others # portaudio-specifics cdef extern from "../pa_common/portaudio.h": ctypedef int PaError cdef enum PA_ERRORS: paNoError paHostError paInvalidChannelCount paInvalidSampleRate paInvalidDeviceId paInvalidFlag paSampleFormatNotSupported paBadIODeviceCombination paInsufficientMemory paBufferTooBig paBufferTooSmall paNullCallback paBadStreamPtr paTimedOut paInternalError paDeviceUnavailable cdef PaError Pa_Initialize() cdef PaError Pa_Terminate() cdef long Pa_GetHostError() cdef char *Pa_GetErrorText(PaError errnum) ctypedef unsigned long PaSampleFormat cdef enum PA_SAMPLE_FORMATS: paFloat32 paInt16 paInt32 paPackedInt24 paInt8 paUInt8 paCustomFormat ctypedef int PaDeviceID cdef enum PA_MISC_CONSTANTS: paNoDevice ctypedef struct PaDeviceInfo: int structVersion char *name int maxInputChannels int maxOutputChannels int numSampleRates double *sampleRates PaSampleFormat nativeSampleFormats cdef PaDeviceID Pa_GetDefaultInputDeviceID() cdef PaDeviceID Pa_GetDefaultOutputDeviceID() cdef PaDeviceInfo* Pa_GetDeviceInfo(PaDeviceID device) ctypedef double PaTimestamp ctypedef int (PortAudioCallback)(void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp outTime, void *userData ) cdef enum PA_STREAM_FLAGS: paNoFlag paClipOff paDitherOff paPlatformSpecificFlags ctypedef unsigned long PaStreamFlags ctypedef void PortAudioStream cdef enum PA_STREAM_DEF: PaStream cdef PaError Pa_OpenStream( PortAudioStream** stream, PaDeviceID inputDevice, int numInputChannels, PaSampleFormat inputSampleFormat, void *inputDriverInfo, PaDeviceID outputDevice, int numOutputChannels, PaSampleFormat outputSampleFormat, void *outputDriverInfo, double sampleRate, unsigned long framesPerBuffer, unsigned long numberOfBuffers, PaStreamFlags streamFlags, PortAudioCallback *callback, void *userData) cdef PaError Pa_OpenDefaultStream( PortAudioStream** stream, int numInputChannels, int numOutputChannels, PaSampleFormat sampleFormat, double sampleRate, unsigned long framesPerBuffer, unsigned long numberOfBuffers, PortAudioCallback *callback, void *userData) cdef PaError Pa_CloseStream(PortAudioStream* stream) cdef PaError Pa_StartStream(PortAudioStream *stream) cdef PaError Pa_StopStream(PortAudioStream *stream) cdef PaError Pa_AbortStream(PortAudioStream *stream) cdef PaError Pa_StreamActive( PortAudioStream *stream) cdef PaTimestamp Pa_StreamTime(PortAudioStream *stream) cdef double Pa_GetCPULoad(PortAudioStream* stream) cdef int Pa_GetMinNumBuffers(int framesPerBuffer, double sampleRate) cdef void Pa_Sleep(long msec) cdef PaError Pa_GetSampleSize(PaSampleFormat format) import sys # define a linked list ctypedef struct BLKNODE: void *next void *data # define a fifo data structure for read/write ctypedef struct FIFO: int desc # descriptor char int maxblks int blksize int nblocks BLKNODE *first BLKNODE *last _isInitialised = 0 # flag indicating that Pa_Initialize() has been called _isOpen = 0 @others # create a new FIFO cdef public FIFO *fifoNew(int blksize, int maxblks, int desc): cdef FIFO *fifo fifo = <FIFO *>malloc(sizeof(FIFO)) fifo.desc = desc fifo.maxblks = maxblks fifo.blksize = blksize fifo.nblocks = 0 fifo.first = <BLKNODE *>0 fifo.last = <BLKNODE *>0 return fifo # consumes one block from the fifo. # if fifo is empty, returns null # block returned must be ultimately free()'d by the caller cdef public void *fifoConsume(FIFO *fifo): cdef BLKNODE *node cdef void *blk if fifo.nblocks == 0: return <void *>0 else: node = fifo.first blk = node.data fifo.first = <BLKNODE *>node.next free(node) fifo.nblocks = fifo.nblocks - 1 #printf("consume(%c): nblocks=%d blk=%lx\n", fifo.desc, fifo.nblocks, blk) return blk # appends one block to the fifo # a malloc()'ed copy is made of the block for internal storage. cdef public void fifoAppend(FIFO *fifo, void *blk, int size, int okToExceed): cdef BLKNODE *node cdef void *blk1 cdef int csize csize = size if not fifo: printf("fastaudio:fifoAppend:got null fifo pointer\n") if not blk: printf("fastaudio:fifoAppend:got null blk pointer\n") if not (fifo and blk): return #printf("fifoAppend: size=%ld\n", size) # grab a copy node = <BLKNODE *>malloc(sizeof(BLKNODE)) blk1 = <void *>malloc(fifo.blksize) if csize > fifo.blksize: csize = fifo.blksize memcpy(blk1, blk, csize) # copy the smaller of size or fifo.blksize # pad the rest of this buffer if size is smaller if csize < fifo.blksize: memset(<char *>blk1 + csize, 0, fifo.blksize - csize) node.data = blk1 node.next = <void *>0 # and add to fifo if fifo.nblocks == 0: fifo.first = node fifo.last = node fifo.nblocks = 1 else: fifo.last.next = <void *>node fifo.last = node fifo.nblocks = fifo.nblocks + 1 if fifo.nblocks > fifo.maxblks and not okToExceed: free(fifoConsume(fifo)) # maxed out - so ditch the first block #printf("fifoAppend(%c): maxed out at %d blocks\n", # fifo.desc, fifo.nblocks) # destroy a fifo and all its buffers cdef public void fifoDestroy(FIFO *fifo): #printf("fifoDestroy: fifo=%lx\n", fifo) cdef void *blk while 1: blk = fifoConsume(fifo) if blk == <void *>0: break free(blk) free(fifo) @others # define data structure shared between callback and clients ctypedef struct PA_SHARED: int samplerate # frames per sec int channels # number of channels int format # sample format int framesPerBuf # size of each buffer, in frames int bytesPerFrame # num bytes in each frame int bytesPerBuf # size of each buffer, in bytes FIFO *rxFifo FIFO *txFifo int hits # incremented each time the callback fires (diagnostic) int running # set to zero to stop the stream # now define our callback cdef int callback(void *inBuf, void *outBuf, unsigned long framesPerBuf, PaTimestamp outTime, void *myData): cdef PA_SHARED *cdata cdef int bufsize cdef void *txblk cdata = <PA_SHARED *>myData cdata.hits = cdata.hits + 1 bufsize = cdata.bytesPerBuf # append received data to FIFO fifoAppend(cdata.rxFifo, <void *>inBuf, cdata.txFifo.blksize, 0) # write sent data txblk = fifoConsume(cdata.txFifo) if txblk: memcpy(outBuf, txblk, cdata.bytesPerBuf) #printf("callback: playing a block\n") else: memset(outBuf, 0, cdata.bytesPerBuf) # generate test waveform #cdef int i #cdef short *tstbuf #i = 0 #tstbuf = <short *>outBuf #while i < bufsize / 2: # tstbuf[i] = (((i / 20) % 2) * 2 - 1) * 30000 # i = i + 1 # allow client to stop the stream if cdata.running: return 0 else: return 1 @others def closestRate(rate): """ Returns the closest sample rate to the one given Arguments: - wanted sample rate Returns: - closest available sample rate """ cdef float diff cdef float r cdef float best global _isInitialised # initialise if necessary if _isInitialised == 0: err = Pa_Initialize() if err != paNoError: raise Exception("fastaudio.__new__: Pa_Initialize failed!") _isInitialised = 1 # fetch the available sample rates #print "seeking closest rate to %d" % rate rates = getInfo()['samplerates'] #print rates smallestDiff = 9999999 # ridiculous best = 8000 for r in rates: diff = fabs(rate - r)/rate if diff < smallestDiff: best = r smallestDiff = diff return best def getInfo(output=0): """ gets the info about the default audio device, returning it as a dict with the keys 'version', 'name', 'maxInChannels', 'maxOutChannels', 'sampleRates' and 'formats' """ cdef PaDeviceInfo *info global _isInitialised global _isOpen # initialise if necessary if _isInitialised == 0: err = Pa_Initialize() if err != paNoError: raise Exception("fastaudio.__new__: Pa_Initialize failed!") _isInitialised = 1 if sys.platform == 'win32' and not _isOpen: try: # create and open a temporary stream object temp = stream(11025, exactrate=1) temp.open() except: pass else: temp = None if output: info = Pa_GetDeviceInfo(Pa_GetDefaultOutputDeviceID()) else: info = Pa_GetDeviceInfo(Pa_GetDefaultInputDeviceID()) if temp: try: temp.close() except: pass if not info: raise Exception("fastaudio.getInfo: Pa_GetDeviceInfo() failed") samplerates = [] for i in range(info.numSampleRates): samplerates.append(info.sampleRates[i]) sampleformats = [] formats = info.nativeSampleFormats for k in _sampleFormatsMap2: if (k & formats): sampleformats.append(_sampleFormatsMap2[k]) return {'version':info.structVersion, 'name':info.name, 'maxInChannels': info.maxInputChannels, 'maxOutChannels': info.maxOutputChannels, 'samplerates': samplerates, 'formats': sampleformats} # now define the user class _sampleFormatsMap1 = {'float32':paFloat32, 'int16':paInt16, 'int32':paInt32, 'int8':paInt8, 'uint8':paUInt8, 'custom':paCustomFormat, } _sampleFormatsMap2 = {paFloat32:'float32', paInt16:'int16', paInt32:'int32', paInt8:'int8', paUInt8:'uint8', paCustomFormat:'custom' } _sampleFormatSizes = {'float32':4, paFloat32:4, 'int16':2, paInt16:2, 'int32':4, paInt32:4, 'int8':1, paInt8:1, 'uint8':1, paUInt8:1, 'custom':4, paCustomFormat:4 } cdef class stream: """ Class for fastaudio operations. Also has utilities for reading/converting WAV files. """ @others cdef PA_SHARED *clientData cdef PortAudioStream *stream cdef int isopen cdef int isrunning def __new__(self, samplerate=8000, channels=2, format='int16', framesPerBuf=4096, maxbufs=16, **kwds): """ Constructor for fastaudio stream objects. Arguments: - samplerate - required sample rate for stream (default 8000) - channels - must be 1 or 2 (default 2) - format - 'int8', 'int16', 'int32' (default 'int16') - framesPerBuf - number of frames written to each internal buffer (default 4096). - maxbufs - maximum number of buffers stored for audio input (default 16). Raises an exception if stream initialisation failed. """ cdef PaError err cdef PA_SHARED *cdata cdef int i global _isInitialised global _isOpen # set up internal class vars self.isopen = 0 self.isrunning = 0 cdata = <PA_SHARED *>malloc(sizeof(PA_SHARED)) cdata.channels = channels cdata.format = _sampleFormatsMap1.get(format, paInt16) cdata.framesPerBuf = framesPerBuf cdata.bytesPerFrame = channels * _sampleFormatSizes[format] cdata.bytesPerBuf = framesPerBuf * cdata.bytesPerFrame cdata.txFifo = fifoNew(cdata.bytesPerBuf, maxbufs, 't') cdata.rxFifo = fifoNew(cdata.bytesPerBuf, maxbufs, 'r') printf("__new__: txFifo=%lx rxFifo=%lx\n", cdata.txFifo, cdata.rxFifo) cdata.hits = 0 cdata.running = 1 self.clientData = cdata # initialise if necessary if _isInitialised == 0: print "fastaudio.stream.__new__: calling Pa_Initialize()" err = Pa_Initialize() print "fastaudio.stream.__new__: back from Pa_Initialize()" if err != paNoError: raise Exception("fastaudio.__new__: Pa_Initialize failed!") _isInitialised = 1 if kwds.get('exactrate', None): cdata.samplerate = samplerate else: print "fastaudio.stream.__new__:seeking closest rate to %d" % samplerate cdata.samplerate = closestRate(samplerate) print "fastaudio.stream.__new__:rate = %d" % cdata.samplerate def __dealloc__(self): """ Destructor for fastaudio stream objects """ cdef int i i = 0 # delete the input and output fifos fifoDestroy(self.clientData.rxFifo) fifoDestroy(self.clientData.txFifo) # delete the shared callback data structure free(<void *>(self.clientData)) def open(self): """ Opens a previously created audio stream for reading/writing. No arguments, no returns. Raises an exception if the stream could not be opened. """ cdef PaError err cdef char *cerrTxt global _isOpen if _isOpen: raise Exception("fastaudio.stream.open: another stream is currently open") if self.isopen: raise Exception("fastaudio.stream.open: stream is already open") print "fastaudio.stream.open: calling Pa_OpenStream()" err = Pa_OpenStream( &self.stream, Pa_GetDefaultInputDeviceID(), self.clientData.channels, self.clientData.format, <void *>0, Pa_GetDefaultInputDeviceID(), self.clientData.channels, self.clientData.format, <void *>0, self.clientData.samplerate, self.clientData.framesPerBuf, 0, # number of buffers, if zero then use default minimum 0, # //paDitherOff, /* flags */ callback, self.clientData) print "fastaudio.stream.open: back from Pa_OpenStream()" if err == paHostError: printf("fastaudio.open: err=%d\n", err) err = Pa_GetHostError() if err != paNoError: cerrTxt = Pa_GetErrorText(err) errTxt = cerrTxt raise Exception("fastaudio.stream.open: %s" % errTxt) self.isopen = 1 _isOpen = 1 print "fastaudio.stream.open: finished" def close(self): """ Closes a previously-opened stream. If the stream was running, the stream will be stopped before closing. No arguments, no returns. Raises an exception if stream was not open, or if the close failed. """ cdef PaError err cdef char *cerrTxt global _isOpen if not self.isopen: raise Exception("fastaudio.stream.close: stream is not open") # stop stream if running if self.isrunning: self.stop() err = Pa_CloseStream(self.stream) if err != paNoError: cerrTxt = Pa_GetErrorText(err) errTxt = cerrTxt raise Exception("fastaudio.stream.close: %s" % errTxt) self.isopen = 0 _isOpen = 0 def start(self): """ Starts an already-opened stream. This causes the internal portaudio thread to fire up, and start reading audio input to an internal buffer chain (which can be read with the read() method), and playing to audio device anything that is written to the chain. No arguments, no returns. Refer to read() and write() for more info. Raises an exception if stream is not already open (or is already running), or if any other occurred. """ cdef PaError err cdef char *cerrTxt if not self.isopen: raise Exception("fastaudio.stream.start: stream is not open") if self.isrunning: raise Exception("fastaudio.stream.stop: stream is already running") print "fastaudio.stream.start: about to call Pa_StartStream" err = Pa_StartStream(self.stream) print "fastaudio.stream.start: back from Pa_StartStream" if err != paNoError: cerrTxt = Pa_GetErrorText(err) errTxt = cerrTxt raise Exception("fastaudio.stream.start: %s" % errTxt) self.isrunning = 1 def stop(self): """ Stops a previously-started stream. After this call, the stream will stop accumulating input from the audio device, and will stop playing queued data to the device. However, it will not flush the input chain. Even after this call, you can call read() to get any remaining data. read() is also a handy way of flushing the input buffer. No arguments, no returns. Raises exception if stream is not already open and running, or if an error occurred. """ cdef PaError err cdef char *cerrTxt if not self.isopen: raise Exception("fastaudio.stream.stop: stream is not open") if not self.isrunning: raise Exception("fastaudio.stream.stop: stream is not running") err = Pa_StopStream(self.stream) if err != paNoError: cerrTxt = Pa_GetErrorText(err) errTxt = cerrTxt raise Exception("fastaudio.stream.stop: %s" % errTxt) self.isrunning = 0 def abort(self): """ Aborts a stream. Refer to portaudio doco for more info. Likely, you won't need to use this method. No arguments, no returns. Raises exception if stream is not already open and running, or if an error occurred. """ cdef PaError err cdef char *cerrTxt if not self.isopen: raise Exception("fastaudio.stream.abort: stream is not open") if not self.isrunning: raise Exception("fastaudio.stream.abort: stream is not running") err = Pa_AbortStream(self.stream) if err != paNoError: cerrTxt = Pa_GetErrorText(err) errTxt = cerrTxt raise Exception("fastaudio.stream.abort: %s" % errTxt) def isActive(self): """ Returns nonzero if stream is currently running, or zero if not. No arguments, no exceptions """ return Pa_StreamActive(self.stream) def hits(self): """ Mainly a diagnostic function. Returns the number of times the stream object's internal callback function has been triggered. Unlikely you'll need this, unless you're suspecting something is not working. No arguments. Returns number of callback 'hits' since the stream was created. """ return self.clientData.hits def read(self): """ Reads all available audio frames. Returns a multiple of bufsize bytes, as a raw string, where bufsize = frames/buf * bytes/frame IMPORTANT NOTE - this function will never block. It works retrospectively, returning the data which has *already* come in off the audio device. It's also a useful way to flush the input buffer. For example, if you want to read exactly 1.5 seconds of audio data, try something like: mystream.read() # flush the data time.sleep(1.5) # wait for 1.5 secs worth of data to come in x = mystream.read() # grab this data No arguments. Returns the read data as a python string. There is a limit to how much audio data can be accumulated internally. Refer to the class constructor, whose framesPerBuf and maxbufs args determine this. For example, if sample rate is 8000, and framesPerBuf is 16000, and maxbufs is 5, then the stream will buffer a maximum of 10 seconds of audio. (16000 / 8000 * 5) If you want to be able to accumulate more data, simply construct your stream objects with larger and/or more buffers. """ cdef PA_SHARED *cdata cdef int numbufs cdef int i cdef char *buf cdef char *buf1 cdef char *buftmp cdata = self.clientData numbufs = cdata.rxFifo.nblocks if numbufs == 0: return "" #printf("read: fetching %d blocks\n", numbufs) buf = <char *>malloc(numbufs * cdata.bytesPerBuf) buf1 = buf i = 0 while i < numbufs: buftmp = <char *>fifoConsume(cdata.rxFifo) memcpy(buf1, buftmp, cdata.bytesPerBuf) free(buftmp) buf1 = buf1 + cdata.bytesPerBuf i = i + 1 pybuf = PyString_FromStringAndSize(buf, numbufs * cdata.bytesPerBuf) return pybuf def write(self, buf, rate=0, channels=0, format=0): """ Writes a string to audio output. Will not block. No arguments, no returns. Does not check if stream is already open/running. That's your job. """ cdef PA_SHARED *cdata cdef char *cbuf cdef char *cbuf1 cdef char *cbufn cdef long clen cdef char resampled cdef int size cdata = self.clientData if rate != 0 or channels != 0 or format != 0: #print "write: resampling" resampled = 1 buf = self.resample(buf, rate, channels, format) tmplen = len(buf) #printf("write: buflen after resampling=%ld\n", tmplen) clen = len(buf) cbuf = buf cbuf1 = cbuf cbufn = cbuf + clen #printf("write: len=%x cbuf1=%lx cbufn=%lx\n", clen, cbuf1, cbufn) while cbuf1 < cbufn: size = cbufn - cbuf1 if size > cdata.txFifo.blksize: size = cdata.txFifo.blksize #printf("write: cbuf1=%lx, cbufn=%lx\n", cbuf1, cbufn) fifoAppend(cdata.txFifo, cbuf1, size, 1) cbuf1 = cbuf1 + size #printf("write: completed fine\n") def getSampleRate(self): """ Returns the sample rate for this stream object No arguments """ return self.clientData.samplerate def resample(self, buf, rate=0, channels=0, format=0): """ Resamples a chunk of data to parameters which match the current stream. Arguments: - rate - sample rate of given data - channels - 1 or 2, default to using streams num channels default is to use stream's number of channels - format - 'uint8', 'int16', 'int24', 'int32', 'float32', default to using stream's num channels Returns: - Python string with resampled data Note - to clear up confusion, the 'rate', 'channels' and 'format' args above refer to the audio data you're passing in, ie the parameters you're converting FROM. The data will be converted TO the parameters with which the stream was created. """ cdef PA_SHARED *cdata cdef double inRate # vars for sample rate conversion cdef char *bufIn cdef char *bufInEnd cdef long bufInLen cdef int inFormat cdef int inFormatSize cdef int inFrameSize cdef long numFramesIn cdef int cChansIn cdef double frameLeft cdef double frameRight cdef long idxIn # indexes samples not frames, cdef char *inPtr_8 cdef unsigned char *inPtr_u8 cdef short *inPtr_16 cdef long *inPtr_32 cdef float *inPtr_f32 cdef char *bufOut cdef char *bufOutEnd cdef long bufOutLen cdef int outFormat cdef int outFormatSize cdef int outFrameSize cdef long numFramesOut cdef int cChansOut cdef long idxOut # ditto cdef char *outPtr_8 cdef unsigned char *outPtr_u8 cdef short *outPtr_16 cdef long *outPtr_32 cdef float *outPtr_f32 cdef double ratio cdata = self.clientData inRate = rate ratio = <double>(cdata.samplerate) / inRate print "fastaudio.stream.resample: entered" # work out channels if channels == 0: cChansIn = cdata.channels else: cChansIn = channels cChansOut = cdata.channels # work out formats outFormat = cdata.format outFormatSize = _sampleFormatSizes[outFormat] if format == 0: inFormat = outFormat inFormatSize = outFormatSize else: inFormat = _sampleFormatsMap1[format] inFormatSize = _sampleFormatSizes[inFormat] # work out frame sizes inFrameSize = cChansIn * inFormatSize outFrameSize = cChansOut * outFormatSize # get input C buffer bufInLen = len(buf) bufIn = buf bufInEnd = bufIn + bufInLen # work out frame counts numFramesIn = bufInLen / inFrameSize numFramesOut = <long>(<double>numFramesIn * ratio + 0.5) # create output C buffer bufOutLen = numFramesOut * outFrameSize bufOut = <char *>malloc(bufOutLen) if bufOut == <char *>0: print "malloc fail" bufOutEnd = bufOut + bufOutLen # set up input pointer - format-dependent if inFormat == paInt8: inPtr_8 = <char *>bufIn elif inFormat == paUInt8: inPtr_u8 = <unsigned char *>bufIn elif inFormat == paInt16: inPtr_16 = <short *>bufIn elif inFormat == paInt32: inPtr_32 = <long *>bufIn elif inFormat == paFloat32: inPtr_f32 = <float *>bufIn # set up output pointer - format-dependent if outFormat == paInt8: outPtr_8 = <char *>bufOut elif outFormat == paUInt8: outPtr_u8 = <unsigned char *>bufOut elif outFormat == paInt16: outPtr_16 = <short *>bufOut elif outFormat == paInt32: outPtr_32 = <long *>bufOut elif outFormat == paFloat32: outPtr_f32 = <float *>bufOut # loop to convert input to output idxIn = 0 idxOut = 0 print "fastaudio.stream.resample: entering resample loop" while idxOut < numFramesOut: # convert index idxIn = <long>(<double>idxOut / ratio) # fetch left and right input frame - convert to double if cChansIn == 1: if inFormat == paInt8: frameLeft = inPtr_8[idxIn] * 0x1000000 elif inFormat == paUInt8: frameLeft = inPtr_u8[idxIn] * 0x1000000 elif inFormat == paInt16: frameLeft = inPtr_16[idxIn] * 0x10000 elif inFormat == paInt32: frameLeft = inPtr_32[idxIn] elif inFormat == paFloat32: frameLeft = inPtr_f32[idxIn] frameRight = frameLeft else: if inFormat == paInt8: frameLeft = inPtr_8[idxIn * 2] * 0x1000000 frameRight = inPtr_8[idxIn * 2 + 1] * 0x1000000 elif inFormat == paUInt8: frameLeft = inPtr_u8[idxIn * 2] * 0x1000000 frameRight = inPtr_u8[idxIn * 2 + 1] * 0x1000000 elif inFormat == paInt16: frameLeft = inPtr_16[idxIn * 2] * 0x10000 frameRight = inPtr_16[idxIn * 2 + 1] * 0x10000 elif inFormat == paInt32: frameLeft = inPtr_32[idxIn * 2] frameRight = inPtr_32[idxIn * 2 + 1] elif inFormat == paFloat32: frameLeft = inPtr_f32[idxIn * 2] frameRight = inPtr_f32[idxIn * 2 + 1] # write to output buffer, converting to output format if cChansOut == 1: if outFormat == paInt8: outPtr_8[idxOut] = (frameLeft + frameRight) / (2 * 0x1000000) elif outFormat == paUInt8: outPtr_u8[idxOut] = (frameLeft + frameRight) / (2 * 0x1000000) elif outFormat == paInt16: outPtr_16[idxOut] = (frameLeft + frameRight) / (2 * 0x10000) elif outFormat == paInt32: outPtr_32[idxOut] = (frameLeft + frameRight) / 2 elif outFormat == paFloat32: outPtr_f32[idxOut] = (frameLeft + frameRight) / 2 else: if outFormat == paInt8: outPtr_8[idxOut * 2] = frameLeft / 0x1000000 outPtr_8[idxOut * 2 + 1] = frameRight / 0x1000000 elif outFormat == paUInt8: outPtr_u8[idxOut * 2] = frameLeft / 0x1000000 outPtr_u8[idxOut * 2 + 1] = frameRight / 0x1000000 elif outFormat == paInt16: outPtr_16[idxOut * 2] = frameLeft / 0x10000 outPtr_16[idxOut * 2 + 1] = frameRight / 0x10000 elif outFormat == paInt32: outPtr_32[idxOut * 2] = frameLeft outPtr_32[idxOut * 2 + 1] = frameRight elif outFormat == paFloat32: outPtr_f32[idxOut * 2] = frameLeft outPtr_f32[idxOut * 2 + 1] = frameRight idxOut = idxOut + 1 # convert out buf to python string print "fastaudio.stream.resample: converting to python string" outPyStr = PyString_FromStringAndSize(bufOut, bufOutLen) print "fastaudio.stream.resample: converting to python string" free(bufOut) print "fastaudio.stream.resample: ditched C buffer" print "fastaudio.stream.resample: finished" return outPyStr def readwav(self, wavfile): """ Read a WAV file and convert it to the format of this stream instance. The returned string can then be played at any time by passing it to the 'write' method. Arguments: - wavfile - relative or absolute pathname of WAV file Returns: - wav data, as raw python string Raises exceptions if there's not enough memory, or if the wav file could not be opened. """ cdef int FRAMES_PER_READ cdef char *bufPtr cdef int readCount cdef SNDFILE *infile cdef SF_INFO sfinfo cdef char *cFile cdef char *buf cdef int nFrames cdef int bytesPerFrame cdef int sampleRate cdef int channels cFile = wavfile nFrames = 0 FRAMES_PER_READ = 5000 print "fastaudio.stream.readwav: entered" infile = sf_open(cFile, SFM_READ, &sfinfo) print "fastaudio.stream.readwav: sf_open succeeded" if infile == <SNDFILE *>0: sf_perror(<void *>0) raise Exception("Error opening input file '%s'" % wavfile) bytesPerFrame = sfinfo.channels * 2 sampleRate = sfinfo.samplerate channels = sfinfo.channels buf = <char *>malloc(FRAMES_PER_READ * bytesPerFrame) if buf == <char *>0: #printf("fail4\n") raise Exception("Insufficient memory") print "fastaudio.stream.readwav: entering read loop" while 1: bufPtr = buf + nFrames * bytesPerFrame readCount = sf_readf_short(infile, <short *>bufPtr, FRAMES_PER_READ) if not readCount: break # extend the buffer nFrames = nFrames + readCount buf = <char *>realloc(buf, (nFrames + FRAMES_PER_READ) * bytesPerFrame) print "fastaudio.stream.readwav: read loop completed" sf_close(infile) # make a string of the raw wav data print "fastaudio.stream.readwav: converting bin sample to python string" frames = PyString_FromStringAndSize(buf, nFrames * bytesPerFrame) print "fastaudio.stream.readwav: got python string" free(buf) # no longer needed print "fastaudio.stream.readwav: completed" # resample it to current stream parameters return self.resample(frames, sampleRate, channels, 'int16') def amplitude(self, sample, howmuch=1.0): """ Calculates the amplitude of the given sample. Assumes the sample is in the format of this current stream object. Presently, only supports signed 8-bit, 16-bit and 32-bit ints I'm not sure about the scaling - you'll have to work this out for yourself by trial and error. """ cdef PA_SHARED *cdata cdef int format cdef double sum cdef int nsamples cdef int i cdef char *buf cdef int bufLen cdef char *bufEnd cdef short *bufShort cdef long *bufLong cdata = self.clientData format = cdata.format buf = sample bufLen = len(sample) sum = 0 if bufLen == 0: raise Exception("Cannot calculate amplitude for zero-length sample") nsamples = nsamples * howmuch if format == paInt8: nsamples = bufLen for i from 0 <= i < nsamples: sum = sum + fabs(buf[i]) sum = sum * 4294967296.0 / nsamples elif format == paInt16: bufShort = <short *>buf nsamples = bufLen / 2 for i from 0 <= i < nsamples: sum = sum + fabs(bufShort[i]) sum = sum * 65536.0 / nsamples elif format == paInt32: bufLong = <long *>buf nsamples = bufLen / 4 for i from 0 <= i < nsamples: sum = sum + fabs(bufLong[i]) sum = sum / nsamples else: raise Exception( "Amplitude calculation only supported for signed 8/16/32-bit int") sum = sum / 1000000 return sum