
"""
vstutil.pyx

Classes for VST sound noodling In Python.
Does not currently support GUI interfaces of VST plugins, but you
can instantiate them, query their parameters, chain them together,
send audio through them, send MIDI to them, and adjust their parameter values.

This code is placed into the public domain, Oct 4, 2007, by Tim Thompson.
"""

cdef extern from "vsthost.h":

	ctypedef struct AEffect:
		long numPrograms
		long numParams
		long numInputs
		long numOutputs
		long resvd1
		long resvd2
		long initialDelay
		long realQualities
		long offQualities
		long ioRatio
		long object
		long user
		long uniqueID
		long version

	ctypedef void VstParameterProperties

	cdef int vsthostmain()
	cdef float vst_avg(float *blk, int sz)
	cdef void vst_gotinput(float *blk,int i)
	cdef void vst_set_samplerate(float samplerate, int blocksize)
	cdef AEffect * vst_get_plugin(char *fname, char **err)
	cdef int vst_is_vst2(AEffect *plug)
	cdef int vst_can_receive_midi(AEffect *plug)
	cdef int vst_can_send_midi(AEffect *plug)
	cdef int vst_can_midi_program_names(AEffect * plug)
	cdef int vst_get_num_categories(AEffect *plug)
	cdef int vst_get_program_name(AEffect *plug, int category, int index, char s[25])
	cdef int vst_get_param_name(AEffect *plug, int index, char s[25])
	cdef int vst_get_param_label(AEffect *plug, int index, char s[25])
	cdef int vst_get_param_display(AEffect *plug, int index, char s[25])
	cdef int vst_get_product_string(AEffect *plug, char s[65])
	cdef int vst_set_param(AEffect *plug, int index, float f)
	cdef float vst_get_param(AEffect *plug, int index)
	cdef void vst_do_process(AEffect *, float *infd, float *outfd, float *outfd2, int len, int enabled)
	cdef int vst_get_param_properties_isswitch(AEffect *plug, int index)
	cdef int vst_send_midi(AEffect *plug, int chan, int pitch, long dur, int offvel)
	cdef int vst_set_program(AEffect *plug, int program)
	cdef int vst_is_synth(AEffect *plug)

cdef extern from "string.h":

	cdef void *memset(void *s, int c, int n)
	cdef void *memcpy(void *dest, void *src, int n)
	cdef int strlen(char *s)


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)




# Python-specific imports

cdef extern from "Python.h":
	object PyString_FromStringAndSize(char *, int)



# sndfile-specifics

cdef extern from "../../python/libsndfile/Win32/sndfile.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)

# portaudio-specifics

cdef extern from "../../python/portaudio/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 *My_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* My_Pa_GetDeviceInfo(PaDeviceID device)

	ctypedef double PaTimestamp

	ctypedef int (PortAudioCallback)(void *inputBuffer, void *outputBuffer, unsigned long nframesfer, 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 nframesfer,
		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 nframesfer,
		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 nframesfer, double sampleRate)

	cdef void Pa_Sleep(long msec)

	cdef PaError Pa_GetSampleSize(PaSampleFormat format)




import sys
import os
import os.path
import time
import array
from threading import Timer


_isInitialised = 0    # flag indicating that Pa_Initialize() has been called
_isOpen = 0


ctypedef struct cdata:
	int samplerate    # frames per sec
	int channels      # number of channels
	int format        # sample format
	int nframes       # size of each buffer, in frames
	int running       # set to zero to stop the stream
	
	int bufbytes    # This buffer gets sent to the audio device
	short *outbuff1
	short *outbuff2

	int sendme
	int outbuff1_filled
	int outbuff2_filled


