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