cdef int callback(void *inBuf, void *outBuf, unsigned long nframes,
			PaTimestamp outTime, void *myData):

	cdef cdata *cbdata
	cdef int bufsize

	cbdata = <cdata *>myData
	bufsize = cbdata.bufbytes

	cdef float *fp
	fp = <float*>inBuf
	vst_gotinput(fp,1)

	# fifoAppend(cbdata.rxFifo, <void *>inBuf, cbdata.rxFifo.blksize, 0)

	if outBuf != NULL:
		if cbdata.sendme == 1 and cbdata.outbuff1_filled:
			# print("Sending 1, outbuff2_filled=",cbdata.outbuff2_filled,"\n");
			memcpy(outBuf, cbdata.outbuff1, cbdata.bufbytes)
			cbdata.outbuff1_filled = 0
			if cbdata.outbuff2_filled == 1:
				cbdata.sendme = 2
			else:
				cbdata.sendme = 0
		else:
			if cbdata.sendme == 2 and cbdata.outbuff2_filled:
				# print("Sending 2, outbuff1_filled=",cbdata.outbuff1_filled,"\n");
				memcpy(outBuf, cbdata.outbuff2, cbdata.bufbytes)
				cbdata.outbuff2_filled = 0
				if cbdata.outbuff1_filled == 1:
					cbdata.sendme = 1
				else:
					cbdata.sendme = 0
			else:
				# print("Sending nothing, sendme=",cbdata.sendme," buff1_filled=",cbdata.outbuff1_filled," buff2_filled=",cbdata.outbuff2_filled,"\n")
				if cbdata.outbuff1_filled == 0 and cbdata.outbuff2_filled == 0:
					cbdata.sendme = 1
				memset(outBuf, 0, cbdata.bufbytes)
		
	if cbdata.running:
		return 0
	else:
		return 1

cdef int callback2(void *inBuf, void *outBuf, unsigned long nframes,
			PaTimestamp outTime, void *myData):

	cdef cdata *cbdata
	cdef int bufsize

	cbdata = <cdata *>myData
	bufsize = cbdata.bufbytes

	cdef float *fp
	fp = <float*>inBuf
	vst_gotinput(fp,2)

	if cbdata.running:
		return 0
	else:
		return 1

def millitimeout():
	return 0

def millisleep(long ms):
	tm = 0.001 * ms
	t = Timer(tm,millitimeout)
	t.start()
	t.join(tm)

def PaGetInfo(output=0):
	"""
	Obtains 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("getInfo: Pa_Initialize failed!")
		_isInitialised = 1

	if sys.platform == 'win32' and not _isOpen:
		try:
			# create and open a temporary stream object
			temp = PaAudioDevice(11025, exactrate=1)
			temp.open()
		except:
			pass
	else:
		temp = None

	if output:
		info = My_Pa_GetDeviceInfo(Pa_GetDefaultOutputDeviceID())
	else:
		info = My_Pa_GetDeviceInfo(Pa_GetDefaultInputDeviceID())

	if temp:
		try:
			temp.close()
		except:
			pass

	if not info:
		raise Exception("vstutil.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
					  }

import numpy.core

cdef extern from "arrayobject.h":

	struct PyArray_Descr:
		int type_num, elsize
		char type

	ctypedef class numpy.ndarray [object PyArrayObject]:
		cdef char *data
		cdef int nd
		cdef int *dimensions, *strides
		cdef object base
		cdef PyArray_Descr *descr
		cdef int flags


cdef class _audiodata:

	cdef arr
	cdef int rate
	cdef int channels

	def __init__(self,filename=None):
		self.rate = 44100
		if filename:
			self.readwav(filename)

	def _printme(self,pos=-1,len=-1):
		self.realprint(self.arr,pos,len)

	def realprint(self,ndarray a,pos,len):
		if chr(a.descr.type) <> "f":
			raise TypeError("Float array required")
		cdef int n, i
		cdef float *fdata, x
		n = a.dimensions[0]
		fdata = <float *>a.data
		if pos < 0:
			pos = 0
		if len < 0:
			len = n
		pos2 = pos + len

		# The Pyrex documentation says this kind of loop
		# translates well into C.  Do not change the for syntax.
		for i from pos <= i < pos2:
			x = fdata[i]
			print "%d = %8f" % (i,x)
		print "|"

	cdef setarray(self,ndarray a,int pos, int len, float* fb):
		cdef float *fdata
		fdata = <float *>a.data
		# The Pyrex documentation says this kind of loop
		# translates well into C.  Do not change the for syntax.
		for i from 0 <= i < len:
			fdata[pos+i] = fb[i]

	def stretch(self,factor):

		orig_arrsize = len(self.arr)
		new_arrsize = factor * orig_arrsize
		new_arr = numpy.zeros((new_arrsize), dtype='f')
		self.real_stretch(factor,self.arr,new_arr)
		self.arr = new_arr

	cdef real_stretch(self,factor,ndarray old_a,ndarray new_a):

		cdef float *old_data
		cdef float *new_data

		old_data = <float *>old_a.data
		new_data = <float *>new_a.data
		new_arrsize = len(new_a)
		old_arrsize = len(old_a)

		# The Pyrex documentation says this kind of loop
		# translates well into C.  Do not change the for syntax.
		for i from 0 <= i < new_arrsize:
			oi = i * old_arrsize / new_arrsize
			new_data[i] = old_data[oi]

	def samples(self):
		return len(self.arr)

	def getarray(self):
		return self.arr

	def duration(self):
		return len(self.arr) / float(self.rate)

	def readwav(self, wavfile):
		cdef int READ_SIZE
	
		cdef float *fbuf
		cdef int fsize
		cdef long n
		cdef SNDFILE   *infile
		cdef SF_INFO   sfinfo
	
		cdef char *cFile
		cdef char *buf
		cdef int nf
		cdef float *fp
		cdef long arrsize
		cdef int pos
	
		cFile = wavfile
		READ_SIZE = 5000
	
		infile = sf_open(cFile, SFM_READ, &sfinfo)
		if infile == <SNDFILE *>0:
			sf_perror(<void *>0)
			raise Exception("Error opening input file '%s'" % wavfile)
	
		# printf("SIZE OF float = %d\n",sizeof(float))
		# printf("SIZE OF double = %d\n",sizeof(double))
		# printf("SFINFO.samplerate=%d\n",sfinfo.samplerate);
		# printf("SFINFO.format=%d\n",sfinfo.format);
		# printf("SFINFO.frames=%d\n",sfinfo.frames);
		# printf("SFINFO.channels=%d\n",sfinfo.channels);

		self.channels = sfinfo.channels

		arrsize = <long> sfinfo.frames*sfinfo.channels
		self.arr = numpy.zeros((arrsize), dtype='f')
	
		fsize = READ_SIZE * self.channels * sizeof(float)
		fbuf = <float *>malloc(fsize)

		# printf("READWAV fsize=%d arrsize=%d\n",fsize,arrsize)

		if fbuf == <float *>0:
			raise Exception("Insufficient memory")
	
		nf = 0
		pos = 0
		while 1:
			n = <long> sf_readf_float(infile, fbuf, READ_SIZE)
			if not n:
				break
			if (pos+n) > sfinfo.frames:
				raise Exception("Too many samples in file?")
			self.setarray(self.arr,pos,n,fbuf)
			pos = pos + n

		if sfinfo.samplerate != self.rate:
			self.arr = self.resample(self.arr,
						sfinfo.samplerate,
						sfinfo.channels)
			self.channels = sfinfo.channels

	
		sf_close(infile)
	
		return self

	def resample(self, ndarray inarr, int inrate, int inchannels):
		"""
		Resamples data and returns a new array
		"""
	
		cdef long inarrlen
		cdef long outarrlen
		cdef double ratio
		cdef double outr
		cdef double inr
	
		inr = inrate
		outr = self.rate
		ratio = outr / inr
	
		# compute frame sizes
		formatsize = _sampleFormatSizes['float32']
	
		inarrlen = len(self.arr)
		outarrlen = <long>(<double>inarrlen * ratio + 0.5)

		outarr = numpy.zeros((outarrlen), dtype='f')

		self.realresample(inarr,inchannels,outarr,self.channels,ratio)
		return outarr


	def realresample(self, ndarray inarr, int inchannels, ndarray outarr, int outchannels, double ratio):
	
		cdef float *indata
		cdef float *outdata
		cdef long inindex
		cdef long outindex
		cdef long outarrlen

		outarrlen = len(outarr)

		indata = <float *>inarr.data
		outdata = <float *>outarr.data
		inindex = 0
		outindex = 0
		while outindex < outarrlen:
			inindex = <long>(<double>outindex / ratio)
			if inchannels == 1:
				frameLeft = indata[inindex]
				frameRight = frameLeft
			else:
				frameLeft = indata[inindex * 2]
				frameRight = indata[inindex * 2 + 1]
				
			if outchannels == 1:
				outdata[outindex] = (frameLeft + frameRight) / 2
			else:
				outdata[outindex * 2] = frameLeft
				outdata[outindex * 2 + 1] = frameRight
			outindex = outindex + 1

cdef class PaAudioDevice:
	"""
	Class for audio device operations, as well as
	utilities for reading/converting WAV files.

	Constructor keyword parameters:

	    - samplerate (default 44100)
	    - channels   (default 2)
	    - format     ('int8', 'int16', 'int32', default 'int16')
	    - nframes    (frames per buffer, default 4096)
	    - inbufs     (max number of input buffers, default 16)

	Raises an exception if PaAudioDevice initialization failed.
	"""

	cdef cdata *cbdata
	cdef PortAudioStream *portaudio_stream
	cdef int isopen
	cdef int isrunning
	cdef done
	cdef monitor_lock
	cdef links
	cdef float *outfbuff     # This gets used to add up streams
	cdef float reduction

	def __init__(self, samplerate=44100, channels=2, format='int16', nframes=4096, inbufs=16, **kwds):
	
		cdef PaError err
		cdef int i
		cdef int nfb
		
		global _isInitialised
		global _isOpen
	
		self.isopen = 0
		self.isrunning = 0
		self.links = []
	
		self.cbdata = <cdata *>malloc(sizeof(cdata))
		self.cbdata.channels = channels
		self.cbdata.samplerate = samplerate
		self.cbdata.format = _sampleFormatsMap1.get(format, paInt16)
		self.cbdata.nframes = nframes
		self.reduction = 1.0

		self.cbdata.bufbytes = \
			nframes * channels * _sampleFormatSizes[format]

		# printf("=========== INITIALIZING OUTBUFF1\n")
		self.cbdata.outbuff1 = <short *>malloc(self.cbdata.bufbytes)
		self.cbdata.outbuff2 = <short *>malloc(self.cbdata.bufbytes)

		nfb = nframes * channels * _sampleFormatSizes['float32']
		self.outfbuff = <float *>malloc(nfb)

		# self.cbdata.rxFifo = fifoNew(self.cbdata.bufbytes, inbufs, ord('r'))

		self.cbdata.outbuff1_filled = 0
		self.cbdata.outbuff2_filled = 0
		self.cbdata.sendme = 1

		self.cbdata.running = 1
	
		# initialise if necessary
		if _isInitialised == 0:
			err = Pa_Initialize()
			if err != paNoError:
				raise Exception("PaAudioDevice.__init__: Pa_Initialize failed!")
			_isInitialised = 1

		# print "AUDIODEVICE NEW samplerate=",samplerate," nframes=",nframes
		vst_set_samplerate(samplerate,nframes)
	
	cdef finishdevicebuff(self,int whichbuff):
		cdef short *outbuff
		cdef float *fd
		cdef int i2
		cdef int i
		cdef int len
		cdef float maxf
		cdef float f
		cdef float newreduce

		fd = self.outfbuff
		if whichbuff == 1:
			outbuff = self.cbdata.outbuff1
		else:
			outbuff = self.cbdata.outbuff2
		# print("finishdevicebuff  start whichbuff=",whichbuff)

		len = self.cbdata.nframes * self.cbdata.channels
		maxfn = 0.0
		maxfp = 0.0
		for i from 0 <= i < len:
			f = fd[i] * self.reduction
			if f > 0:
				if maxfp < f:
					maxfp = f
			else:
				if f < 0:
					if maxfn < -f:
						maxfn = -f
			outbuff[i] = <short> (32768 * f)
		maxfn = maxfn * 1.1
		maxfp = maxfp * 1.1
		if maxfn > maxfp:
			maxfp = maxfn
		# print("maxfn=",maxfn)
		# print("maxfp=",maxfp)
		if maxfp > 0.0:
			newreduce = 1.0 / maxfp
			self.reduction = self.reduction * newreduce
			# print("******* self.reduction=",self.reduction)
			for i from 0 <= i < len:
				outbuff[i] = <short>(32768 * fd[i] * self.reduction)

		# print("finishdevicebuff  end whichbuff=",whichbuff)

	cdef filldevicebuff(self,ndarray a, int merge):

		cdef float *fd
		cdef short *outbuff
		cdef float *outfbuff
		cdef int i2
		cdef int i
		cdef int len

		len = self.cbdata.nframes
		outfbuff = self.outfbuff

		fd = <float *>a.data
		len = a.dimensions[0]

		# The Pyrex documentation says the kind of for loops below
		# translate well into C.  Do not change it.

		if merge:
			if self.cbdata.channels == 1:
				for i from 0 <= i < len:
					outfbuff[i] = outfbuff[i] + fd[i]
				# print("filldev A outbuff=",outbuff[len-1])
			else:
				for i from 0 <= i < len:
					i2 = 2 * i
					outfbuff[i2] = outfbuff[i2] + fd[i]
					outfbuff[i2+1] = outfbuff[i2+1] + fd[i]
				# print("filldev B outbuff=",outbuff[2*len-1])
		else:
			if self.cbdata.channels == 1:
				for i from 0 <= i < len:
					outfbuff[i] = fd[i]
				# print("filldev C outbuff=",outbuff[len-1])
			else:
				for i from 0 <= i < len:
					i2 = 2 * i
					outfbuff[i2] = fd[i]
					outfbuff[i2+1] = fd[i]
				# print("filldev D outbuff=",outbuff[2*len-1])


	def _monitor_start(self):
		import thread
		self.done = 0
		self.monitor_lock = thread.allocate_lock()
		self.monitor_lock.acquire()
		thread.start_new_thread(self._monitor_start_real,())

	def _monitor_stop(self):
		# print("TRYING TO STOP MONITOR, setting self.done")
		self.done = 1
		self.monitor_lock.acquire()
		self.monitor_lock.release()
		print("MONITOR STOPPED")

	def _tick(self):
		self._realtick()

	cdef _realtick(self):
		# print("TICK time=",time.time())
		# If either of the buffers need filling, fill it
		cdef short *s1
		cdef short *s2

		if len(self.links) == 0:
			return

		if self.cbdata.outbuff1_filled != 0 and self.cbdata.outbuff2_filled != 0:
			return


		if self.cbdata.outbuff1_filled == 0:
			whichbuff = 1
		else:
			whichbuff = 2

		# Loop through all the links 
		merge = 0
		nlink = 0
		for link in self.links:
			nlink = nlink + 1
			# print("nlink = ",nlink)
			arr = link._grabdata(self.cbdata.nframes)
			ln = len(arr)
			if ln == 0:
				# print("_grabdata in PaAudioDevice got 0 arr, should be deleting link")
				self.links.remove(link)
				continue
				
			if ln < self.cbdata.nframes:
				# print("Hey, ln < nframes, substituting zeros")
				arr = numpy.zeros((self.cbdata.nframes), dtype='f')

			self.filldevicebuff(arr,merge)
			merge = 1

		self.finishdevicebuff(whichbuff)

		if whichbuff == 1:
			if self.cbdata.outbuff1_filled == 1:
				print("HEY, OUTBUFF1_filled was already 1!")
			# print("SETTING outbuff1 filled, outbuff2_filled=",self.cbdata.outbuff2_filled)
			self.cbdata.outbuff1_filled = 1
			if self.cbdata.sendme == 0:
				self.cbdata.sendme = 1
		else:
			if self.cbdata.outbuff2_filled == 1:
				print("HEY, OUTBUFF2_filled was already 1!")
			self.cbdata.outbuff2_filled = 1
			if self.cbdata.sendme == 0:
				self.cbdata.sendme = 2
			# print("SETTING outbuff2 filled, outbuff1_filled=",self.cbdata.outbuff1_filled)

	def _monitor_start_real(self):
		# print("MONITOR RUNNING")
		while not self.done:
			t = Timer(0.01,self._tick)
			t.start()
			# I'm not sure this join() actually works.
			# It may be the timeout that really does it.
			t.join(0.01)

		# print("MONITOR TERMINATING")
		self.monitor_lock.release()

	def attach(self,link):
		if isinstance(link,AudioSnippet):
			loop = AudioLoop(link,loops=0)
			self.links.append(loop)
		elif isinstance(link,AudioLoop) or isinstance(link,VstPlugin):
			self.links.append(link)
		else:
			print "You must attach an AudioSnippet, AudioLoop, or VstPlugin"
			print "link=",link
	
	def __dealloc__(self):
		cdef int i
		i = 0
		
		free(<void *>(self.cbdata))
	
	
	def open(self):
		cdef PaError err
		cdef char *cerrTxt
	
		global _isOpen
	
		if _isOpen:
			raise Exception("PaAudioDevice.open: another PaAudioDevice is currently open")
		if self.isopen:
			raise Exception("PaAudioDevice.open: PaAudioDevice is already open")
	
		#
		# INPUT IS NOT OPENED, HERE.
		# Change -1 to Pa_GetDefaultInputDeviceID and 0 to 1
		# to enable input
		#
		# print("OPEN samplerate = ",self.cbdata.samplerate)
		err = Pa_OpenStream(
			  &self.portaudio_stream,
			  -1, # Pa_GetDefaultInputDeviceID(),
			  0, # 1
			  paFloat32, # self.cbdata.format,
			  <void *>0,
			  Pa_GetDefaultOutputDeviceID(),
			  self.cbdata.channels,
			  self.cbdata.format,
			  <void *>0,
			  self.cbdata.samplerate,
			  self.cbdata.nframes,
			  0,             # number of buffers, if zero then use default minimum
			  0, # //paDitherOff,    /* flags */
			  callback,
			  self.cbdata)
	
		if err == paHostError:
			printf("PaAudioDevice.open: err=%d\n", err)
			err = Pa_GetHostError()
		if err != paNoError:
			cerrTxt = My_Pa_GetErrorText(err)
			errTxt = cerrTxt
			raise Exception("PaAudioDevice.open: %s" % errTxt)

	
		self.isopen = 1
		_isOpen = 1
	
	def close(self):
		cdef PaError err
		cdef char *cerrTxt
	
		global _isOpen
	
		if not self.isopen:
			raise Exception("PaAudioDevice.close: PaAudioDevice is not open")
	
		# stop PaAudioDevice if running
		if self.isrunning:
			self.stop()
	
		err = Pa_CloseStream(self.portaudio_stream)
		if err != paNoError:
			cerrTxt = My_Pa_GetErrorText(err)
			errTxt = cerrTxt
			raise Exception("PaAudioDevice.close: %s" % errTxt)
	
		self.isopen = 0
		_isOpen = 0
	
	def start(self):
		cdef PaError err
		cdef char *cerrTxt
	
		if not self.isopen:
			raise Exception("PaAudioDevice.start: PaAudioDevice is not open")
		if self.isrunning:
			raise Exception("PaAudioDevice.stop: PaAudioDevice is already running")
	
		err = Pa_StartStream(self.portaudio_stream)
		if err != paNoError:
			cerrTxt = My_Pa_GetErrorText(err)
			errTxt = cerrTxt
			raise Exception("PaAudioDevice.start: %s" % errTxt)
	
		self.isrunning = 1
		self.reduction = 1.0
		self._monitor_start()
	
	
	def stop(self):
		cdef PaError err
		cdef char *cerrTxt
	
		if not self.isopen:
			raise Exception("PaAudioDevice.stop: PaAudioDevice is not open")
		if not self.isrunning:
			raise Exception("PaAudioDevice.stop: PaAudioDevice is not running")
	
		err = Pa_StopStream(self.portaudio_stream)
		if err != paNoError:
			cerrTxt = My_Pa_GetErrorText(err)
			errTxt = cerrTxt
			raise Exception("PaAudioDevice.stop: %s" % errTxt)
	
		self._monitor_stop()
		self.isrunning = 0
	
	
	def abort(self):
		cdef PaError err
		cdef char *cerrTxt
	
		if not self.isopen:
			raise Exception("PaAudioDevice.abort: PaAudioDevice is not open")
		if not self.isrunning:
			raise Exception("PaAudioDevice.abort: PaAudioDevice is not running")
	
		err = Pa_AbortStream(self.portaudio_stream)
		if err != paNoError:
			cerrTxt = My_Pa_GetErrorText(err)
			errTxt = cerrTxt
			raise Exception("PaAudioDevice.abort: %s" % errTxt)
	
	
	def is_active(self):
		return Pa_StreamActive(self.portaudio_stream)

	def reset_reduction(self):
		self.reduction = 1.0
	
	cdef float *mydata(self):
		return <float *>0;
	
cdef class _audiobase:

	cdef insnip

	def __init__(self,audiosnippet=None,offset=0,length=-1,loops=0,dll=None):
		self.insnip = None
	
	def setinput(self,link):
		self.insnip = link

	def _collectdata(self,leng):

		if self.insnip == None:
			return numpy.zeros((leng), dtype='f')

		done = 0
		needed = leng
		retarr = numpy.zeros((0,),dtype='f')
		sofar = 0
		arr = numpy.zeros((needed), dtype='f')
		while not done:
			arr = self.insnip._grabdata(needed)
			lenarr = len(arr)
			if lenarr == 0:
				done = 1
			retarr = numpy.concatenate((retarr,arr))
			sofar = sofar + lenarr
			needed = needed - lenarr
			if sofar >= leng:
				if sofar > leng:
					print("UNEXPECTED sofar=",sofar," leng=",leng)
				done = 1
		return retarr


cdef class VstPlugin(_audiobase):

	"""This class represents a VST plugin, and has methods for
	most of the interfaces to a VST plugin.

	Constructor keyword parameters:

	    - dll (filename of VST plugin DLL to load)

	"""

	cdef char* fileNamePtr
	cdef AEffect * plugin
	cdef int enabled

	def __init__(self,dll=None):

		cdef char *err

		self.fileNamePtr = dll
		self.plugin = vst_get_plugin(dll,&err)
		self.enabled = 1

		if self.plugin == NULL :
			printf("After calling vst_plugin dll=%s err=%s\n",self.fileNamePtr,err)

	def name(self):
		# This is wrong - the original name passed in has sometimes
		# gone out of scope, and this memory is garbage.
		return self.fileNamePtr

	def is_vst2(self):
		return vst_is_vst2(self.plugin)

	def is_synth(self):
		return vst_is_synth(self.plugin)

	def can_receive_midi(self):
		return vst_can_receive_midi(self.plugin)

	def can_send_midi(self):
		return vst_can_send_midi(self.plugin)

	def num_categories(self):
		return vst_get_num_categories(self.plugin)

	def can_midi_program_names(self):
		return vst_can_midi_program_names(self.plugin)

	def send_midi(self,int chan, int pitch, long dur, int veloff):
		return vst_send_midi(self.plugin,chan,pitch,dur,veloff)
	
	def set_program(self, int value):
		return vst_set_program(self.plugin,value)

	def set_enable(self,enable):
		self.enabled = enable

	def get_enable(self):
		return self.enabled

	def set_param(self,index,f):
		return vst_set_param(self.plugin,index,f)

	def get_param(self,index):
		cdef float f
		f = vst_get_param(self.plugin,index)
		return f

	def program_name(self,index,category):
		cdef char s[25]
		vst_get_program_name(self.plugin,index,category,s)
		return PyString_FromStringAndSize(s, strlen(s))


	def param_name(self,index):
		cdef char s[25]
		vst_get_param_name(self.plugin,index,s)
		return PyString_FromStringAndSize(s, strlen(s))

	def param_properties_isswitch(self, int index):
		return vst_get_param_properties_isswitch(self.plugin, index)

	def param_label(self,index):
		cdef char s[25]
		vst_get_param_label(self.plugin,index,s)
		return PyString_FromStringAndSize(s, strlen(s))

	def param_display(self,index):
		cdef char s[25]
		vst_get_param_display(self.plugin,index,s)
		return PyString_FromStringAndSize(s, strlen(s))

	def product_string(self):
		cdef char s[65]
		cdef int b
		b = vst_get_product_string(self.plugin,s)
		if b:
			return PyString_FromStringAndSize(s, strlen(s))
		else:
			return PyString_FromStringAndSize("NA", strlen("NA"))

	def num_programs(self):
		return self.plugin.numPrograms

	def num_params(self):
		return self.plugin.numParams

	def num_inputs(self):
		return self.plugin.numInputs

	def num_outputs(self):
		return self.plugin.numOutputs

	def _grabdata(self,leng):
		"""Collect and process the next 'leng' values from
		the input of this plugin."""

		retarr = self._collectdata(leng)
		outarr = numpy.zeros((leng,),dtype='f')
		outarr2 = numpy.zeros((leng,),dtype='f')
		self._process(retarr,outarr,outarr2)
		retarr = outarr

		return retarr

	def _process(self,ndarray inarr, ndarray outarr, ndarray outarr2):
		"""Call the VST plugin's process method to process data."""

		cdef float *infd
		cdef float *outfd
		cdef float *outfd2

		infd = <float *>inarr.data
		outfd = <float *>outarr.data
		outfd2 = <float *>outarr2.data
		len = inarr.dimensions[0]
		vst_do_process(self.plugin,infd,outfd,outfd2,len,self.enabled)


cdef class AudioLoop(_audiobase):
	"""This class represents a looping snippet of audio.

	Constructor keyword parameters:

	    - audiosnippet (an AudioSnippet)
	    - offset (sample offset within snippet to start at)
	    - length (length of the snippet to use)
	    - loops (number of loops.  -1 means loop forever)
	"""
	cdef s
	cdef offset
	cdef length
	cdef loops
	cdef currpos
	cdef currloop
	cdef ended

	def __init__(self,audiosnippet=None,offset=0,length=-1,loops=0):
		self.s = audiosnippet
		self.offset = offset
		self.loops = loops
		self.length = length
		self.currpos = 0
		self.currloop = 0
		self.ended = 0

	def restart(self):
		self.ended = 0
		self.currpos = 0
		self.currloop = 0

	def setlength(self,length):
		self.length = length

	def setoffset(self,offset):
		# XXX - THIS NEEDS TO HANDLE CHANNELS
		self.offset = offset

	def _grabdata(self,leng):
		if self.ended:
			retarr = numpy.zeros((0), dtype='f')
			return retarr
			
		adata = self.s._audiodata()
		arr = adata.getarray()

		spos = self.offset + self.currpos
		epos = spos + leng

		# See if we've reached the end of the offset/length specified
		# in this link
		if self.length > 0:
			epos2 = self.offset + self.length
			if epos2 <= epos:
				epos = epos2
				self.ended = 1

		if spos > len(arr):
			self.ended = 1
			retarr = numpy.zeros((leng), dtype='f')
		else:
			if epos > len(arr):
				self.ended = 1
				epos = len(arr)-1
			retarr = arr[spos:epos]

		if self.ended:
			self.currpos = 0
			if self.loops != -1:
				self.currloop = self.currloop+1
				if self.currloop <= self.loops:
					self.ended = 0
			else:
				# -1 means loop infinitely
				self.ended = 0
		else:
			self.currpos = self.currpos + len(retarr)
		return retarr

cdef class _audiolist:
	"""
	Class for a _audiolist.
	"""

	cdef links
	cdef currlink
	cdef vst

	def __init__(self):
		self.links = []
		self.currlink = -1

	def restart(self):
		if len(self.links) == 0:
			self.currlink = -1
		else:
			self.currlink = 0
		for link in self.links:
			link.restart()

	def _grabdata(self,leng):

		done = 0
		needed = leng
		retarr = numpy.zeros((0,),dtype='f')
		sofar = 0
		while not done:
			if self.currlink < 0:
				arr = numpy.zeros((needed), dtype='f')
			else:
				link = self.links[self.currlink]
				(arr,ended) = link._grabdata(needed)
				if ended:
					if self.currlink < (len(self.links)-1):
						self.currlink = self.currlink + 1
					else:
						self.currlink = -1
					print("Link ended, currlink is now ",self.currlink)
			lenarr = len(arr)
			retarr = numpy.concatenate((retarr,arr))
			sofar = sofar + lenarr
			needed = needed - lenarr
			if sofar >= leng:
				if sofar > leng:
					print("UNEXPECTED sofar=",sofar," leng=",leng)
				done = 1
		return retarr

	def _getlinks(self):
		return self.links

	def append(self,link):
		self.links.append(link)
		if self.currlink < 0:
			self.currlink = 0
			link.restart()

cdef class AudioSnippet:
	"""
	This class represents a single audio snippet.
	Basically an array of float32.

	Constructor keyword parameters:

	    - filename (name of .wav file)
	    - data (audio data)
	"""

	cdef adata

	def __init__(self, data=None, filename=None):
		if filename:
			self.adata = _audiodata(filename=filename)
		else:
			if data:
				self.adata = data
			else:
				self.adata = _audiodata(filename=None)

	def _audiodata(self):
		return self.adata

	def duration(self):
		"""Return the duration of the snippet in seconds."""
		return self.adata.duration()
	
	def _printme(self,pos=-1,len=-1):
		self.adata._printme(pos=pos,len=len)

	def stretch(self,factor):
		"""Simple example to test modifying of audio data.  This
		stretches the length of the audio by the specified factor."""
		self.real_stretch(factor)

	cdef real_stretch(self,factor):
		ad = self.adata
		ad.stretch(factor)

