# HG changeset patch # User fazekasgy # Date 1254742080 0 # Node ID 27bab3a16c9a966e61f90c13c6601478274ac9c8 new branch Vampy2final diff -r 000000000000 -r 27bab3a16c9a Example VamPy plugins/PyMFCC.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Example VamPy plugins/PyMFCC.py Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,397 @@ +'''PyMFCC.py - This example Vampy plugin demonstrates +how to return sprectrogram-like features and how to return +data using the getRemainingFeatures() function. + +The plugin has frequency domain input and is using the +numpy array interface. (Flag: vf_ARRAY) + +Outputs: +1) 2-128 MFCC coefficients +2) Mel-warped spectrum used for the MFCC computation +3) Filter matrix used for Mel scaling + +Centre for Digital Music, Queen Mary University of London. +Copyright (C) 2009 Gyorgy Fazekas, QMUL. (See Vamp sources +for licence information.) + +Constants for Mel frequency conversion and filter +centre calculation are taken from the GNU GPL licenced +Freespeech library. Copyright (C) 1999 Jean-Marc Valin +''' + +import sys,numpy,vampy +from numpy import abs,log,exp,floor,sum,sqrt,cos,hstack +from numpy.fft import * +from vampy import * + + +class melScaling(object): + + def __init__(self,sampleRate,inputSize,numBands,minHz = 0,maxHz = None): + '''Initialise frequency warping and DCT matrix. + Parameters: + sampleRate: audio sample rate + inputSize: length of magnitude spectrum (half of FFT size assumed) + numBands: number of mel Bands (MFCCs) + minHz: lower bound of warping (default = DC) + maxHz: higher bound of warping (default = Nyquist frequency) + ''' + self.sampleRate = sampleRate + self.NqHz = sampleRate / 2.0 + self.minHz = minHz + if maxHz is None : maxHz = self.NqHz + self.maxHz = maxHz + self.inputSize = inputSize + self.numBands = numBands + self.valid = False + self.updated = False + + def update(self): + # make sure this will run only once + # if called from a vamp process + if self.updated: return self.valid + self.updated = True + self.valid = False + print 'Updating parameters and recalculating filters: ' + print 'Nyquist: ',self.NqHz + + if self.maxHz > self.NqHz : + raise Exception('Maximum frequency must be smaller than the Nyquist frequency') + + self.maxMel = 1000*log(1+self.maxHz/700.0)/log(1+1000.0/700.0) + self.minMel = 1000*log(1+self.minHz/700.0)/log(1+1000.0/700.0) + print 'minHz:%s\nmaxHz:%s\nminMel:%s\nmaxMel:%s\n' \ + %(self.minHz,self.maxHz,self.minMel,self.maxMel) + self.filterMatrix = self.getFilterMatrix(self.inputSize,self.numBands) + self.DCTMatrix = self.getDCTMatrix(self.numBands) + self.filterIter = self.filterMatrix.__iter__() + self.valid = True + return self.valid + + def getFilterCentres(self,inputSize,numBands): + '''Calculate Mel filter centres around FFT bins. + This function calculates two extra bands at the edges for + finding the starting and end point of the first and last + actual filters.''' + centresMel = numpy.array(xrange(numBands+2)) * (self.maxMel-self.minMel)/(numBands+1) + self.minMel + centresBin = numpy.floor(0.5 + 700.0*inputSize*(exp(centresMel*log(1+1000.0/700.0)/1000.0)-1)/self.NqHz) + return numpy.array(centresBin,int) + + def getFilterMatrix(self,inputSize,numBands): + '''Compose the Mel scaling matrix.''' + filterMatrix = numpy.zeros((numBands,inputSize)) + self.filterCentres = self.getFilterCentres(inputSize,numBands) + for i in xrange(numBands) : + start,centre,end = self.filterCentres[i:i+3] + self.setFilter(filterMatrix[i],start,centre,end) + return filterMatrix.transpose() + + def setFilter(self,filt,filterStart,filterCentre,filterEnd): + '''Calculate a single Mel filter.''' + k1 = numpy.float32(filterCentre-filterStart) + k2 = numpy.float32(filterEnd-filterCentre) + up = (numpy.array(xrange(filterStart,filterCentre))-filterStart)/k1 + dn = (filterEnd-numpy.array(xrange(filterCentre,filterEnd)))/k2 + filt[filterStart:filterCentre] = up + filt[filterCentre:filterEnd] = dn + + def warpSpectrum(self,magnitudeSpectrum): + '''Compute the Mel scaled spectrum.''' + return numpy.dot(magnitudeSpectrum,self.filterMatrix) + + def getDCTMatrix(self,size): + '''Calculate the square DCT transform matrix. Results are + equivalent to Matlab dctmtx(n) with 64 bit precision.''' + DCTmx = numpy.array(xrange(size),numpy.float64).repeat(size).reshape(size,size) + DCTmxT = numpy.pi * (DCTmx.transpose()+0.5) / size + DCTmxT = (1.0/sqrt( size / 2.0)) * cos(DCTmx * DCTmxT) + DCTmxT[0] = DCTmxT[0] * (sqrt(2.0)/2.0) + return DCTmxT + + def dct(self,data_matrix): + '''Compute DCT of input matrix.''' + return numpy.dot(self.DCTMatrix,data_matrix) + + def getMFCCs(self,warpedSpectrum,cn=True): + '''Compute MFCC coefficients from Mel warped magnitude spectrum.''' + mfccs=self.dct(numpy.log(warpedSpectrum)) + if cn is False : mfccs[0] = 0.0 + return mfccs + + +class PyMFCC(melScaling): + + def __init__(self,inputSampleRate): + + # flags for setting some Vampy options + self.vampy_flags = vf_DEBUG | vf_ARRAY | vf_REALTIME + + self.m_inputSampleRate = int(inputSampleRate) + self.m_stepSize = 1024 + self.m_blockSize = 2048 + self.m_channels = 1 + self.numBands = 40 + self.cnull = 1 + self.two_ch = False + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + + def initialise(self,channels,stepSize,blockSize): + self.m_channels = channels + self.m_stepSize = stepSize + self.m_blockSize = blockSize + self.window = numpy.hamming(blockSize) + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + return True + + def getMaker(self): + return 'Vampy Example Plugins' + + def getCopyright(self): + return 'Plugin By George Fazekas' + + def getName(self): + return 'Vampy MFCC Plugin' + + def getIdentifier(self): + return 'vampy-mfcc' + + def getDescription(self): + return 'A simple MFCC plugin' + + def getMaxChannelCount(self): + return 2 + + def getInputDomain(self): + return FrequencyDomain #TimeDomain + + def getPreferredBlockSize(self): + return 2048 + + def getPreferredStepSize(self): + return 1024 + + def getOutputDescriptors(self): + + Generic = OutputDescriptor() + Generic.hasFixedBinCount=True + Generic.binCount=int(self.numBands)-self.cnull + Generic.hasKnownExtents=False + Generic.isQuantized=True + Generic.sampleType = OneSamplePerStep + + # note the inheritance of attributes (optional) + MFCC = OutputDescriptor(Generic) + MFCC.identifier = 'mfccs' + MFCC.name = 'MFCCs' + MFCC.description = 'MFCC Coefficients' + MFCC.binNames=map(lambda x: 'C '+str(x),range(self.cnull,int(self.numBands))) + if self.two_ch and self.m_channels == 2 : + MFCC.binNames *= 2 #repeat the list + MFCC.unit = None + if self.two_ch and self.m_channels == 2 : + MFCC.binCount = self.m_channels * (int(self.numBands)-self.cnull) + else : + MFCC.binCount = self.numBands-self.cnull + + warpedSpectrum = OutputDescriptor(Generic) + warpedSpectrum.identifier='warped-fft' + warpedSpectrum.name='Mel Scaled Spectrum' + warpedSpectrum.description='Mel Scaled Magnitide Spectrum' + warpedSpectrum.unit='Mel' + if self.two_ch and self.m_channels == 2 : + warpedSpectrum.binCount = self.m_channels * int(self.numBands) + else : + warpedSpectrum.binCount = self.numBands + + melFilter = OutputDescriptor(Generic) + melFilter.identifier = 'mel-filter-matrix' + melFilter.sampleType='FixedSampleRate' + melFilter.sampleRate=self.m_inputSampleRate/self.m_stepSize + melFilter.name='Mel Filter Matrix' + melFilter.description='Returns the created filter matrix in getRemainingFeatures.' + melFilter.unit = None + + return OutputList(MFCC,warpedSpectrum,melFilter) + + + def getParameterDescriptors(self): + + melbands = ParameterDescriptor() + melbands.identifier='melbands' + melbands.name='Number of bands (coefficients)' + melbands.description='Set the number of coefficients.' + melbands.unit = '' + melbands.minValue = 2 + melbands.maxValue = 128 + melbands.defaultValue = 40 + melbands.isQuantized = True + melbands.quantizeStep = 1 + + cnull = ParameterDescriptor() + cnull.identifier='cnull' + cnull.name='Return C0' + cnull.description='Select if the DC coefficient is required.' + cnull.unit = None + cnull.minValue = 0 + cnull.maxValue = 1 + cnull.defaultValue = 0 + cnull.isQuantized = True + cnull.quantizeStep = 1 + + two_ch = ParameterDescriptor(cnull) + two_ch.identifier='two_ch' + two_ch.name='Process channels separately' + two_ch.description='Process two channel files separately.' + two_ch.defaultValue = False + + minHz = ParameterDescriptor() + minHz.identifier='minHz' + minHz.name='minimum frequency' + minHz.description='Set the lower frequency bound.' + minHz.unit='Hz' + minHz.minValue = 0 + minHz.maxValue = 24000 + minHz.defaultValue = 0 + minHz.isQuantized = True + minHz.quantizeStep = 1.0 + + maxHz = ParameterDescriptor() + maxHz.identifier='maxHz' + maxHz.description='Set the upper frequency bound.' + maxHz.name='maximum frequency' + maxHz.unit='Hz' + maxHz.minValue = 100 + maxHz.maxValue = 24000 + maxHz.defaultValue = 11025 + maxHz.isQuantized = True + maxHz.quantizeStep = 100 + + return ParameterList(melbands,minHz,maxHz,cnull,two_ch) + + + def setParameter(self,paramid,newval): + self.valid = False + if paramid == 'minHz' : + if newval < self.maxHz and newval < self.NqHz : + self.minHz = float(newval) + if paramid == 'maxHz' : + if newval < self.NqHz and newval > self.minHz+1000 : + self.maxHz = float(newval) + else : + self.maxHz = self.NqHz + if paramid == 'cnull' : + self.cnull = int(not int(newval)) + if paramid == 'melbands' : + self.numBands = int(newval) + if paramid == 'two_ch' : + self.two_ch = bool(newval) + return None + + + def getParameter(self,paramid): + if paramid == 'minHz' : + return self.minHz + if paramid == 'maxHz' : + return self.maxHz + if paramid == 'cnull' : + return bool(not int(self.cnull)) + if paramid == 'melbands' : + return self.numBands + if paramid == 'two_ch' : + return self.two_ch + else: + return 0.0 + + # set numpy array process using the 'vf_ARRAY' flag in __init__() + # and RealTime time stamps using the 'vf_REALTIME' flag + def process(self,inputbuffers,timestamp): + + # calculate the filter and DCT matrices, check + # if they are computable given a set of parameters + # (we only do this once, when the process is called first) + if not self.update() : return None + + # if two channel processing is set, use process2ch + if self.m_channels == 2 and self.two_ch : + return self.process2ch(inputbuffers,timestamp) + + fftsize = self.m_blockSize + + if self.m_channels > 1 : + # take the average of two magnitude spectra + mS0 = abs(inputbuffers[0])[0:fftsize/2] + mS1 = abs(inputbuffers[1])[0:fftsize/2] + magnitudeSpectrum = (mS0 + mS1) / 2 + else : + complexSpectrum = inputbuffers[0] + magnitudeSpectrum = abs(complexSpectrum)[0:fftsize/2] + + # do the frequency warping and MFCC computation + melSpectrum = self.warpSpectrum(magnitudeSpectrum) + melCepstrum = self.getMFCCs(melSpectrum,cn=True) + + # returning the values: + outputs = FeatureSet() + + # 1) full initialisation example using a FeatureList + f_mfccs = Feature() + f_mfccs.values = melCepstrum[self.cnull:] + outputs[0] = FeatureList(f_mfccs) + + # 2) simplified: when only one feature is required, + # the FeatureList() can be omitted + outputs[1] = Feature(melSpectrum) + + # this is equivalint to writing : + # outputs[1] = Feature() + # outputs[1].values = melSpectrum + # or using keyword args: Feature(values = melSpectrum) + + return outputs + + # process channels separately (stack the returned arrays) + def process2ch(self,inputbuffers,timestamp): + + fftsize = self.m_blockSize + + complexSpectrum0 = inputbuffers[0] + complexSpectrum1 = inputbuffers[1] + + magnitudeSpectrum0 = abs(complexSpectrum0)[0:fftsize/2] + magnitudeSpectrum1 = abs(complexSpectrum1)[0:fftsize/2] + + # do the computations + melSpectrum0 = self.warpSpectrum(magnitudeSpectrum0) + melCepstrum0 = self.getMFCCs(melSpectrum0,cn=True) + melSpectrum1 = self.warpSpectrum(magnitudeSpectrum1) + melCepstrum1 = self.getMFCCs(melSpectrum1,cn=True) + + outputs = FeatureSet() + outputs[0] = Feature(hstack((melCepstrum1[self.cnull:],melCepstrum0[self.cnull:]))) + outputs[1] = Feature(hstack((melSpectrum1,melSpectrum0))) + + return outputs + + + def getRemainingFeatures(self): + if not self.update() : return [] + frameSampleStart = 0 + + output_featureSet = FeatureSet() + + # the filter is the third output (index starts from zero) + output_featureSet[2] = flist = FeatureList() + + while True: + f = Feature() + f.hasTimestamp = True + f.timestamp = frame2RealTime(frameSampleStart,self.m_inputSampleRate) + try : + f.values = self.filterIter.next() + except StopIteration : + break + flist.append(f) + frameSampleStart += self.m_stepSize + + return output_featureSet + diff -r 000000000000 -r 27bab3a16c9a Example VamPy plugins/PySpectralFeatures.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Example VamPy plugins/PySpectralFeatures.py Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,182 @@ +'''PySpectralFeatures.py - Example plugin demonstrates +how to calculate and return simple low-level spectral +descriptors (curves) using Numpy and the buffer interface. + +Outputs: +1) Spectral Centroid +2) Spectral Creast Factor +3) Spectral Band-width +4) Spectral Difference (first order) + +Centre for Digital Music, Queen Mary University of London. +Copyright (C) 2009 Gyorgy Fazekas, QMUL. (See Vamp sources +for licence information.) + +''' + +from numpy import * +from vampy import * + +class PySpectralFeatures: + + def __init__(self,inputSampleRate): + + # flags: + self.vampy_flags = vf_DEBUG | vf_BUFFER | vf_REALTIME + + self.m_inputSampleRate = inputSampleRate + self.m_stepSize = 0 + self.m_blockSize = 0 + self.m_channels = 0 + self.threshold = 0.05 + return None + + def initialise(self,channels,stepSize,blockSize): + self.m_channels = channels + self.m_stepSize = stepSize + self.m_blockSize = blockSize + self.prevMag = zeros((blockSize/2)-1) + return True + + def reset(self): + # reset any initial conditions + self.prevMag = zeros((blockSize/2)-1) + return None + + def getMaker(self): + return 'Vampy Example Plugins' + + def getName(self): + return 'Vampy Spectral Features' + + def getIdentifier(self): + return 'vampy-sf3' + + def getDescription(self): + return 'A collection of low-level spectral descriptors.' + + def getMaxChannelCount(self): + return 1 + + def getInputDomain(self): + return FrequencyDomain + + def getOutputDescriptors(self): + + #Generic values are the same for all + Generic = OutputDescriptor() + Generic.hasFixedBinCount=True + Generic.binCount=1 + Generic.hasKnownExtents=False + Generic.isQuantized=False + Generic.sampleType = OneSamplePerStep + Generic.unit = 'Hz' + + #Spectral centroid etc... + SC = OutputDescriptor(Generic) + SC.identifier = 'vampy-sc' + SC.name = 'Spectral Centroid' + SC.description ='Spectral Centroid (Brightness)' + + SCF = OutputDescriptor(Generic) + SCF.identifier = 'vampy-scf' + SCF.name = 'Spectral Crest Factor' + SCF.description = 'Spectral Crest (Tonality)' + SCF.unit = 'v' + + BW = OutputDescriptor(Generic) + BW.identifier = 'vampy-bw' + BW.name = 'Band Width' + BW.description = 'Spectral Band Width' + + SD = OutputDescriptor(Generic) + SD.identifier = 'vampy-sd' + SD.name = 'Spectral Difference' + SD.description = 'Eucledian distance of successive magnitude spectra.' + + #return a tuple, list or OutputList(SC,SCF,BW) + return OutputList(SC,SCF,BW,SD) + + def getParameterDescriptors(self): + + threshold = ParameterDescriptor() + threshold.identifier='threshold' + threshold.name='Noise threshold' + threshold.description='Noise threshold' + threshold.unit='v' + threshold.minValue=0 + threshold.maxValue=1 + threshold.defaultValue=0.05 + threshold.isQuantized=False + + return ParameterList(threshold) + + def setParameter(self,paramid,newval): + if paramid == 'threshold' : + self.threshold = newval + return + + def getParameter(self,paramid): + if paramid == 'threshold' : + return self.threshold + else: + return 0.0 + + + # using the numpy memory buffer interface: + # flag : vf_BUFFER (or implement processN) + # NOTE: Vampy can now pass numpy arrays directly using + # the flag vf_ARRAY (see MFCC plugin for example) + def process(self,membuffer,timestamp): + + fftsize = self.m_blockSize + sampleRate = self.m_inputSampleRate + + #for time domain plugins use the following line: + #audioSamples = frombuffer(membuffer[0],float32) + + #for frequency domain plugins use: + complexSpectrum = frombuffer(membuffer[0],complex64,-1,8) + + # meaning of the parameters above: + # complex64 : data type of the created numpy array + # -1 : convert the whole buffer + # 8 : skip the DC component (2*32bit / 8bit = 8byte) + + magnitudeSpectrum = abs(complexSpectrum) / (fftsize*0.5) + #phaseSpectrum = angle(complexSpectrum) + + freq = array(range(1,len(complexSpectrum)+1)) \ + * sampleRate / fftsize + + # return features in a FeatureSet() + output_featureSet = FeatureSet() + + tpower = sum(magnitudeSpectrum) + + if tpower > self.threshold : + centroid = sum(freq * magnitudeSpectrum) / tpower + crest = max(magnitudeSpectrum) / tpower + bw = sum( abs(freq - centroid) * magnitudeSpectrum ) / tpower + normMag = magnitudeSpectrum / tpower + sd = sqrt(sum(power((normMag - self.prevMag),2))) + self.prevMag = normMag + else : + centroid = 0.0 + crest = 0.0 + bw = 0.0 + sd = 0.0 + + # Any value resulting from the process can be returned. + # It is no longer necessary to wrap single values into lists + # and convert numpy.floats to python floats, + # however a FeatureList() (or python list) can be returned + # if more than one feature is calculated per frame. + # The feature values can be e.g. int, float, list or array. + + output_featureSet[0] = Feature(centroid) + output_featureSet[1] = Feature(crest) + output_featureSet[2] = Feature(bw) + output_featureSet[3] = Feature(sd) + + return output_featureSet diff -r 000000000000 -r 27bab3a16c9a Example VamPy plugins/PyZeroCrossing.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Example VamPy plugins/PyZeroCrossing.py Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,162 @@ +'''PyZeroCrossing.py - Example plugin demonstrates +how to write a Vampy plugin in pure Python without +using Numpy or the extensions provided by the embedded +vampy module. + +This plugin is compatible with provious versions of vampy, +apart from moving the inputSampleRate +argument from initialise to __init__() + +Outputs: +1) Zero crossing counts +2) Zero crossing locations + +Centre for Digital Music, Queen Mary University of London. +Copyright (C) 2009 Gyorgy Fazekas, QMUL. (See Vamp sources +for licence information.) + +''' + +class PyZeroCrossing: + + def __init__(self,inputSampleRate): + self.m_inputSampleRate = inputSampleRate + self.m_stepSize = 0 + self.m_blockSize = 0 + self.m_channels = 0 + self.previousSample = 0.0 + self.threshold = 0.005 + self.counter = 0 + + def initialise(self,channels,stepSize,blockSize): + self.m_channels = channels + self.m_stepSize = stepSize + self.m_blockSize = blockSize + return True + + def getMaker(self): + return 'Vampy Example Plugins' + + def getName(self): + return 'Vampy Zero Crossings' + + def getIdentifier(self): + return 'vampy-zc2' + + def getMaxChannelCount(self): + return 1 + + def getInputDomain(self): + return 'TimeDomain' + + def getOutputDescriptors(self): + + #descriptors can be returned as python dictionaries + output0={ + 'identifier':'vampy-counts', + 'name':'Number of Zero Crossings', + 'description':'Number of zero crossings per audio frame', + 'unit':' ', + 'hasFixedBinCount':True, + 'binCount':1, + #'binNames':['1 Hz',1.5,'2 Hz',3,'4 Hz'], + 'hasKnownExtents':False, + #'minValue':0.0, + #'maxValue':0.0, + 'isQuantized':True, + 'quantizeStep':1.0, + 'sampleType':'OneSamplePerStep' + #'sampleRate':48000.0 + } + + output1={ + 'identifier':'vampy-crossings', + 'name':'Zero Crossing Locations', + 'description':'The locations of zero crossing points', + 'unit':'discrete', + 'hasFixedBinCount':True, + 'binCount':0, + 'sampleType':'VariableSampleRate' + } + + return [output0,output1] + + + def getParameterDescriptors(self): + paramlist1={ + 'identifier':'threshold', + 'name':'Noise threshold', + 'description':'', + 'unit':'v', + 'minValue':0.0, + 'maxValue':0.5, + 'defaultValue':0.005, + 'isQuantized':False + } + return [paramlist1] + + + def setParameter(self,paramid,newval): + if paramid == 'threshold' : + self.threshold = newval + return + + + def getParameter(self,paramid): + if paramid == 'threshold' : + return self.threshold + else: + return 0.0 + + + # legacy process type: the input is a python list of samples + def process(self,inbuf,timestamp): + crossing = False + prev = self.previousSample + count = 0.0; + channel = inbuf[0] + + #we have two outputs defined thus we have to declare + #them as empty dictionaries in our output list + #in order to be able to return variable rate outputs + output0=[] + output1=[] + + if sum([abs(s) for s in channel]) > self.threshold : + + for x in range(len(channel)-1) : + crossing = False + sample = channel[x] + if sample <= 0.0 : + if prev > 0.0 : crossing = True + else : + if sample > 0.0 : + if prev <= 0.0 : crossing = True + + if crossing == True : + count = count + 1 + feature1={ + 'hasTimestamp':True, + 'timeStamp':long(timestamp + x), + 'values':[count], + 'label':str(count), + } + output1.append(feature1) + + prev = sample + self.previousSample = prev + + else : + count = 0.0 + self.previousSample = channel[len(channel)-1] + + feature0={ + 'hasTimestamp':False, + 'values':[count], + 'label':str(count) + } + output0.append(feature0) + + #return a LIST of list of dictionaries + return [output0,output1] + diff -r 000000000000 -r 27bab3a16c9a Example VamPy plugins/obsolete/PySpectralCentroid.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Example VamPy plugins/obsolete/PySpectralCentroid.py Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,132 @@ +'''PySpectralCentroid.py - Example plugin demonstrates +how to write a C style plugin using VamPy. + +Obsolete warning: this plugin will no longer be supported +since the legacy interface should pass a list of complex +numbers for frequency domain plugins and a list of floats +for time domin plugins. + +''' + +from numpy import * + +class PySpectralCentroid: + + def __init__(self,inputSampleRate): + self.m_imputSampleRate = 0.0 + self.m_stepSize = 0 + self.m_blockSize = 0 + self.m_channels = 0 + self.previousSample = 0.0 + self.m_inputSampleRate = inputSampleRate + self.threshold = 0.00 + + def initialise(self,channels,stepSize,blockSize): + self.m_channels = channels + self.m_stepSize = stepSize + self.m_blockSize = blockSize + return True + + def getMaker(self): + return 'Vampy Example Plugins' + + def getName(self): + return 'Spectral Centroid (legacy process interface)' + + def getIdentifier(self): + return 'vampy-sc2' + + def getMaxChannelCount(self): + return 1 + + def getInputDomain(self): + return 'FrequencyDomain' + + def getOutputDescriptors(self): + + output0={ + 'identifier':'vampy-sf1', + 'name':'Spectral Centroid', + 'description':'Spectral Centroid (Brightness)', + 'unit':' ', + 'hasFixedBinCount':True, + 'binCount':1, + 'hasKnownExtents':False, + 'isQuantized':True, + 'quantizeStep':1.0, + 'sampleType':'OneSamplePerStep' + } + + return [output0] + + def getParameterDescriptors(self): + paramlist1={ + 'identifier':'threshold', + 'name':'Noise threshold: ', + 'description':'Return null or delete this function if not needed.', + 'unit':'v', + 'minValue':0.0, + 'maxValue':0.5, + 'defaultValue':0.05, + 'isQuantized':False + } + return [paramlist1] + + def setParameter(self,paramid,newval): + if paramid == 'threshold' : + self.threshold = newval + return + + def getParameter(self,paramid): + if paramid == 'threshold' : + return self.threshold + else: + return 0.0 + + def process(self,inbuf,timestamp): + + inArray = array(inbuf[0]) + crossing = False + prev = self.previousSample + count = 0.0 + numLin = 0.0 + denom = 0.0 + centroid = 0.0 + + # re = array(inbuf[2:len(inArray):2]) + # im = array(inbuf[3:len(inArray):2]) + + output0=[] + output1=[] + + # pw = 0 + # for i in xrange(1,len(inbuf[0])) : + # pw = pw + abs(inbuf[0][i]) + + if sum(abs(inArray)) > self.threshold : + for i in range(1,(len(inArray)/2)) : + # for i in range(1,len(inbuf[0])) : + + re = inArray[i*2] + im = inArray[i*2+1] + # re = inbuf[0][i].real + # im = inbuf[0][i].imag + freq = i * self.m_inputSampleRate / self.m_blockSize + power = sqrt (re*re + im*im) / (self.m_blockSize/2) + denom = denom + power + numLin = numLin + freq * power + + if denom != 0 : + centroid = numLin / denom + + else : + centroid = 0.0 + + feature0={ + 'hasTimestamp':False, + 'values':[centroid], #strictly must be a list + 'label':str(centroid) + } + output0.append(feature0) + + return [output0] diff -r 000000000000 -r 27bab3a16c9a Example VamPy plugins/test/PyMFCC_buffer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Example VamPy plugins/test/PyMFCC_buffer.py Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,392 @@ +'''PyMFCC_buffer.py - This example Vampy plugin demonstrates +how to return sprectrogram-like features. + +This plugin uses the numpy BUFFER interface and +frequency domain input. Flag: vf_BUFFER + +Centre for Digital Music, Queen Mary University of London. +Copyright 2006 Gyorgy Fazekas, QMUL. +(See Vamp API for licence information.) + +Constants for Mel frequency conversion and filter +centre calculation are taken from the GNU GPL licenced +Freespeech library. Copyright (C) 1999 Jean-Marc Valin +''' + +import sys,numpy +from numpy import log,exp,floor,sum +from numpy import * +from numpy.fft import * +import vampy +from vampy import * + +class melScaling(object): + + def __init__(self,sampleRate,inputSize,numBands,minHz = 0,maxHz = None): + '''Initialise frequency warping and DCT matrix. + Parameters: + sampleRate: audio sample rate + inputSize: length of magnitude spectrum (half of FFT size assumed) + numBands: number of mel Bands (MFCCs) + minHz: lower bound of warping (default = DC) + maxHz: higher bound of warping (default = Nyquist frequency) + ''' + self.sampleRate = sampleRate + self.NqHz = sampleRate / 2.0 + self.minHz = minHz + if maxHz is None : maxHz = self.NqHz + self.maxHz = maxHz + self.inputSize = inputSize + self.numBands = numBands + self.valid = False + self.updated = False + + def update(self): + # make sure this will run only once if called from a vamp process + + if self.updated: return self.valid + self.updated = True + self.valid = False + print 'Updating parameters and recalculating filters: ' + print 'Nyquist: ',self.NqHz + + if self.maxHz > self.NqHz : + raise Exception('Maximum frequency must be smaller than the Nyquist frequency') + + self.maxMel = 1000*log(1+self.maxHz/700.0)/log(1+1000.0/700.0) + self.minMel = 1000*log(1+self.minHz/700.0)/log(1+1000.0/700.0) + print 'minHz:%s\nmaxHz:%s\nminMel:%s\nmaxMel:%s\n' %(self.minHz,self.maxHz,self.minMel,self.maxMel) + self.filterMatrix = self.getFilterMatrix(self.inputSize,self.numBands) + self.DCTMatrix = self.getDCTMatrix(self.numBands) + self.filterIter = self.filterMatrix.__iter__() + self.valid = True + return self.valid + + # try : + # self.maxMel = 1000*log(1+self.maxHz/700.0)/log(1+1000.0/700.0) + # self.minMel = 1000*log(1+self.minHz/700.0)/log(1+1000.0/700.0) + # self.filterMatrix = self.getFilterMatrix(self.inputSize,self.numBands) + # self.DCTMatrix = self.getDCTMatrix(self.numBands) + # self.filterIter = self.filterMatrix.__iter__() + # self.valid = True + # return True + # except : + # print "Invalid parameter setting encountered in MelScaling class." + # return False + # return True + + def getFilterCentres(self,inputSize,numBands): + '''Calculate Mel filter centres around FFT bins. + This function calculates two extra bands at the edges for + finding the starting and end point of the first and last + actual filters.''' + centresMel = numpy.array(xrange(numBands+2)) * (self.maxMel-self.minMel)/(numBands+1) + self.minMel + centresBin = numpy.floor(0.5 + 700.0*inputSize*(exp(centresMel*log(1+1000.0/700.0)/1000.0)-1)/self.NqHz) + return numpy.array(centresBin,int) + + def getFilterMatrix(self,inputSize,numBands): + '''Compose the Mel scaling matrix.''' + filterMatrix = numpy.zeros((numBands,inputSize)) + self.filterCentres = self.getFilterCentres(inputSize,numBands) + for i in xrange(numBands) : + start,centre,end = self.filterCentres[i:i+3] + self.setFilter(filterMatrix[i],start,centre,end) + return filterMatrix.transpose() + + def setFilter(self,filt,filterStart,filterCentre,filterEnd): + '''Calculate a single Mel filter.''' + k1 = numpy.float32(filterCentre-filterStart) + k2 = numpy.float32(filterEnd-filterCentre) + up = (numpy.array(xrange(filterStart,filterCentre))-filterStart)/k1 + dn = (filterEnd-numpy.array(xrange(filterCentre,filterEnd)))/k2 + filt[filterStart:filterCentre] = up + filt[filterCentre:filterEnd] = dn + + def warpSpectrum(self,magnitudeSpectrum): + '''Compute the Mel scaled spectrum.''' + return numpy.dot(magnitudeSpectrum,self.filterMatrix) + + def getDCTMatrix(self,size): + '''Calculate the square DCT transform matrix. Results are + equivalent to Matlab dctmtx(n) but with 64 bit precision.''' + DCTmx = numpy.array(xrange(size),numpy.float64).repeat(size).reshape(size,size) + DCTmxT = numpy.pi * (DCTmx.transpose()+0.5) / size + DCTmxT = (1.0/sqrt( size / 2.0)) * cos(DCTmx * DCTmxT) + DCTmxT[0] = DCTmxT[0] * (sqrt(2.0)/2.0) + return DCTmxT + + def dct(self,data_matrix): + '''Compute DCT of input matrix.''' + return numpy.dot(self.DCTMatrix,data_matrix) + + def getMFCCs(self,warpedSpectrum,cn=True): + '''Compute MFCC coefficients from Mel warped magnitude spectrum.''' + mfccs=self.dct(numpy.log(warpedSpectrum)) + if cn is False : mfccs[0] = 0.0 + return mfccs + + +class PyMFCC_buffer(melScaling): + + def __init__(self,inputSampleRate): + + # flags for setting some Vampy options + self.vampy_flags = vf_DEBUG | vf_BUFFER | vf_REALTIME + + self.m_inputSampleRate = int(inputSampleRate) + self.m_stepSize = 512 + self.m_blockSize = 2048 + self.m_channels = 1 + self.numBands = 40 + self.cnull = 1 + self.two_ch = False + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + + def initialise(self,channels,stepSize,blockSize): + self.m_channels = channels + self.m_stepSize = stepSize + self.m_blockSize = blockSize + self.window = numpy.hamming(blockSize) + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + return True + + def getMaker(self): + return 'Vampy Test Plugins' + + def getCopyright(self): + return 'Plugin By George Fazekas' + + def getName(self): + return 'Vampy Buffer MFCC Plugin' + + def getIdentifier(self): + return 'vampy-mfcc-test-buffer' + + def getDescription(self): + return 'A simple MFCC plugin. (using the Buffer interface)' + + def getMaxChannelCount(self): + return 2 + + def getInputDomain(self): + return FrequencyDomain + + def getPreferredBlockSize(self): + return 2048 + + def getPreferredStepSize(self): + return 512 + + def getOutputDescriptors(self): + + Generic = OutputDescriptor() + Generic.hasFixedBinCount=True + Generic.binCount=int(self.numBands)-self.cnull + Generic.hasKnownExtents=False + Generic.isQuantized=True + Generic.sampleType = OneSamplePerStep + + # note the inheritance of attributes (use is optional) + MFCC = OutputDescriptor(Generic) + MFCC.identifier = 'mfccs' + MFCC.name = 'MFCCs' + MFCC.description = 'MFCC Coefficients' + MFCC.binNames=map(lambda x: 'C '+str(x),range(self.cnull,int(self.numBands))) + MFCC.unit = None + if self.two_ch and self.m_channels == 2 : + MFCC.binCount = self.m_channels * (int(self.numBands)-self.cnull) + else : + MFCC.binCount = self.numBands-self.cnull + + warpedSpectrum = OutputDescriptor(Generic) + warpedSpectrum.identifier='warped-fft' + warpedSpectrum.name='Mel Scaled Spectrum' + warpedSpectrum.description='Mel Scaled Magnitide Spectrum' + warpedSpectrum.unit='Mel' + if self.two_ch and self.m_channels == 2 : + warpedSpectrum.binCount = self.m_channels * int(self.numBands) + else : + warpedSpectrum.binCount = self.numBands + + melFilter = OutputDescriptor(Generic) + melFilter.identifier = 'mel-filter-matrix' + melFilter.sampleType='FixedSampleRate' + melFilter.sampleRate=self.m_inputSampleRate/self.m_stepSize + melFilter.name='Mel Filter Matrix' + melFilter.description='Returns the created filter matrix in getRemainingFeatures.' + melFilter.unit = None + + return OutputList(MFCC,warpedSpectrum,melFilter) + + + def getParameterDescriptors(self): + + melbands = ParameterDescriptor() + melbands.identifier='melbands' + melbands.name='Number of bands (coefficients)' + melbands.description='Set the number of coefficients.' + melbands.unit = '' + melbands.minValue = 2 + melbands.maxValue = 128 + melbands.defaultValue = 40 + melbands.isQuantized = True + melbands.quantizeStep = 1 + + cnull = ParameterDescriptor() + cnull.identifier='cnull' + cnull.name='Return C0' + cnull.description='Select if the DC coefficient is required.' + cnull.unit = None + cnull.minValue = 0 + cnull.maxValue = 1 + cnull.defaultValue = 0 + cnull.isQuantized = True + cnull.quantizeStep = 1 + + two_ch = ParameterDescriptor(cnull) + two_ch.identifier='two_ch' + two_ch.name='Process channels separately' + two_ch.description='Process two channel files separately.' + two_ch.defaultValue = False + + minHz = ParameterDescriptor() + minHz.identifier='minHz' + minHz.name='minimum frequency' + minHz.description='Set the lower frequency bound.' + minHz.unit='Hz' + minHz.minValue = 0 + minHz.maxValue = 24000 + minHz.defaultValue = 0 + minHz.isQuantized = True + minHz.quantizeStep = 1.0 + + maxHz = ParameterDescriptor() + maxHz.identifier='maxHz' + maxHz.description='Set the upper frequency bound.' + maxHz.name='maximum frequency' + maxHz.unit='Hz' + maxHz.minValue = 100 + maxHz.maxValue = 24000 + maxHz.defaultValue = 11025 + maxHz.isQuantized = True + maxHz.quantizeStep = 100 + + return ParameterList(melbands,minHz,maxHz,cnull,two_ch) + + + def setParameter(self,paramid,newval): + self.valid = False + if paramid == 'minHz' : + if newval < self.maxHz and newval < self.NqHz : + self.minHz = float(newval) + print 'minHz: ', self.minHz + if paramid == 'maxHz' : + print 'trying to set maxHz to: ',newval + if newval < self.NqHz and newval > self.minHz+1000 : + self.maxHz = float(newval) + else : + self.maxHz = self.NqHz + print 'set to: ',self.maxHz + if paramid == 'cnull' : + self.cnull = int(not int(newval)) + if paramid == 'melbands' : + self.numBands = int(newval) + if paramid == 'two_ch' : + self.two_ch = bool(newval) + + return + + def getParameter(self,paramid): + if paramid == 'minHz' : + return float(self.minHz) + if paramid == 'maxHz' : + return float(self.maxHz) + if paramid == 'cnull' : + return float(not int(self.cnull)) + if paramid == 'melbands' : + return float(self.numBands) + if paramid == 'two_ch' : + return float(self.two_ch) + else: + return 0.0 + + # numpy process using the buffer interface + def process(self,inputbuffers,timestamp): + + if not self.update() : return None + + if self.m_channels == 2 and self.two_ch : + return self.process2ch(inputbuffers,timestamp) + + fftsize = self.m_blockSize + + if self.m_channels > 1 : + # take the mean of the two magnitude spectra + complexSpectrum0 = frombuffer(inputbuffers[0],complex64,-1,0) + complexSpectrum1 = frombuffer(inputbuffers[1],complex64,-1,0) + magnitudeSpectrum0 = abs(complexSpectrum0)[0:fftsize/2] + magnitudeSpectrum1 = abs(complexSpectrum1)[0:fftsize/2] + magnitudeSpectrum = (magnitudeSpectrum0 + magnitudeSpectrum1) / 2 + else : + complexSpectrum = frombuffer(inputbuffers[0],complex64,-1,0) + magnitudeSpectrum = abs(complexSpectrum)[0:fftsize/2] + + # do the computation + melSpectrum = self.warpSpectrum(magnitudeSpectrum) + melCepstrum = self.getMFCCs(melSpectrum,cn=True) + + # output feature set (the builtin dict type can also be used) + outputs = FeatureSet() + outputs[0] = Feature(melCepstrum[self.cnull:]) + outputs[1] = Feature(melSpectrum) + + return outputs + + # process two channel files (stack the returned arrays) + def process2ch(self,inputbuffers,timestamp): + + fftsize = self.m_blockSize + + complexSpectrum0 = frombuffer(inputbuffers[0],complex64,-1,0) + complexSpectrum1 = frombuffer(inputbuffers[1],complex64,-1,0) + + magnitudeSpectrum0 = abs(complexSpectrum0)[0:fftsize/2] + magnitudeSpectrum1 = abs(complexSpectrum1)[0:fftsize/2] + + # do the computations + melSpectrum0 = self.warpSpectrum(magnitudeSpectrum0) + melCepstrum0 = self.getMFCCs(melSpectrum0,cn=True) + melSpectrum1 = self.warpSpectrum(magnitudeSpectrum1) + melCepstrum1 = self.getMFCCs(melSpectrum1,cn=True) + + outputs = FeatureSet() + + outputs[0] = Feature(hstack((melCepstrum1[self.cnull:],melCepstrum0[self.cnull:]))) + + outputs[1] = Feature(hstack((melSpectrum1,melSpectrum0))) + + return outputs + + + def getRemainingFeatures(self): + if not self.update() : return [] + frameSampleStart = 0 + + output_featureSet = FeatureSet() + + # the filter is the third output (index starts from zero) + output_featureSet[2] = flist = FeatureList() + + while True: + f = Feature() + f.hasTimestamp = True + f.timestamp = frame2RealTime(frameSampleStart,self.m_inputSampleRate) + try : + f.values = self.filterIter.next() + except StopIteration : + break + flist.append(f) + frameSampleStart += self.m_stepSize + + return output_featureSet + \ No newline at end of file diff -r 000000000000 -r 27bab3a16c9a Example VamPy plugins/test/PyMFCC_freq.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Example VamPy plugins/test/PyMFCC_freq.py Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,392 @@ +'''PyMFCC_freq.py - This example Vampy plugin demonstrates +how to return sprectrogram-like features. + +This plugin has frequency domain input and is using +the numpy array interface. Flag: vf_ARRAY + +Centre for Digital Music, Queen Mary University of London. +Copyright 2006 Gyorgy Fazekas, QMUL. +(See Vamp API for licence information.) + +Constants for Mel frequency conversion and filter +centre calculation are taken from the GNU GPL licenced +Freespeech library. Copyright (C) 1999 Jean-Marc Valin +''' + +import sys,numpy +from numpy import log,exp,floor,sum +from numpy import * +from numpy.fft import * +import vampy +from vampy import * + + +class melScaling(object): + + def __init__(self,sampleRate,inputSize,numBands,minHz = 0,maxHz = None): + '''Initialise frequency warping and DCT matrix. + Parameters: + sampleRate: audio sample rate + inputSize: length of magnitude spectrum (half of FFT size assumed) + numBands: number of mel Bands (MFCCs) + minHz: lower bound of warping (default = DC) + maxHz: higher bound of warping (default = Nyquist frequency) + ''' + self.sampleRate = sampleRate + self.NqHz = sampleRate / 2.0 + self.minHz = minHz + if maxHz is None : maxHz = self.NqHz + self.maxHz = maxHz + self.inputSize = inputSize + self.numBands = numBands + self.valid = False + self.updated = False + + + def update(self): + # make sure this will run only once if called from a vamp process + + if self.updated: return self.valid + self.updated = True + self.valid = False + print 'Updating parameters and recalculating filters: ' + print 'Nyquist: ',self.NqHz + + if self.maxHz > self.NqHz : + raise Exception('Maximum frequency must be smaller than the Nyquist frequency') + + self.maxMel = 1000*log(1+self.maxHz/700.0)/log(1+1000.0/700.0) + self.minMel = 1000*log(1+self.minHz/700.0)/log(1+1000.0/700.0) + print 'minHz:%s\nmaxHz:%s\nminMel:%s\nmaxMel:%s\n' %(self.minHz,self.maxHz,self.minMel,self.maxMel) + self.filterMatrix = self.getFilterMatrix(self.inputSize,self.numBands) + self.DCTMatrix = self.getDCTMatrix(self.numBands) + self.filterIter = self.filterMatrix.__iter__() + self.valid = True + return self.valid + + # try : + # self.maxMel = 1000*log(1+self.maxHz/700.0)/log(1+1000.0/700.0) + # self.minMel = 1000*log(1+self.minHz/700.0)/log(1+1000.0/700.0) + # self.filterMatrix = self.getFilterMatrix(self.inputSize,self.numBands) + # self.DCTMatrix = self.getDCTMatrix(self.numBands) + # self.filterIter = self.filterMatrix.__iter__() + # self.valid = True + # return True + # except : + # print "Invalid parameter setting encountered in MelScaling class." + # return False + # return True + + def getFilterCentres(self,inputSize,numBands): + '''Calculate Mel filter centres around FFT bins. + This function calculates two extra bands at the edges for + finding the starting and end point of the first and last + actual filters.''' + centresMel = numpy.array(xrange(numBands+2)) * (self.maxMel-self.minMel)/(numBands+1) + self.minMel + centresBin = numpy.floor(0.5 + 700.0*inputSize*(exp(centresMel*log(1+1000.0/700.0)/1000.0)-1)/self.NqHz) + return numpy.array(centresBin,int) + + def getFilterMatrix(self,inputSize,numBands): + '''Compose the Mel scaling matrix.''' + filterMatrix = numpy.zeros((numBands,inputSize)) + self.filterCentres = self.getFilterCentres(inputSize,numBands) + for i in xrange(numBands) : + start,centre,end = self.filterCentres[i:i+3] + self.setFilter(filterMatrix[i],start,centre,end) + return filterMatrix.transpose() + + def setFilter(self,filt,filterStart,filterCentre,filterEnd): + '''Calculate a single Mel filter.''' + k1 = numpy.float32(filterCentre-filterStart) + k2 = numpy.float32(filterEnd-filterCentre) + up = (numpy.array(xrange(filterStart,filterCentre))-filterStart)/k1 + dn = (filterEnd-numpy.array(xrange(filterCentre,filterEnd)))/k2 + filt[filterStart:filterCentre] = up + filt[filterCentre:filterEnd] = dn + + def warpSpectrum(self,magnitudeSpectrum): + '''Compute the Mel scaled spectrum.''' + return numpy.dot(magnitudeSpectrum,self.filterMatrix) + + def getDCTMatrix(self,size): + '''Calculate the square DCT transform matrix. Results are + equivalent to Matlab dctmtx(n) but with 64 bit precision.''' + DCTmx = numpy.array(xrange(size),numpy.float64).repeat(size).reshape(size,size) + DCTmxT = numpy.pi * (DCTmx.transpose()+0.5) / size + DCTmxT = (1.0/sqrt( size / 2.0)) * cos(DCTmx * DCTmxT) + DCTmxT[0] = DCTmxT[0] * (sqrt(2.0)/2.0) + return DCTmxT + + def dct(self,data_matrix): + '''Compute DCT of input matrix.''' + return numpy.dot(self.DCTMatrix,data_matrix) + + def getMFCCs(self,warpedSpectrum,cn=True): + '''Compute MFCC coefficients from Mel warped magnitude spectrum.''' + mfccs=self.dct(numpy.log(warpedSpectrum)) + if cn is False : mfccs[0] = 0.0 + return mfccs + + +class PyMFCC_freq(melScaling): + + def __init__(self,inputSampleRate): + + # flags for setting some Vampy options + self.vampy_flags = vf_DEBUG | vf_ARRAY | vf_REALTIME + + self.m_inputSampleRate = int(inputSampleRate) + self.m_stepSize = 512 + self.m_blockSize = 2048 + self.m_channels = 1 + self.numBands = 40 + self.cnull = 1 + self.two_ch = False + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + + def initialise(self,channels,stepSize,blockSize): + self.m_channels = channels + self.m_stepSize = stepSize + self.m_blockSize = blockSize + self.window = numpy.hamming(blockSize) + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + return True + + def getMaker(self): + return 'Vampy Test Plugins' + + def getCopyright(self): + return 'Plugin By George Fazekas' + + def getName(self): + return 'Vampy FrequencyDomain MFCC Plugin' + + def getIdentifier(self): + return 'vampy-mfcc-test-freq' + + def getDescription(self): + return 'A simple MFCC plugin. (FrequencyDomain)' + + def getMaxChannelCount(self): + return 2 + + def getInputDomain(self): + return FrequencyDomain + + def getPreferredBlockSize(self): + return 2048 + + def getPreferredStepSize(self): + return 512 + + def getOutputDescriptors(self): + + Generic = OutputDescriptor() + Generic.hasFixedBinCount=True + Generic.binCount=int(self.numBands)-self.cnull + Generic.hasKnownExtents=False + Generic.isQuantized=True + Generic.sampleType = OneSamplePerStep + + # note the inheritance of attributes (use is optional) + MFCC = OutputDescriptor(Generic) + MFCC.identifier = 'mfccs' + MFCC.name = 'MFCCs' + MFCC.description = 'MFCC Coefficients' + MFCC.binNames=map(lambda x: 'C '+str(x),range(self.cnull,int(self.numBands))) + MFCC.unit = None + if self.two_ch and self.m_channels == 2 : + MFCC.binCount = self.m_channels * (int(self.numBands)-self.cnull) + else : + MFCC.binCount = self.numBands-self.cnull + + warpedSpectrum = OutputDescriptor(Generic) + warpedSpectrum.identifier='warped-fft' + warpedSpectrum.name='Mel Scaled Spectrum' + warpedSpectrum.description='Mel Scaled Magnitide Spectrum' + warpedSpectrum.unit='Mel' + if self.two_ch and self.m_channels == 2 : + warpedSpectrum.binCount = self.m_channels * int(self.numBands) + else : + warpedSpectrum.binCount = self.numBands + + melFilter = OutputDescriptor(Generic) + melFilter.identifier = 'mel-filter-matrix' + melFilter.sampleType='FixedSampleRate' + melFilter.sampleRate=self.m_inputSampleRate/self.m_stepSize + melFilter.name='Mel Filter Matrix' + melFilter.description='Returns the created filter matrix in getRemainingFeatures.' + melFilter.unit = None + + return OutputList(MFCC,warpedSpectrum,melFilter) + + + def getParameterDescriptors(self): + + melbands = ParameterDescriptor() + melbands.identifier='melbands' + melbands.name='Number of bands (coefficients)' + melbands.description='Set the number of coefficients.' + melbands.unit = '' + melbands.minValue = 2 + melbands.maxValue = 128 + melbands.defaultValue = 40 + melbands.isQuantized = True + melbands.quantizeStep = 1 + + cnull = ParameterDescriptor() + cnull.identifier='cnull' + cnull.name='Return C0' + cnull.description='Select if the DC coefficient is required.' + cnull.unit = None + cnull.minValue = 0 + cnull.maxValue = 1 + cnull.defaultValue = 0 + cnull.isQuantized = True + cnull.quantizeStep = 1 + + two_ch = ParameterDescriptor(cnull) + two_ch.identifier='two_ch' + two_ch.name='Process channels separately' + two_ch.description='Process two channel files separately.' + two_ch.defaultValue = False + + minHz = ParameterDescriptor() + minHz.identifier='minHz' + minHz.name='minimum frequency' + minHz.description='Set the lower frequency bound.' + minHz.unit='Hz' + minHz.minValue = 0 + minHz.maxValue = 24000 + minHz.defaultValue = 0 + minHz.isQuantized = True + minHz.quantizeStep = 1.0 + + maxHz = ParameterDescriptor() + maxHz.identifier='maxHz' + maxHz.description='Set the upper frequency bound.' + maxHz.name='maximum frequency' + maxHz.unit='Hz' + maxHz.minValue = 100 + maxHz.maxValue = 24000 + maxHz.defaultValue = 11025 + maxHz.isQuantized = True + maxHz.quantizeStep = 100 + + return ParameterList(melbands,minHz,maxHz,cnull,two_ch) + + + def setParameter(self,paramid,newval): + self.valid = False + if paramid == 'minHz' : + if newval < self.maxHz and newval < self.NqHz : + self.minHz = float(newval) + print 'minHz: ', self.minHz + if paramid == 'maxHz' : + print 'trying to set maxHz to: ',newval + if newval < self.NqHz and newval > self.minHz+1000 : + self.maxHz = float(newval) + else : + self.maxHz = self.NqHz + print 'set to: ',self.maxHz + if paramid == 'cnull' : + self.cnull = int(not int(newval)) + if paramid == 'melbands' : + self.numBands = int(newval) + if paramid == 'two_ch' : + self.two_ch = bool(newval) + + return + + def getParameter(self,paramid): + if paramid == 'minHz' : + return float(self.minHz) + if paramid == 'maxHz' : + return float(self.maxHz) + if paramid == 'cnull' : + return float(not int(self.cnull)) + if paramid == 'melbands' : + return float(self.numBands) + if paramid == 'two_ch' : + return float(self.two_ch) + else: + return 0.0 + + # set numpy process using the 'use_numpy_interface' flag + def process(self,inputbuffers,timestamp): + + if not self.update() : return None + + if self.m_channels == 2 and self.two_ch : + return self.process2ch(inputbuffers,timestamp) + + fftsize = self.m_blockSize + + if self.m_channels > 1 : + # take the mean of the two magnitude spectra + complexSpectrum0 = inputbuffers[0] + complexSpectrum1 = inputbuffers[1] + magnitudeSpectrum0 = abs(complexSpectrum0)[0:fftsize/2] + magnitudeSpectrum1 = abs(complexSpectrum1)[0:fftsize/2] + magnitudeSpectrum = (magnitudeSpectrum0 + magnitudeSpectrum1) / 2 + else : + complexSpectrum = inputbuffers[0] + magnitudeSpectrum = abs(complexSpectrum)[0:fftsize/2] + + # do the computation + melSpectrum = self.warpSpectrum(magnitudeSpectrum) + melCepstrum = self.getMFCCs(melSpectrum,cn=True) + + outputs = FeatureSet() + outputs[0] = Feature(melCepstrum[self.cnull:]) + outputs[1] = Feature(melSpectrum) + return outputs + + + # process channels separately (stack the returned arrays) + def process2ch(self,inputbuffers,timestamp): + + fftsize = self.m_blockSize + + complexSpectrum0 = inputbuffers[0] + complexSpectrum1 = inputbuffers[1] + + magnitudeSpectrum0 = abs(complexSpectrum0)[0:fftsize/2] + magnitudeSpectrum1 = abs(complexSpectrum1)[0:fftsize/2] + + # do the computations + melSpectrum0 = self.warpSpectrum(magnitudeSpectrum0) + melCepstrum0 = self.getMFCCs(melSpectrum0,cn=True) + melSpectrum1 = self.warpSpectrum(magnitudeSpectrum1) + melCepstrum1 = self.getMFCCs(melSpectrum1,cn=True) + + outputs = FeatureSet() + + outputs[0] = Feature(hstack((melCepstrum1[self.cnull:],melCepstrum0[self.cnull:]))) + + outputs[1] = Feature(hstack((melSpectrum1,melSpectrum0))) + + return outputs + + + def getRemainingFeatures(self): + if not self.update() : return [] + frameSampleStart = 0 + + output_featureSet = FeatureSet() + + # the filter is the third output (index starts from zero) + output_featureSet[2] = flist = FeatureList() + + while True: + f = Feature() + f.hasTimestamp = True + f.timestamp = frame2RealTime(frameSampleStart,self.m_inputSampleRate) + try : + f.values = self.filterIter.next() + except StopIteration : + break + flist.append(f) + frameSampleStart += self.m_stepSize + + return output_featureSet diff -r 000000000000 -r 27bab3a16c9a Example VamPy plugins/test/PyMFCC_oldstyle.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Example VamPy plugins/test/PyMFCC_oldstyle.py Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,361 @@ +'''PyMFCC_oldstyle.py - This example Vampy plugin demonstrates +how to return sprectrogram-like features. + +This plugin uses backward compatible syntax and +no extension module. + +Centre for Digital Music, Queen Mary University of London. +Copyright 2006 Gyorgy Fazekas, QMUL. +(See Vamp API for licence information.) + +Constants for Mel frequency conversion and filter +centre calculation are taken from the GNU GPL licenced +Freespeech library. Copyright (C) 1999 Jean-Marc Valin +''' + +import sys,numpy +from numpy import log,exp,floor,sum +from numpy import * +from numpy.fft import * + + +class melScaling(object): + + def __init__(self,sampleRate,inputSize,numBands,minHz = 0,maxHz = None): + '''Initialise frequency warping and DCT matrix. + Parameters: + sampleRate: audio sample rate + inputSize: length of magnitude spectrum (half of FFT size assumed) + numBands: number of mel Bands (MFCCs) + minHz: lower bound of warping (default = DC) + maxHz: higher bound of warping (default = Nyquist frequency) + ''' + self.sampleRate = sampleRate + self.NqHz = sampleRate / 2.0 + self.minHz = minHz + if maxHz is None : maxHz = self.NqHz + self.maxHz = maxHz + self.inputSize = inputSize + self.numBands = numBands + self.valid = False + self.updated = False + # print '\n\n>>Plugin initialised with sample rate: %s<<\n\n' %self.sampleRate + # print 'minHz:%s\nmaxHz:%s\n' %(self.minHz,self.maxHz) + + + def update(self): + # make sure this will run only once if called from a vamp process + + if self.updated: return self.valid + self.updated = True + self.valid = False + print 'Updating parameters and recalculating filters: ' + print 'Nyquist: ',self.NqHz + + if self.maxHz > self.NqHz : + raise Exception('Maximum frequency must be smaller than the Nyquist frequency') + + self.maxMel = 1000*log(1+self.maxHz/700.0)/log(1+1000.0/700.0) + self.minMel = 1000*log(1+self.minHz/700.0)/log(1+1000.0/700.0) + print 'minHz:%s\nmaxHz:%s\nminMel:%s\nmaxMel:%s\n' %(self.minHz,self.maxHz,self.minMel,self.maxMel) + self.filterMatrix = self.getFilterMatrix(self.inputSize,self.numBands) + self.DCTMatrix = self.getDCTMatrix(self.numBands) + self.filterIter = self.filterMatrix.__iter__() + self.valid = True + return self.valid + + # try : + # self.maxMel = 1000*log(1+self.maxHz/700.0)/log(1+1000.0/700.0) + # self.minMel = 1000*log(1+self.minHz/700.0)/log(1+1000.0/700.0) + # self.filterMatrix = self.getFilterMatrix(self.inputSize,self.numBands) + # self.DCTMatrix = self.getDCTMatrix(self.numBands) + # self.filterIter = self.filterMatrix.__iter__() + # self.valid = True + # return True + # except : + # print "Invalid parameter setting encountered in MelScaling class." + # return False + # return True + + def getFilterCentres(self,inputSize,numBands): + '''Calculate Mel filter centres around FFT bins. + This function calculates two extra bands at the edges for + finding the starting and end point of the first and last + actual filters.''' + centresMel = numpy.array(xrange(numBands+2)) * (self.maxMel-self.minMel)/(numBands+1) + self.minMel + centresBin = numpy.floor(0.5 + 700.0*inputSize*(exp(centresMel*log(1+1000.0/700.0)/1000.0)-1)/self.NqHz) + return numpy.array(centresBin,int) + + def getFilterMatrix(self,inputSize,numBands): + '''Compose the Mel scaling matrix.''' + filterMatrix = numpy.zeros((numBands,inputSize)) + self.filterCentres = self.getFilterCentres(inputSize,numBands) + for i in xrange(numBands) : + start,centre,end = self.filterCentres[i:i+3] + self.setFilter(filterMatrix[i],start,centre,end) + return filterMatrix.transpose() + + def setFilter(self,filt,filterStart,filterCentre,filterEnd): + '''Calculate a single Mel filter.''' + k1 = numpy.float32(filterCentre-filterStart) + k2 = numpy.float32(filterEnd-filterCentre) + up = (numpy.array(xrange(filterStart,filterCentre))-filterStart)/k1 + dn = (filterEnd-numpy.array(xrange(filterCentre,filterEnd)))/k2 + filt[filterStart:filterCentre] = up + filt[filterCentre:filterEnd] = dn + + def warpSpectrum(self,magnitudeSpectrum): + '''Compute the Mel scaled spectrum.''' + return numpy.dot(magnitudeSpectrum,self.filterMatrix) + + def getDCTMatrix(self,size): + '''Calculate the square DCT transform matrix. Results are + equivalent to Matlab dctmtx(n) but with 64 bit precision.''' + DCTmx = numpy.array(xrange(size),numpy.float64).repeat(size).reshape(size,size) + DCTmxT = numpy.pi * (DCTmx.transpose()+0.5) / size + DCTmxT = (1.0/sqrt( size / 2.0)) * cos(DCTmx * DCTmxT) + DCTmxT[0] = DCTmxT[0] * (sqrt(2.0)/2.0) + return DCTmxT + + def dct(self,data_matrix): + '''Compute DCT of input matrix.''' + return numpy.dot(self.DCTMatrix,data_matrix) + + def getMFCCs(self,warpedSpectrum,cn=True): + '''Compute MFCC coefficients from Mel warped magnitude spectrum.''' + mfccs=self.dct(numpy.log(warpedSpectrum)) + if cn is False : mfccs[0] = 0.0 + return mfccs + + +class PyMFCC_oldstyle(melScaling): + + def __init__(self,inputSampleRate): + self.vampy_flags = 1 # vf_DEBUG = 1 + self.m_inputSampleRate = inputSampleRate + self.m_stepSize = 1024 + self.m_blockSize = 2048 + self.m_channels = 1 + self.numBands = 40 + self.cnull = 1 + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + + def initialise(self,channels,stepSize,blockSize): + self.m_channels = channels + self.m_stepSize = stepSize + self.m_blockSize = blockSize + self.window = numpy.hamming(blockSize) + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + return True + + def getMaker(self): + return 'Vampy Test Plugins' + + def getCopyright(self): + return 'Plugin By George Fazekas' + + def getName(self): + return 'Vampy Old Style MFCC Plugin' + + def getIdentifier(self): + return 'vampy-mfcc-test-old' + + def getDescription(self): + return 'A simple MFCC plugin. (using the old syntax)' + + def getMaxChannelCount(self): + return 1 + + def getInputDomain(self): + return 'TimeDomain' + + def getPreferredBlockSize(self): + return 2048 + + def getPreferredStepSize(self): + return 512 + + def getOutputDescriptors(self): + + Generic={ + 'hasFixedBinCount':True, + 'binCount':int(self.numBands)-self.cnull, + 'hasKnownExtents':False, + 'isQuantized':True, + 'sampleType':'OneSamplePerStep' + } + + MFCC=Generic.copy() + MFCC.update({ + 'identifier':'mfccs', + 'name':'MFCCs', + 'description':'MFCC Coefficients', + 'binNames':map(lambda x: 'C '+str(x),range(self.cnull,int(self.numBands))), + 'unit':'' + }) + + warpedSpectrum=Generic.copy() + warpedSpectrum.update({ + 'identifier':'warped-fft', + 'name':'Mel Scaled Spectrum', + 'description':'Mel Scaled Magnitide Spectrum', + 'unit':'Mel' + }) + + melFilter=Generic.copy() + melFilter.update({ + 'identifier':'mel-filter', + 'name':'Mel Filter Matrix', + 'description':'Returns the created filter matrix.', + 'sampleType':'FixedSampleRate', + 'sampleRate':self.m_inputSampleRate/self.m_stepSize, + 'unit':'' + }) + + return [MFCC,warpedSpectrum,melFilter] + + def getParameterDescriptors(self): + melbands = { + 'identifier':'melbands', + 'name':'Number of bands (coefficients)', + 'description':'Set the number of coefficients.', + 'unit':'', + 'minValue':2.0, + 'maxValue':128.0, + 'defaultValue':40.0, + 'isQuantized':True, + 'quantizeStep':1.0 + } + + cnull = { + 'identifier':'cnull', + 'name':'Return C0', + 'description':'Select if the DC coefficient is required.', + 'unit':'', + 'minValue':0.0, + 'maxValue':1.0, + 'defaultValue':0.0, + 'isQuantized':True, + 'quantizeStep':1.0 + } + + minHz = { + 'identifier':'minHz', + 'name':'minimum frequency', + 'description':'Set the lower frequency bound.', + 'unit':'Hz', + 'minValue':0.0, + 'maxValue':24000.0, + 'defaultValue':0.0, + 'isQuantized':True, + 'quantizeStep':1.0 + } + + maxHz = { + 'identifier':'maxHz', + 'name':'maximum frequency', + 'description':'Set the upper frequency bound.', + 'unit':'Hz', + 'minValue':100.0, + 'maxValue':24000.0, + 'defaultValue':11025.0, + 'isQuantized':True, + 'quantizeStep':100.0 + } + + return [melbands,minHz,maxHz,cnull] + + def setParameter(self,paramid,newval): + self.valid = False + if paramid == 'minHz' : + if newval < self.maxHz and newval < self.NqHz : + self.minHz = float(newval) + print 'minHz: ', self.minHz + if paramid == 'maxHz' : + print 'trying to set maxHz to: ',newval + if newval < self.NqHz and newval > self.minHz+1000 : + self.maxHz = float(newval) + else : + self.maxHz = self.NqHz + print 'set to: ',self.maxHz + if paramid == 'cnull' : + self.cnull = int(not int(newval)) + if paramid == 'melbands' : + self.numBands = int(newval) + return + + def getParameter(self,paramid): + if paramid == 'minHz' : + return float(self.minHz) + if paramid == 'maxHz' : + return float(self.maxHz) + if paramid == 'cnull' : + return float(not int(self.cnull)) + if paramid == 'melbands' : + return float(self.numBands) + else: + return 0.0 + + def processN(self,membuffer,frameSampleStart): + + # recalculate the filter and DCT matrices if needed + if not self.update() : return [] + + fftsize = self.m_blockSize + audioSamples = frombuffer(membuffer[0],float32) + + complexSpectrum = fft(self.window*audioSamples,fftsize) + #complexSpectrum = frombuffer(membuffer[0],complex64,-1,8) + + magnitudeSpectrum = abs(complexSpectrum)[0:fftsize/2] / (fftsize/2) + melSpectrum = self.warpSpectrum(magnitudeSpectrum) + melCepstrum = self.getMFCCs(melSpectrum,cn=True) + + output_melCepstrum = [{ + 'hasTimestamp':False, + 'values':melCepstrum[self.cnull:].tolist() + }] + + output_melSpectrum = [{ + 'hasTimestamp':False, + 'values':melSpectrum.tolist() + }] + + return [output_melCepstrum,output_melSpectrum,[]] + + + def getRemainingFeatures(self): + if not self.update() : return [] + frameSampleStart = 0 + output_melFilter = [] + + while True: + try : + melFilter = self.filterIter.next() + output_melFilter.append({ + 'hasTimestamp':True, + 'timeStamp':frameSampleStart, + 'values':melFilter.tolist() + }) + frameSampleStart += self.m_stepSize + except StopIteration : + break; + + return [[],[],output_melFilter] + + +# ============================================ +# Simple Unit Tests +# ============================================ + +def main(): + + dct = melScaling(44100,2048,numBands=4) + dct.update() + print dct.DCTMatrix + # print dct.getMFCCs(numpy.array([0.0,0.1,0.0,-0.1],numpy.float64)) + sys.exit(-1) + +if __name__ == '__main__': + main() + diff -r 000000000000 -r 27bab3a16c9a Example VamPy plugins/test/PyMFCC_time.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Example VamPy plugins/test/PyMFCC_time.py Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,398 @@ +'''PyMFCC_time.py - This example Vampy plugin demonstrates +how to return sprectrogram-like features. + +This plugin has time domain input and is using +the numpy array interface. Flag: vf_ARRAY + +Centre for Digital Music, Queen Mary University of London. +Copyright 2006 Gyorgy Fazekas, QMUL. +(See Vamp API for licence information.) + +Constants for Mel frequency conversion and filter +centre calculation are taken from the GNU GPL licenced +Freespeech library. Copyright (C) 1999 Jean-Marc Valin +''' + +import sys,numpy +from numpy import log,exp,floor,sum +from numpy import * +from numpy.fft import * +import vampy +from vampy import * + + +class melScaling(object): + + def __init__(self,sampleRate,inputSize,numBands,minHz = 0,maxHz = None): + '''Initialise frequency warping and DCT matrix. + Parameters: + sampleRate: audio sample rate + inputSize: length of magnitude spectrum (half of FFT size assumed) + numBands: number of mel Bands (MFCCs) + minHz: lower bound of warping (default = DC) + maxHz: higher bound of warping (default = Nyquist frequency) + ''' + self.sampleRate = sampleRate + self.NqHz = sampleRate / 2.0 + self.minHz = minHz + if maxHz is None : maxHz = self.NqHz + self.maxHz = maxHz + self.inputSize = inputSize + self.numBands = numBands + self.valid = False + self.updated = False + + + def update(self): + # make sure this will run only once if called from a vamp process + + if self.updated: return self.valid + self.updated = True + self.valid = False + print 'Updating parameters and recalculating filters: ' + print 'Nyquist: ',self.NqHz + + if self.maxHz > self.NqHz : + raise Exception('Maximum frequency must be smaller than the Nyquist frequency') + + self.maxMel = 1000*log(1+self.maxHz/700.0)/log(1+1000.0/700.0) + self.minMel = 1000*log(1+self.minHz/700.0)/log(1+1000.0/700.0) + print 'minHz:%s\nmaxHz:%s\nminMel:%s\nmaxMel:%s\n' %(self.minHz,self.maxHz,self.minMel,self.maxMel) + self.filterMatrix = self.getFilterMatrix(self.inputSize,self.numBands) + self.DCTMatrix = self.getDCTMatrix(self.numBands) + self.filterIter = self.filterMatrix.__iter__() + self.valid = True + return self.valid + + # try : + # self.maxMel = 1000*log(1+self.maxHz/700.0)/log(1+1000.0/700.0) + # self.minMel = 1000*log(1+self.minHz/700.0)/log(1+1000.0/700.0) + # self.filterMatrix = self.getFilterMatrix(self.inputSize,self.numBands) + # self.DCTMatrix = self.getDCTMatrix(self.numBands) + # self.filterIter = self.filterMatrix.__iter__() + # self.valid = True + # return True + # except : + # print "Invalid parameter setting encountered in MelScaling class." + # return False + # return True + + def getFilterCentres(self,inputSize,numBands): + '''Calculate Mel filter centres around FFT bins. + This function calculates two extra bands at the edges for + finding the starting and end point of the first and last + actual filters.''' + centresMel = numpy.array(xrange(numBands+2)) * (self.maxMel-self.minMel)/(numBands+1) + self.minMel + centresBin = numpy.floor(0.5 + 700.0*inputSize*(exp(centresMel*log(1+1000.0/700.0)/1000.0)-1)/self.NqHz) + return numpy.array(centresBin,int) + + def getFilterMatrix(self,inputSize,numBands): + '''Compose the Mel scaling matrix.''' + filterMatrix = numpy.zeros((numBands,inputSize)) + self.filterCentres = self.getFilterCentres(inputSize,numBands) + for i in xrange(numBands) : + start,centre,end = self.filterCentres[i:i+3] + self.setFilter(filterMatrix[i],start,centre,end) + return filterMatrix.transpose() + + def setFilter(self,filt,filterStart,filterCentre,filterEnd): + '''Calculate a single Mel filter.''' + k1 = numpy.float32(filterCentre-filterStart) + k2 = numpy.float32(filterEnd-filterCentre) + up = (numpy.array(xrange(filterStart,filterCentre))-filterStart)/k1 + dn = (filterEnd-numpy.array(xrange(filterCentre,filterEnd)))/k2 + filt[filterStart:filterCentre] = up + filt[filterCentre:filterEnd] = dn + + def warpSpectrum(self,magnitudeSpectrum): + '''Compute the Mel scaled spectrum.''' + return numpy.dot(magnitudeSpectrum,self.filterMatrix) + + def getDCTMatrix(self,size): + '''Calculate the square DCT transform matrix. Results are + equivalent to Matlab dctmtx(n) but with 64 bit precision.''' + DCTmx = numpy.array(xrange(size),numpy.float64).repeat(size).reshape(size,size) + DCTmxT = numpy.pi * (DCTmx.transpose()+0.5) / size + DCTmxT = (1.0/sqrt( size / 2.0)) * cos(DCTmx * DCTmxT) + DCTmxT[0] = DCTmxT[0] * (sqrt(2.0)/2.0) + return DCTmxT + + def dct(self,data_matrix): + '''Compute DCT of input matrix.''' + return numpy.dot(self.DCTMatrix,data_matrix) + + def getMFCCs(self,warpedSpectrum,cn=True): + '''Compute MFCC coefficients from Mel warped magnitude spectrum.''' + mfccs=self.dct(numpy.log(warpedSpectrum)) + if cn is False : mfccs[0] = 0.0 + return mfccs + + +class PyMFCC_time(melScaling): + + def __init__(self,inputSampleRate): + + # flags for setting some Vampy options + self.vampy_flags = vf_DEBUG | vf_ARRAY | vf_REALTIME + + self.m_inputSampleRate = int(inputSampleRate) + self.m_stepSize = 512 + self.m_blockSize = 2048 + self.m_channels = 1 + self.numBands = 40 + self.cnull = 1 + self.two_ch = False + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + + def initialise(self,channels,stepSize,blockSize): + self.m_channels = channels + self.m_stepSize = stepSize + self.m_blockSize = blockSize + self.window = numpy.hamming(blockSize) + melScaling.__init__(self,int(self.m_inputSampleRate),self.m_blockSize/2,self.numBands) + return True + + def getMaker(self): + return 'Vampy Test Plugins' + + def getCopyright(self): + return 'Plugin By George Fazekas' + + def getName(self): + return 'Vampy TimeDomain MFCC Plugin' + + def getIdentifier(self): + return 'vampy-mfcc-test-timedomain' + + def getDescription(self): + return 'A simple MFCC plugin. (TimeDomain)' + + def getMaxChannelCount(self): + return 2 + + def getInputDomain(self): + return TimeDomain + + def getPreferredBlockSize(self): + return 2048 + + def getPreferredStepSize(self): + return 512 + + def getOutputDescriptors(self): + + Generic = OutputDescriptor() + Generic.hasFixedBinCount=True + Generic.binCount=int(self.numBands)-self.cnull + Generic.hasKnownExtents=False + Generic.isQuantized=True + Generic.sampleType = OneSamplePerStep + + # note the inheritance of attributes (use is optional) + MFCC = OutputDescriptor(Generic) + MFCC.identifier = 'mfccs' + MFCC.name = 'MFCCs' + MFCC.description = 'MFCC Coefficients' + MFCC.binNames=map(lambda x: 'C '+str(x),range(self.cnull,int(self.numBands))) + MFCC.unit = None + if self.two_ch and self.m_channels == 2 : + MFCC.binCount = self.m_channels * (int(self.numBands)-self.cnull) + else : + MFCC.binCount = self.numBands-self.cnull + + warpedSpectrum = OutputDescriptor(Generic) + warpedSpectrum.identifier='warped-fft' + warpedSpectrum.name='Mel Scaled Spectrum' + warpedSpectrum.description='Mel Scaled Magnitide Spectrum' + warpedSpectrum.unit='Mel' + if self.two_ch and self.m_channels == 2 : + warpedSpectrum.binCount = self.m_channels * int(self.numBands) + else : + warpedSpectrum.binCount = self.numBands + + melFilter = OutputDescriptor(Generic) + melFilter.identifier = 'mel-filter-matrix' + melFilter.sampleType='FixedSampleRate' + melFilter.sampleRate=self.m_inputSampleRate/self.m_stepSize + melFilter.name='Mel Filter Matrix' + melFilter.description='Returns the created filter matrix in getRemainingFeatures.' + melFilter.unit = None + + return OutputList(MFCC,warpedSpectrum,melFilter) + + + def getParameterDescriptors(self): + + melbands = ParameterDescriptor() + melbands.identifier='melbands' + melbands.name='Number of bands (coefficients)' + melbands.description='Set the number of coefficients.' + melbands.unit = '' + melbands.minValue = 2 + melbands.maxValue = 128 + melbands.defaultValue = 40 + melbands.isQuantized = True + melbands.quantizeStep = 1 + + cnull = ParameterDescriptor() + cnull.identifier='cnull' + cnull.name='Return C0' + cnull.description='Select if the DC coefficient is required.' + cnull.unit = None + cnull.minValue = 0 + cnull.maxValue = 1 + cnull.defaultValue = 0 + cnull.isQuantized = True + cnull.quantizeStep = 1 + + two_ch = ParameterDescriptor(cnull) + two_ch.identifier='two_ch' + two_ch.name='Process channels separately' + two_ch.description='Process two channel files separately.' + two_ch.defaultValue = False + + minHz = ParameterDescriptor() + minHz.identifier='minHz' + minHz.name='minimum frequency' + minHz.description='Set the lower frequency bound.' + minHz.unit='Hz' + minHz.minValue = 0 + minHz.maxValue = 24000 + minHz.defaultValue = 0 + minHz.isQuantized = True + minHz.quantizeStep = 1.0 + + maxHz = ParameterDescriptor() + maxHz.identifier='maxHz' + maxHz.description='Set the upper frequency bound.' + maxHz.name='maximum frequency' + maxHz.unit='Hz' + maxHz.minValue = 100 + maxHz.maxValue = 24000 + maxHz.defaultValue = 11025 + maxHz.isQuantized = True + maxHz.quantizeStep = 100 + + return ParameterList(melbands,minHz,maxHz,cnull,two_ch) + + + def setParameter(self,paramid,newval): + self.valid = False + if paramid == 'minHz' : + if newval < self.maxHz and newval < self.NqHz : + self.minHz = float(newval) + print 'minHz: ', self.minHz + if paramid == 'maxHz' : + print 'trying to set maxHz to: ',newval + if newval < self.NqHz and newval > self.minHz+1000 : + self.maxHz = float(newval) + else : + self.maxHz = self.NqHz + print 'set to: ',self.maxHz + if paramid == 'cnull' : + self.cnull = int(not int(newval)) + if paramid == 'melbands' : + self.numBands = int(newval) + if paramid == 'two_ch' : + self.two_ch = bool(newval) + + return + + def getParameter(self,paramid): + if paramid == 'minHz' : + return float(self.minHz) + if paramid == 'maxHz' : + return float(self.maxHz) + if paramid == 'cnull' : + return float(not int(self.cnull)) + if paramid == 'melbands' : + return float(self.numBands) + if paramid == 'two_ch' : + return float(self.two_ch) + else: + return 0.0 + + # set numpy process using the 'use_numpy_interface' flag + def process(self,inputbuffers,timestamp): + + if self.m_channels == 2 and self.two_ch : + return self.process2ch(inputbuffers,timestamp) + + # calculate the filter and DCT matrices, check + # if they are computable given a set of parameters + # (we only do this once, when the process is called first) + if not self.update() : return None + + fftsize = self.m_blockSize + + if self.m_channels > 1 : + audioSamples = (inputbuffers[0]+inputbuffers[1])/2 + else : + audioSamples = inputbuffers[0] + + #complexSpectrum = frombuffer(membuffer[0],complex64,-1,8) + complexSpectrum = fft(self.window*audioSamples,fftsize) + magnitudeSpectrum = abs(complexSpectrum)[0:fftsize/2] / (fftsize/2) + + # do the computation + melSpectrum = self.warpSpectrum(magnitudeSpectrum) + melCepstrum = self.getMFCCs(melSpectrum,cn=True) + + # output feature set (the builtin dict type can also be used) + outputs = FeatureSet() + outputs[0] = Feature(melCepstrum[self.cnull:]) + outputs[1] = Feature(melSpectrum) + + return outputs + + + # process two channel files (stack the returned arrays) + def process2ch(self,inputbuffers,timestamp): + if not self.update() : return None + + fftsize = self.m_blockSize + + audioSamples0 = inputbuffers[0] + audioSamples1 = inputbuffers[1] + + complexSpectrum0 = fft(self.window*audioSamples0,fftsize) + complexSpectrum1 = fft(self.window*audioSamples1,fftsize) + + magnitudeSpectrum0 = abs(complexSpectrum0)[0:fftsize/2] / (fftsize/2) + magnitudeSpectrum1 = abs(complexSpectrum1)[0:fftsize/2] / (fftsize/2) + + # do the computation + melSpectrum0 = self.warpSpectrum(magnitudeSpectrum0) + melCepstrum0 = self.getMFCCs(melSpectrum0,cn=True) + melSpectrum1 = self.warpSpectrum(magnitudeSpectrum1) + melCepstrum1 = self.getMFCCs(melSpectrum1,cn=True) + + outputs = FeatureSet() + outputs[0] = Feature(hstack((melCepstrum1[self.cnull:],melCepstrum0[self.cnull:]))) + outputs[1] = Feature(hstack((melSpectrum1,melSpectrum0))) + + return outputs + + + def getRemainingFeatures(self): + if not self.update() : return [] + frameSampleStart = 0 + + output_featureSet = FeatureSet() + + # the filter is the third output (index starts from zero) + output_featureSet[2] = flist = FeatureList() + + while True: + f = Feature() + f.hasTimestamp = True + f.timestamp = frame2RealTime(frameSampleStart,self.m_inputSampleRate) + try : + f.values = self.filterIter.next() + except StopIteration : + break + flist.append(f) + frameSampleStart += self.m_stepSize + + return output_featureSet + \ No newline at end of file diff -r 000000000000 -r 27bab3a16c9a Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,44 @@ + +#CXXFLAGS := -D_DEBUG_VALUES -D_DEBUG -DHAVE_NUMPY -I../vamp-plugin-sdk -O2 -Wall -I/usr/include/python2.5 -I/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/site-packages/numpy/core/include/numpy/ +CXXFLAGS := -D_DEBUG -DHAVE_NUMPY -I../vamp-plugin-sdk -O2 -Wall -I/usr/include/python2.5 -I/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/site-packages/numpy/core/include/ +#CXXFLAGS := -DHAVE_NUMPY -I../vamp-plugin-sdk -O2 -Wall -I/usr/include/python2.5 -I/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/site-packages/numpy/core/include/numpy/ + +#without numpy headers available: +#CXXFLAGS := -I../vamp-plugin-sdk -O2 -Wall -I/usr/include/python2.5 #-I../host/pyRealTime.h +LDFLAGS := -L../vamp-plugin-sdk/vamp-sdk -lvamp-sdk -dynamiclib -lpython2.5 -lpthread + +default: vampy.dylib +all: vampy.dylib vampymod.so + +PyExtensionModule.a: PyExtensionModule.o PyRealTime.o PyFeature.o PyParameterDescriptor.o PyOutputDescriptor.o PyFeatureSet.o + libtool -static $^ -o $@ + +# The standard python extension is .so (even on the Mac) +vampymod.so: PyExtensionModule.o PyRealTime.o PyFeature.o PyParameterDescriptor.o PyOutputDescriptor.o PyFeatureSet.o + g++ -shared $^ -o $@ $(LDFLAGS) + +vampy.dylib: PyPlugin.o PyPlugScanner.o vampy-main.o Mutex.o PyTypeInterface.o PyExtensionModule.a PyExtensionManager.o + g++ -shared $^ -o $@ $(LDFLAGS) + +# Install plugin +# +LIBRARY_PREFIX :=/Library +INSTALL_DIR :=$(LIBRARY_PREFIX)/Audio/Plug-Ins/Vamp +PYEXAMPLE_DIR :='Example VamPy Plugins' +PLUGIN_NAME :=vampy +PLUGIN_EXT :=.dylib + +install: + mkdir -p $(INSTALL_DIR) + rm -f $(INSTALL_DIR)/$(PLUGIN_NAME)$(PLUGIN_EXT) + cp $(PLUGIN_NAME)$(PLUGIN_EXT) $(INSTALL_DIR)/$(PLUGIN_NAME)$(PLUGIN_EXT) + #cp $(PYEXAMPLE_DIR)/*.py $(INSTALL_DIR) + +installplug : install +cleanplug : clean + +clean: + rm *.o + rm *.a + rm *$(PLUGIN_EXT) + diff -r 000000000000 -r 27bab3a16c9a Makefile.cc-linux --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.cc-linux Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,38 @@ + +CXXFLAGS := -DHAVE_NUMPY -O2 -Wall -I/usr/include/python2.6 -I/usr/lib/python2.6/dist-packages/numpy/core/include/numpy -fPIC +LDFLAGS := -shared -Wl,-Bstatic -lvamp-sdk -Wl,-Bdynamic -lpython2.6 -lpthread -Wl,--version-script=vamp-plugin.map + +default: vampy.so +all: vampy.so vampymod.so + +PyExtensionModule.a: PyExtensionModule.o PyRealTime.o PyFeature.o PyParameterDescriptor.o PyOutputDescriptor.o PyFeatureSet.o + ar cr $@ $^ + +# The standard python extension is .so (even on the Mac) +vampymod.so: PyExtensionModule.o PyRealTime.o PyFeature.o PyParameterDescriptor.o PyOutputDescriptor.o PyFeatureSet.o + g++ $^ -o $@ $(LDFLAGS) + +vampy.so: PyPlugin.o PyPlugScanner.o vampy-main.o Mutex.o PyTypeInterface.o PyExtensionModule.a + g++ $^ -o $@ $(LDFLAGS) + +# Install plugin +# +LIBRARY_PREFIX :=/Library +INSTALL_DIR :=$(LIBRARY_PREFIX)/Audio/Plug-Ins/Vamp +PYEXAMPLE_DIR :='Example VamPy Plugins' +PLUGIN_NAME :=vampy +PLUGIN_EXT :=.dylib + +install: + mkdir -p $(INSTALL_DIR) + rm -f $(INSTALL_DIR)/$(PLUGIN_NAME)$(PLUGIN_EXT) + cp $(PLUGIN_NAME)$(PLUGIN_EXT) $(INSTALL_DIR)/$(PLUGIN_NAME)$(PLUGIN_EXT) + +installplug : install +cleanplug : clean + +clean: + rm *.o + rm *.a + rm *$(PLUGIN_EXT) + diff -r 000000000000 -r 27bab3a16c9a Makefile.cc-mingw32 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.cc-mingw32 Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,10 @@ + +CXX := i586-mingw32msvc-g++ +CXXFLAGS := -I../include -O2 -Wall -I../../Python26/include + +vampy.dll: PyPlugin.o PyPlugScanner.o pyvamp-main.o Mutex.o + i586-mingw32msvc-g++ -shared $^ -o $@ -L../lib -L../../Python26/libs -Wl,-Bstatic -lvamp-sdk -Wl,-Bdynamic -lpython26 -Wl,--version-script=vamp-plugin.map + +clean: + rm *.o + diff -r 000000000000 -r 27bab3a16c9a Makefile.cc-osol --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.cc-osol Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,39 @@ + +CXX := CC +CXXFLAGS := -I/usr/local/include -G -DHAVE_NUMPY -O2 +w -I/usr/include/python2.6 -I/usr/lib/python2.6/site-packages/numpy/core/include/numpy -KPIC +LDFLAGS := -L/usr/local/lib -G -Bstatic -lvamp-sdk -Bdynamic -lpython2.6 -lpthread -Qoption ld -Mvamp-plugin.map + +default: vampy.so +all: vampy.so vampymod.so + +PyExtensionModule.a: PyExtensionModule.o PyRealTime.o PyFeature.o PyParameterDescriptor.o PyOutputDescriptor.o PyFeatureSet.o + ar cr $@ $^ + +# The standard python extension is .so (even on the Mac) +vampymod.so: PyExtensionModule.o PyRealTime.o PyFeature.o PyParameterDescriptor.o PyOutputDescriptor.o PyFeatureSet.o + $(CXX) $^ -o $@ $(LDFLAGS) + +vampy.so: PyPlugin.o PyPlugScanner.o vampy-main.o Mutex.o PyTypeInterface.o PyExtensionModule.a + $(CXX) $^ -o $@ $(LDFLAGS) + +# Install plugin +# +LIBRARY_PREFIX :=/Library +INSTALL_DIR :=$(LIBRARY_PREFIX)/Audio/Plug-Ins/Vamp +PYEXAMPLE_DIR :='Example VamPy Plugins' +PLUGIN_NAME :=vampy +PLUGIN_EXT :=.so + +install: + mkdir -p $(INSTALL_DIR) + rm -f $(INSTALL_DIR)/$(PLUGIN_NAME)$(PLUGIN_EXT) + cp $(PLUGIN_NAME)$(PLUGIN_EXT) $(INSTALL_DIR)/$(PLUGIN_NAME)$(PLUGIN_EXT) + +installplug : install +cleanplug : clean + +clean: + rm -f *.o + rm -f *.a + rm -f *$(PLUGIN_EXT) + diff -r 000000000000 -r 27bab3a16c9a Mutex.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Mutex.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,211 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Basic cross-platform mutex abstraction class. + This file copyright 2007 Chris Cannam. +*/ + +#include "Mutex.h" +#include + +#ifndef _WIN32 +#include +#include +#endif + +using std::cerr; +using std::endl; +using std::string; + +#ifdef _WIN32 + +Mutex::Mutex() +#ifndef NO_THREAD_CHECKS + : + m_lockedBy(-1) +#endif +{ + m_mutex = CreateMutex(NULL, FALSE, NULL); +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)GetCurrentThreadId() << ": Initialised mutex " << &m_mutex << endl; +#endif +} + +Mutex::~Mutex() +{ +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)GetCurrentThreadId() << ": Destroying mutex " << &m_mutex << endl; +#endif + CloseHandle(m_mutex); +} + +void +Mutex::lock() +{ +#ifndef NO_THREAD_CHECKS + DWORD tid = GetCurrentThreadId(); + if (m_lockedBy == tid) { + cerr << "ERROR: Deadlock on mutex " << &m_mutex << endl; + } +#endif +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Want to lock mutex " << &m_mutex << endl; +#endif + WaitForSingleObject(m_mutex, INFINITE); +#ifndef NO_THREAD_CHECKS + m_lockedBy = tid; +#endif +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Locked mutex " << &m_mutex << endl; +#endif +} + +void +Mutex::unlock() +{ +#ifndef NO_THREAD_CHECKS + DWORD tid = GetCurrentThreadId(); + if (m_lockedBy != tid) { + cerr << "ERROR: Mutex " << &m_mutex << " not owned by unlocking thread" << endl; + return; + } +#endif +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Unlocking mutex " << &m_mutex << endl; +#endif +#ifndef NO_THREAD_CHECKS + m_lockedBy = -1; +#endif + ReleaseMutex(m_mutex); +} + +bool +Mutex::trylock() +{ +#ifndef NO_THREAD_CHECKS + DWORD tid = GetCurrentThreadId(); +#endif + DWORD result = WaitForSingleObject(m_mutex, 0); + if (result == WAIT_TIMEOUT || result == WAIT_FAILED) { +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Mutex " << &m_mutex << " unavailable" << endl; +#endif + return false; + } else { +#ifndef NO_THREAD_CHECKS + m_lockedBy = tid; +#endif +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Locked mutex " << &m_mutex << " (from trylock)" << endl; +#endif + return true; + } +} + +#else /* !_WIN32 */ + +Mutex::Mutex() +#ifndef NO_THREAD_CHECKS + : + m_lockedBy(0), + m_locked(false) +#endif +{ + pthread_mutex_init(&m_mutex, 0); +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)pthread_self() << ": Initialised mutex " << &m_mutex << endl; +#endif +} + +Mutex::~Mutex() +{ +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)pthread_self() << ": Destroying mutex " << &m_mutex << endl; +#endif + pthread_mutex_destroy(&m_mutex); +} + +void +Mutex::lock() +{ +#ifndef NO_THREAD_CHECKS + pthread_t tid = pthread_self(); + if (m_locked && m_lockedBy == tid) { + cerr << "ERROR: Deadlock on mutex " << &m_mutex << endl; + } +#endif +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Want to lock mutex " << &m_mutex << endl; +#endif + pthread_mutex_lock(&m_mutex); +#ifndef NO_THREAD_CHECKS + m_lockedBy = tid; + m_locked = true; +#endif +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Locked mutex " << &m_mutex << endl; +#endif +} + +void +Mutex::unlock() +{ +#ifndef NO_THREAD_CHECKS + pthread_t tid = pthread_self(); + if (!m_locked) { + cerr << "ERROR: Mutex " << &m_mutex << " not locked in unlock" << endl; + return; + } else if (m_lockedBy != tid) { + cerr << "ERROR: Mutex " << &m_mutex << " not owned by unlocking thread" << endl; + return; + } +#endif +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Unlocking mutex " << &m_mutex << endl; +#endif +#ifndef NO_THREAD_CHECKS + m_locked = false; +#endif + pthread_mutex_unlock(&m_mutex); +} + +bool +Mutex::trylock() +{ +#ifndef NO_THREAD_CHECKS + pthread_t tid = pthread_self(); +#endif + if (pthread_mutex_trylock(&m_mutex)) { +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Mutex " << &m_mutex << " unavailable" << endl; +#endif + return false; + } else { +#ifndef NO_THREAD_CHECKS + m_lockedBy = tid; + m_locked = true; +#endif +#ifdef DEBUG_MUTEX + cerr << "MUTEX DEBUG: " << (void *)tid << ": Locked mutex " << &m_mutex << " (from trylock)" << endl; +#endif + return true; + } +} + +#endif + +MutexLocker::MutexLocker(Mutex *mutex) : + m_mutex(mutex) +{ + if (m_mutex) { + m_mutex->lock(); + } +} + +MutexLocker::~MutexLocker() +{ + if (m_mutex) { + m_mutex->unlock(); + } +} + diff -r 000000000000 -r 27bab3a16c9a Mutex.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Mutex.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Basic cross-platform mutex abstraction class. + This file copyright 2007 Chris Cannam. +*/ + +#ifndef _MUTEX_H_ +#define _MUTEX_H_ + +#ifdef _WIN32 +#include +#else +#include +#endif + +class Mutex +{ +public: + Mutex(); + ~Mutex(); + + void lock(); + void unlock(); + bool trylock(); + +private: +#ifdef _WIN32 + HANDLE m_mutex; +#ifndef NO_THREAD_CHECKS + DWORD m_lockedBy; +#endif +#else + pthread_mutex_t m_mutex; +#ifndef NO_THREAD_CHECKS + pthread_t m_lockedBy; + bool m_locked; +#endif +#endif +}; + +class MutexLocker +{ +public: + MutexLocker(Mutex *); + ~MutexLocker(); + +private: + Mutex *m_mutex; +}; + +#endif + diff -r 000000000000 -r 27bab3a16c9a PyExtensionManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyExtensionManager.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,248 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include +#include "vamp/vamp.h" +#include "PyExtensionModule.h" +#include "PyExtensionManager.h" +#include + +using std::cerr; +using std::endl; +using std::string; +using std::vector; +using std::find; + +//static +char* PyExtensionManager::m_exposedNames[] = { + "ParameterDescriptor", + "OutputDescriptor", + "ParameterList", + "OutputList", + "FeatureList", + "FeatureSet", + "Feature", + "RealTime", + "frame2RealTime", +/* //using builtin objects: + "OneSamplePerStep", + "FixedSampleRate", + "VariableSampleRate", + "TimeDomain", + "FrequencyDomain", +*/ + NULL +}; + +PyExtensionManager::PyExtensionManager() +{ +#ifdef _DEBUG + cerr << "Creating extension manager." << endl; +#endif +} + +bool +PyExtensionManager::initExtension() +{ + cerr << "Initialising extension module." << endl; + + /// call the module initialiser first + initvampy(); + + /// these references are all borrowed + m_pyGlobalNamespace = PyImport_GetModuleDict(); + if (!m_pyGlobalNamespace) + {cerr << "Vampy::PyExtensionManager::initExtension: GlobalNamespace failed." << endl; return false;} + PyObject *pyVampyModule = PyDict_GetItemString(m_pyGlobalNamespace,"vampy"); + if (!pyVampyModule) + {cerr << "Vampy::PyExtensionManager::initExtension: VampyModule failed." << endl; return false;} + m_pyVampyNamespace = PyModule_GetDict(pyVampyModule); + if (!m_pyVampyNamespace) + {cerr << "Vampy::PyExtensionManager::initExtension: VampyNamespace failed." << endl; return false;} + + /// initialise local namespaces + updateAllLocals(); +#ifdef _DEBUG + cerr << "Vampy: Extension namespaces updated." << endl; +#endif + return true; +} + + +PyExtensionManager::~PyExtensionManager() +{ +#ifdef _DEBUG + cerr << "Cleaning locals..." << endl; +#endif + + cleanAllLocals(); + +#ifdef _DEBUG + cerr << "Cleaning module..." << endl; +#endif + + if (!cleanModule()) + cerr << "Vampy::~PyExtensionManager: failed to clean extension module." << endl; + cerr << "Vampy::~PyExtensionManager: Extension module cleaned." << endl; +} + + + +void +PyExtensionManager::setPlugModuleNames(vector pyPlugs) +{ + for (size_t i = 0; i < pyPlugs.size(); ++i) { + string modName = pyPlugs[i]; + string tmp = modName.substr(modName.rfind(':')+1,modName.size()-1); + m_plugModuleNames.push_back(tmp); + +#ifdef _DEBUG_VALUES + cerr << "Inserted module name: " << tmp << endl; +#endif + + } +} + +void +PyExtensionManager::deleteModuleName(string plugKey) +{ + string name = plugKey.substr(plugKey.rfind(':')+1,plugKey.size()-1); + vector::iterator it = + find (m_plugModuleNames.begin(), m_plugModuleNames.end(), name); + if (it != m_plugModuleNames.end()) m_plugModuleNames.erase(it); +#ifdef _DEBUG_VALUES + cerr << "PyExtensionManager::deleteModuleName: Deleted module name: " << name << endl; +#endif +} + + +void +PyExtensionManager::cleanAllLocals() const +{ + for (size_t i = 0; i < m_plugModuleNames.size(); ++i) { + cleanLocalNamespace(m_plugModuleNames[i].c_str()); + } +} + +void +PyExtensionManager::updateAllLocals() const +{ + for (size_t i = 0; i < m_plugModuleNames.size(); ++i) { + updateLocalNamespace(m_plugModuleNames[i].c_str()); + } +} + +void +PyExtensionManager::cleanLocalNamespace(const char* plugModuleName) const +{ + + /// these references are all borrowed + PyObject *pyPlugModule = PyDict_GetItemString(m_pyGlobalNamespace,plugModuleName); + if (!pyPlugModule) return; + PyObject *pyPlugDict = PyModule_GetDict(pyPlugModule); + if (!pyPlugDict) return; + + int i = 0; + while (PyExtensionManager::m_exposedNames[i]) { + char* name = PyExtensionManager::m_exposedNames[i]; + i++; + PyObject *key = PyString_FromString(name); + if (!key) break; + if (PyDict_Contains(pyPlugDict,key)) { + if (PyDict_SetItem(pyPlugDict,key,Py_None) != 0) + cerr << "Vampy::PyExtensionManager::cleanLocalNamespace: Failed: " + << name << " of "<< plugModuleName << endl; +#ifdef _DEBUG_VALUES + else cerr << "Cleaned local name: " << name << endl; +#endif + } + Py_DECREF(key); + } +} + +void +PyExtensionManager::updateLocalNamespace(const char* plugModuleName) const +{ + /// this allows the use of common syntax like: + /// from vampy import * + /// even after several unload/reload cycles + + /// these references are all borrowed + PyObject *pyPlugModule = PyDict_GetItemString(m_pyGlobalNamespace,plugModuleName); + if (!pyPlugModule) return; + PyObject *pyPlugDict = PyModule_GetDict(pyPlugModule); + if (!pyPlugDict) return; + + int i = 0; + while (PyExtensionManager::m_exposedNames[i]) { + const char* name = PyExtensionManager::m_exposedNames[i]; + i++; + PyObject *key = PyString_FromString(name); + if (!key) break; + if (PyDict_Contains(pyPlugDict,key)) { + PyObject* item = PyDict_GetItem(m_pyVampyNamespace,key); + if (PyDict_SetItem(pyPlugDict,key,item) != 0) + cerr << "Vampy::PyExtensionManager::updateLocalNamespace: Failed: " + << name << " of "<< plugModuleName << endl; +#ifdef _DEBUG_VALUES + else cerr << "Updated local name: " << name << endl; +#endif + } + Py_DECREF(key); + } +} + + +bool +PyExtensionManager::cleanModule(void) const +{ + + PyObject *m = PyImport_AddModule("vampy"); + if (!m) { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + cerr << "Vampy::PyExtensionManager::cleanModule: PyImport_AddModule returned NULL!" << endl; + return false; + } else { + PyObject *dict = PyModule_GetDict(m); +#ifdef _DEBUG + Py_ssize_t ln = PyDict_Size(dict); + cerr << "Vampy::PyExtensionManager::cleanModule: Size of module dict = " << (int) ln << endl; +#endif + /// Clean the module dictionary. + // printDict(dict); + PyDict_Clear(dict); + if (PyErr_Occurred()) + { PyErr_Print(); PyErr_Clear(); return false; } + PyObject *name = PyString_FromString("vampy"); + if (name) PyDict_SetItemString(dict,"__name__",name); + Py_XDECREF(name); +#ifdef _DEBUG + ln = PyDict_Size(dict); + cerr << "Vampy::PyExtensionManager::cleanModule: Size of module dict (cleaned) = " << (int) ln << endl; +#endif + return true; + } +} + +void +PyExtensionManager::printDict(PyObject* inDict) const +{ + Py_ssize_t pyPos = 0; + PyObject *pyKey, *pyDictValue; + cerr << endl << endl << "Module dictionary contents: " << endl; + while (PyDict_Next(inDict, &pyPos, &pyKey, &pyDictValue)) + { + char *key = PyString_AS_STRING(pyKey); + char *val = PyString_AS_STRING(PyObject_Str(pyDictValue)); + cerr << "key: [ '" << key << "' ] value: " << val << endl; + } +} + diff -r 000000000000 -r 27bab3a16c9a PyExtensionManager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyExtensionManager.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,86 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +/* +PyExtensionManager: This class is responsible for initialisation +and cleanup of the extension module, as well as the loaded plugin +module namespaces. + +NOTES: Why do we need to clean up the module? + +The module exposed by Vampy to the embedded interpreter contains +callback functions. These functions are accessed via function +pointers stored in the extension module's namespace dictionary. + +Unfortunately, when the shared library is unloaded and reloaded +during a host session, these addresses might change. +Therefore, we reinitialise the module dict before each use. +However, this will cause garbage collection errors or segmentation +faults, when elements of the dict of the previous session are +attempted to free. Therefore, we clear the dictinary describing +the module namespace and replace all fuction pointers with Py_None +objects in individual plugin module namespaces. The reference +count on these can be safely decremented next time vampy is loaded +and the namespaces are reinitialised. + +Why doesn't the GC clean this up correctly? + +In a normal Python session the GC would deallocate the module +dict at the end. In embedded python, although the GC appears +to be called when the shared lib is unloaded, the interpreter +is reused. Since there is no C/API call to unload modules, +and at the time of unloading vampy the wrapped function pointers +are still valid, the GC doesn't collect them, nor are they freed +by the interpreter. When vampy is reloaded however, the module +dict will contain invalid addresses. The above procedure solves +this problem. + + +*/ + + +#ifndef _PYEXTENSIONMANAGER_H_ +#define _PYEXTENSIONMANAGER_H_ + +using std::cerr; +using std::endl; +using std::string; +using std::vector; + +class PyExtensionManager +{ +public: + PyExtensionManager(); + ~PyExtensionManager(); + bool initExtension(); + void setPlugModuleNames(vector pyPlugs); + void deleteModuleName(string plugKey); + +private: + static char* m_exposedNames[]; + + vector m_plugModuleNames; + PyObject* m_pyGlobalNamespace; + PyObject* m_pyVampyNamespace; + + void cleanAllLocals() const; + void cleanLocalNamespace(const char* plugModuleName) const; + void updateAllLocals() const; + void updateLocalNamespace(const char* plugModuleName) const; + + void printDict(PyObject* inDict) const; + bool cleanModule() const; + +}; + +#endif + + diff -r 000000000000 -r 27bab3a16c9a PyExtensionModule.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyExtensionModule.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,265 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include +#include "PyExtensionModule.h" +#include "PyRealTime.h" +#include "PyFeature.h" +#include "PyFeatureSet.h" +#include "PyParameterDescriptor.h" +#include "PyOutputDescriptor.h" +#include "vamp/vamp.h" +#include "vamp-sdk/Plugin.h" + +using namespace std; +using namespace Vamp; +using Vamp::Plugin; +using Vamp::RealTime; + +/* Functions Exposed by Vampy */ + +/* Creating PyRealTime Objects from frame count */ + +/* New RealTime object from Frame (with given samplerate) */ +static PyObject * +RealTime_frame2RealTime(PyObject *ignored, PyObject *args) +{ + long frame; + unsigned int sampleRate; + + if (!(args && PyTuple_GET_SIZE(args) == 2)) { + PyErr_SetString(PyExc_ValueError,"frame2RealTime requires two arguments: frame and sample rate."); + return NULL; + } + + PyObject* pyFrame = PyTuple_GET_ITEM(args,0); + PyObject* pySampleRate = PyTuple_GET_ITEM(args,1); + + /// frame + if (PyInt_Check(pyFrame)) frame = PyInt_AS_LONG(pyFrame); + else if (PyLong_Check(pyFrame)) frame = PyLong_AsLong(pyFrame); + else { + PyErr_SetString(PyExc_ValueError,"frame2RealTime 'frame' argument must be long integer."); + return NULL; + } + + /// sample rate + if (PyInt_Check(pySampleRate)) + sampleRate = _long2uint(PyInt_AS_LONG(pySampleRate)); + else if (PyFloat_Check(pySampleRate)) + sampleRate = _dbl2uint(PyFloat_AS_DOUBLE(pySampleRate)); + else if (PyLong_Check(pySampleRate)) + sampleRate = _long2uint(PyLong_AsLong(pySampleRate)); + else { + PyErr_SetString(PyExc_ValueError,"frame2RealTime 'sample rate' argument must be int, long or float."); + return NULL; + } + + if (!sampleRate) { + PyErr_SetString(PyExc_ValueError,"frame2RealTime 'sample rate' argument overflow error. Argument must be 0 < arg < UINT_MAX."); + cerr << "Value: " << sampleRate << endl; + return NULL; + } + + // simpler but slower: + // if (!PyArg_ParseTuple(args, "lI:realtime.frame2RealTime ", + // &frame,&sampleRate)) + // return NULL; + + RealTimeObject *self; + self = PyObject_New(RealTimeObject, &RealTime_Type); + if (self == NULL) return NULL; + + self->rt = new RealTime( + RealTime::frame2RealTime(frame,sampleRate)); + + return (PyObject *) self; +} + +/* + +Note: these functions are not very interesting on their own, but +they can be used to make the semantics of the plugin clearer. +They return ordinary Python list objects. All type checking +is performed in the type interface. + +*/ + +/* New PyOutputList Objects */ +static PyObject * +OutputList_new(PyObject *ignored, PyObject *args) +{ + if (args and PyTuple_Check(args)) + return PySequence_List(args); + else return (PyObject *) PyList_New(0); +} + + +/* New PyParameterList Objects */ +static PyObject * +ParameterList_new(PyObject *ignored, PyObject *args) +{ + if (args and PyTuple_Check(args)) + return PySequence_List(args); + else return (PyObject *) PyList_New(0); +} + +/* New PyFeatureList Objects */ +static PyObject * +FeatureList_new(PyObject *ignored, PyObject *args) +{ + if (args and PyTuple_Check(args)) + return PySequence_List(args); + else return (PyObject *) PyList_New(0); +} + + +/* Declare the methods exposed by the vampy module */ + + +PyMethodDef VampyMethods[] = { +/*NOTE: This is conventionally static, but limiting the scope + here will cause seg fault if the declared functions are + called back from a Python function wrapped in a C++ class.*/ + + {"frame2RealTime", (PyCFunction)RealTime_frame2RealTime, METH_VARARGS, + PyDoc_STR("frame2RealTime((int64)frame, (uint32)sampleRate ) -> returns new RealTime object from frame.")}, + + {"OutputList", OutputList_new, METH_VARARGS, + PyDoc_STR("OutputList() -> returns new OutputList object")}, + + {"ParameterList", ParameterList_new, METH_VARARGS, + PyDoc_STR("ParameterList() -> returns new ParameterList object")}, + + {"FeatureList", FeatureList_new, METH_VARARGS, + PyDoc_STR("FeatureList() -> returns new FeatureList object")}, + + {NULL, NULL, 0, NULL} +}; + +/* Module Documentation */ +// PyDoc_STRVAR(vampy_doc,"This module exposes Vamp plugin data type wrappers."); + +static int +setint(PyObject *d, char *name, int value) +{ + PyObject *v; + int err; + v = PyInt_FromLong((long)value); + err = PyDict_SetItemString(d, name, v); + Py_XDECREF(v); + return err; +} + +static int +setdbl(PyObject *d, char *name, double value) +{ + PyObject *v; + int err; + v = PyFloat_FromDouble(value); + err = PyDict_SetItemString(d, name, v); + Py_XDECREF(v); + return err; +} + +static int +setstr(PyObject *d, char *name, char *value) +{ + PyObject *v; + int err; + v = PyString_FromString(value); + err = PyDict_SetItemString(d, name, v); + Py_XDECREF(v); + return err; +} + + +PyMODINIT_FUNC +initvampy(void) +{ + PyObject *module, *mdict; + + /* if (PyType_Ready(&Feature_Type) < 0) return; + Note: Why do we get a segfault if this is initialised here? + PyType_Ready adds these object to the GC. + This is OK for an extension module, but it is a mistake here, + because the adresses become invalid when the shared library + is unloaded. When the GC tries to visit a these objects, + it will fail.*/ + + RealTime_Type.ob_type = &PyType_Type; + Feature_Type.ob_type = &PyType_Type; + OutputDescriptor_Type.ob_type = &PyType_Type; + ParameterDescriptor_Type.ob_type = &PyType_Type; + initFeatureSetType(); // this is derived from the builtin dict + + PyImport_AddModule("vampy"); + module = Py_InitModule("vampy", VampyMethods); + if (!module) goto failure; + mdict = PyModule_GetDict(module); + if (!mdict) goto failure; + + /// vampy plugin wrapper flags + if (setint(mdict, "vf_NULL", vf_NULL) < 0) goto failure; + if (setint(mdict, "vf_DEBUG", vf_DEBUG) < 0) goto failure; + if (setint(mdict, "vf_STRICT", vf_STRICT) < 0) goto failure; + if (setint(mdict, "vf_QUIT", vf_QUIT) < 0) goto failure; + if (setint(mdict, "vf_REALTIME", vf_REALTIME) < 0) goto failure; + if (setint(mdict, "vf_BUFFER", vf_BUFFER) < 0) goto failure; + if (setint(mdict, "vf_ARRAY", vf_ARRAY) < 0) goto failure; + if (setint(mdict, "vf_DEFAULT_V2", vf_DEFAULT_V2) < 0) goto failure; + + /// Vamp enum types simulation + if (setint(mdict, "OneSamplePerStep", Vamp::Plugin::OutputDescriptor::OneSamplePerStep) < 0) goto failure; + if (setint(mdict, "FixedSampleRate", Vamp::Plugin::OutputDescriptor::FixedSampleRate) < 0) goto failure; + if (setint(mdict, "VariableSampleRate", Vamp::Plugin::OutputDescriptor::VariableSampleRate) < 0) goto failure; + if (setint(mdict, "TimeDomain", Vamp::Plugin::TimeDomain) < 0) goto failure; + if (setint(mdict, "FrequencyDomain", Vamp::Plugin::FrequencyDomain) < 0) goto failure; + + /// module attributes + if (setstr(mdict, "__name__", "vampy") < 0) goto failure; + if (setdbl(mdict, "__version__", 2.0) < 0) goto failure; + if (setdbl(mdict, "__VAMP_API_VERSION__", (double) VAMP_API_VERSION) < 0) goto failure; +#ifdef HAVE_NUMPY + if (setint(mdict, "__numpy__", 1) < 0) goto failure; +#else + if (setint(mdict, "__numpy__", 0) < 0) goto failure; +#endif + + /// type objects + Py_INCREF(&RealTime_Type); + if (PyModule_AddObject(module,"RealTime",(PyObject*)&RealTime_Type) !=0) goto failure; + + Py_INCREF((PyObject*)&Feature_Type); + if (PyModule_AddObject(module,"Feature",(PyObject*)&Feature_Type) !=0) goto failure; + + Py_INCREF((PyObject*)&FeatureSet_Type); + if (PyModule_AddObject(module,"FeatureSet",(PyObject*)&FeatureSet_Type) !=0) goto failure; + + Py_INCREF((PyObject*)&OutputDescriptor_Type); + if (PyModule_AddObject(module,"OutputDescriptor",(PyObject*)&OutputDescriptor_Type) !=0) goto failure; + + Py_INCREF((PyObject*)&ParameterDescriptor_Type); + if (PyModule_AddObject(module,"ParameterDescriptor",(PyObject*)&ParameterDescriptor_Type) !=0) goto failure; + +#ifdef _DEBUG + cerr << "Vampy: extension module initialised." << endl; +#endif + + return; + +failure : + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + cerr << "Vampy::PyExtensionModule::initvampy: Failed to initialise extension module." << endl; + return; +} + + diff -r 000000000000 -r 27bab3a16c9a PyExtensionModule.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyExtensionModule.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,53 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#ifndef _PYEXTENSIONMODULE_H_ +#define _PYEXTENSIONMODULE_H_ + +#include +#include +#include "PyRealTime.h" +#include "PyFeature.h" +#include "PyFeatureSet.h" +#include "PyParameterDescriptor.h" +#include "PyOutputDescriptor.h" + +#ifndef UINT_MAX +#define UINT_MAX ((unsigned int) -1) +#endif +#define UINT_MAXD ((double) UINT_MAX) +/* long error() { std::cerr << "type error" << std::endl; return 0; } */ +#define _dbl2uint(x) ((x) < 0 || (x) > UINT_MAXD ? 0 : (unsigned int)(x)+0.5) +#define _long2uint(x) ((x) < 0 || (x) > UINT_MAXD ? 0 : (unsigned int)(x)) + +using std::string; +using std::vector; + +enum eVampyFlags { + vf_NULL = 0, + vf_DEBUG = 1, + vf_STRICT = 2, + vf_QUIT = 4, + vf_REALTIME = 8, + vf_BUFFER = 16, + vf_ARRAY = 32, + vf_DEFAULT_V2 = (32 | 8) +}; + +#define PyDescriptor_Check(v) ((v)->ob_type == &Feature_Type) || ((v)->ob_type == &OutputDescriptor_Type) || ((v)->ob_type == &ParameterDescriptor_Type) + +#ifndef PyMODINIT_FUNC +#define PyMODINIT_FUNC void +#endif + +PyMODINIT_FUNC initvampy(); + +#endif diff -r 000000000000 -r 27bab3a16c9a PyFeature.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyFeature.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,235 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include +#include "PyExtensionModule.h" +#include "PyFeature.h" +#include "vamp-sdk/Plugin.h" +#include + +using namespace std; +using namespace Vamp; +using Vamp::Plugin; + +/* CONSTRUCTOR: New Feature object */ +static PyObject * +Feature_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + // FeatureObject *self = PyObject_New(FeatureObject, &Feature_Type); + FeatureObject *self = (FeatureObject*)type->tp_alloc(type, 0); + if (self == NULL) return NULL; + self->dict = PyDict_New(); + if (self->dict == NULL) return NULL; + + /// 4 args max.: {values|self_copy},timestamp,duration,label + if(args && PyTuple_GET_SIZE(args)>0) { + int s = PyTuple_GET_SIZE(args); + PyObject* arg0 = PyTuple_GET_ITEM(args,0); + if (s == 1 && PyFeature_CheckExact(arg0)) + PyDict_Merge(self->dict,PyFeature_AS_DICT(arg0),0); + else + PyDict_SetItemString(self->dict, "values", arg0); + if (s>1) { + PyDict_SetItemString(self->dict, "timestamp", PyTuple_GET_ITEM(args,1)); + PyDict_SetItemString(self->dict, "hasTimestamp", Py_True); + } + if (s>2) { + PyDict_SetItemString(self->dict, "duration", PyTuple_GET_ITEM(args,2)); + PyDict_SetItemString(self->dict, "hasDuration", Py_True); + } + if (s>3) { + PyDict_SetItemString(self->dict, "label", PyTuple_GET_ITEM(args,3)); + } + } + + /// accept keyword arguments: + /// e.g. Feature(values = theOutputArray) + if (!kw || !PyDict_Size(kw)) return (PyObject *) self; + PyDict_Merge(self->dict,kw,0); + + static char *kwlist[] = {"timestamp", "hasTimestamp", "duration", "hasDuration", 0}; + + int i = 0; + while (kwlist[i]) { + char* name = kwlist[i]; + char* attr = kwlist[++i]; + i++; + PyObject *key = PyString_FromString(name); + if (!key) break; + if (PyDict_Contains(kw,key)) { + if (PyDict_SetItem(self->dict,PyString_FromString(attr),Py_True) != 0) + PyErr_SetString(PyExc_TypeError, + "Error: in keyword arguments of vampy.Feature()."); + } + Py_DECREF(key); + } + + return (PyObject *) self; +} + +/* DESTRUCTOR: delete type object */ +static void +FeatureObject_dealloc(FeatureObject *self) +{ + Py_XDECREF(self->dict); + self->ob_type->tp_free((PyObject*)self); +} + +/* Feature Object's Methods */ +//Feature objects have no callable methods + +/* PyFeature methods implementing protocols */ +// these functions are called by the interpreter automatically + +/* Set attributes */ +static int +Feature_setattr(FeatureObject *self, char *name, PyObject *v) +{ + if (v == NULL) + { + int rv = PyDict_DelItemString(self->dict, name); + if (rv < 0) + PyErr_SetString(PyExc_AttributeError,"non-existing Feature attribute"); + return rv; + } + else return PyDict_SetItemString(self->dict, name, v); +} + + +/* Get attributes */ +static PyObject * +Feature_getattr(FeatureObject *self, char *name) +{ + if (self->dict != NULL) { + PyObject *v = PyDict_GetItemString(self->dict, name); + if (v != NULL) + { + Py_INCREF(v); + return v; + } + } + return NULL; +} + +/* The problem with this is that we'd need to implement two-way +conversions which is really unnecesary: The case for using +a Vamp::Feature in Python for anything else than returning +values is rather obscure. It's not really worth it. */ + +/* Set Attribute: Using wrapped Vamp::Feature +static int +Feature_setattr(FeatureObject *self, char *name, PyObject *value) +{ + std::string key = std::string(name); + if (self->ti.SetValue(*(self->feature),key,value)) return 0; + else return -1; +}*/ + +/* Get Attribute: Using wrapped Vamp::Feature +static PyObject * +Feature_getattr(FeatureObject *self, char *name) +{ + std::string key = std::string(name); + PyObject* pyValue; + if (self->ti.GetValue(*(self->feature),key,pyValue)) + return pyValue; + else return NULL; +}*/ + +/* +static int +Feature_init(FeatureObject *self, PyObject *args, PyObject *kwds) +{ + cerr << "FeatureObject Init called" << endl; + return 0; +} + +PyObject* +Feature_test(PyObject *self, PyObject *args, PyObject *kwds) +{ + cerr << "FeatureObject TEST called" << endl; + return self; +} +*/ + +/* String representation */ +static PyObject * +Feature_repr(PyObject *self) +{ + FeatureObject* v = (FeatureObject*)self; + if (v->dict) return PyDict_Type.tp_repr((PyObject *)v->dict); + else return PyString_FromString("Feature()"); +} + +#define Feature_alloc PyType_GenericAlloc +#define Feature_free PyObject_Del + + +/* FEATURE TYPE OBJECT */ + +PyTypeObject Feature_Type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "vampy.Feature", /*tp_name*/ + sizeof(FeatureObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)FeatureObject_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc)Feature_getattr, /*tp_getattr*/ + (setattrfunc)Feature_setattr, /*tp_setattr*/ + 0, /*tp_compare*/ + Feature_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0,//Feature_test, /*tp_call*/ // call on an instance + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ //TypeObject Methods + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0,//(initproc)Feature_init, /*tp_init*/ + Feature_alloc, /*tp_alloc*/ + Feature_new, /*tp_new*/ + Feature_free, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* PyRealTime C++ API */ + +/*Feature* from PyFeature +const Vamp::Plugin::Feature* +PyFeature_AsFeature (PyObject *self) { + + FeatureObject *s = (FeatureObject*) self; + + if (!PyFeature_Check(s)) { + PyErr_SetString(PyExc_TypeError, "Feature Object Expected."); + cerr << "in call PyFeature_AsPointer(): Feature Object Expected. " << endl; + return NULL; } + return s->feature; +};*/ diff -r 000000000000 -r 27bab3a16c9a PyFeature.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyFeature.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,66 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +/* +NOTES: There are two ways to implement the Vamp::Feature wrapper. +1) We could keep a Vamp::Feature in the object and +convert the values on the fly as they are inserted. +However, this requires a way to convert back to Python for +this object to be fully usable in python code. These conversions +are otherwise unnecessary. + +2) Keep the python attribute objects in a dict as it is normally +done in python classes, and convert when the object is returned. +This way the object is usable by the interpreter until it is returned +to the C++ plugin wrapper. +This is different form the Vampy:PyRealTime implementation where the +two-way conversion makes more sense (in fact required). Note: For +a host implementation option 1) will be required. + +*/ + +#ifndef _PYFEATURE_H_ +#define _PYFEATURE_H_ + +#include "vamp-sdk/Plugin.h" +// #include "PyTypeInterface.h" + + +typedef struct { + PyObject_HEAD + PyObject *dict; + // Vamp::Plugin::Feature *feature; + /// pointer to type interface required: PyTypeInterface ti; +} FeatureObject; + +PyAPI_DATA(PyTypeObject) Feature_Type; + +#define PyFeature_CheckExact(v) ((v)->ob_type == &Feature_Type) +#define PyFeature_Check(v) PyObject_TypeCheck(v, &Feature_Type) + +///fast macro version as per API convention +#define PyFeature_AS_DICT(v) ((const FeatureObject* const) (v))->dict +// #define PyFeature_AS_FEATURE(v) ((const FeatureObject* const) (v))->feature + + +/* PyFeature C++ API */ + +/* Not required here: + we will never have to pass a feature back from the wrapper */ +// PyAPI_FUNC(PyObject *) +// PyFeature_FromFeature(Vamp::Plugin::Feature&); + +// PyAPI_FUNC(const Vamp::Plugin::Feature*) +// PyFeature_AsFeature (PyObject *self); + + + +#endif diff -r 000000000000 -r 27bab3a16c9a PyFeatureSet.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyFeatureSet.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,63 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include +#include "PyFeatureSet.h" +#include "vamp-sdk/Plugin.h" + +using namespace std; + +static int +FeatureSet_init(FeatureSetObject *self, PyObject *args, PyObject *kwds) +{ + if (PyDict_Type.tp_init((PyObject *)self, args, kwds) < 0) + return -1; + return 0; +} + +static int +FeatureSetObject_ass_sub(FeatureSetObject *mp, PyObject *v, PyObject *w) +{ + if (!PyInt_CheckExact(v)) { + PyErr_SetString(PyExc_ValueError, + "Output index must be positive integer."); + return 0; + } + if (w == NULL) + return PyDict_DelItem((PyObject *)mp, v); + else + return PyDict_SetItem((PyObject *)mp, v, w); +} + +#define FeatureSet_alloc PyType_GenericAlloc +#define FeatureSet_free PyObject_Del +//#define FeatureSet_as_mapping PyDict_Type.tp_as_mapping + +static PyMappingMethods FeatureSet_as_mapping = *(PyDict_Type.tp_as_mapping); + +PyTypeObject FeatureSet_Type = PyDict_Type; + +void +initFeatureSetType(void) +{ + /*This type is derived from PyDict. We just override some slots here.*/ + /*The typical use case is index based assignment as opposed to object memeber access.*/ + FeatureSet_Type.ob_type = &PyType_Type; + FeatureSet_Type.tp_base = &PyDict_Type; + FeatureSet_Type.tp_bases = PyTuple_Pack(1, FeatureSet_Type.tp_base); + FeatureSet_Type.tp_name = "vampy.FeatureSet"; + // FeatureSet_Type.tp_new = FeatureSet_new; + FeatureSet_Type.tp_init = (initproc)FeatureSet_init; + FeatureSet_Type.tp_basicsize = sizeof(FeatureSetObject); + FeatureSet_as_mapping.mp_ass_subscript = (objobjargproc)FeatureSetObject_ass_sub; + FeatureSet_Type.tp_as_mapping = &FeatureSet_as_mapping; +} + diff -r 000000000000 -r 27bab3a16c9a PyFeatureSet.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyFeatureSet.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,28 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#ifndef _PYFEATURESET_H_ +#define _PYFEATURESET_H_ + +#include + +typedef struct { + PyDictObject dict; +} FeatureSetObject; + +PyAPI_DATA(PyTypeObject) FeatureSet_Type; + +#define PyFeatureSet_CheckExact(v) ((v)->ob_type == &FeatureSet_Type) +#define PyFeatureSet_Check(v) PyObject_TypeCheck(v, &FeatureSet_Type) + +void initFeatureSetType(void); + +#endif diff -r 000000000000 -r 27bab3a16c9a PyOutputDescriptor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyOutputDescriptor.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,156 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include +#include "PyOutputDescriptor.h" +#include "vamp-sdk/Plugin.h" +#include +#include "PyTypeInterface.h" + +using namespace std; +using namespace Vamp; +using Vamp::Plugin; + +/* OutputDescriptor Object's Methods */ +//these objects have no callable methods + +/* PyOutputDescriptor methods implementing protocols */ +// these functions are called by the interpreter automatically + +/* New OutputDescriptor object */ +static PyObject * +OutputDescriptor_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + OutputDescriptorObject *self = + (OutputDescriptorObject*)type->tp_alloc(type, 0); + + if (self == NULL) return NULL; + self->dict = PyDict_New(); + if (self->dict == NULL) return NULL; + + /// allow copying objects + if (args and PyTuple_Size(args) == 1) { + PyObject* arg = PyTuple_GET_ITEM(args,0); + if (PyOutputDescriptor_CheckExact(arg)) + PyDict_Merge(self->dict,PyOutputDescriptor_AS_DICT(arg),0); + else if (PyDict_CheckExact(arg)) + PyDict_Merge(self->dict,arg,0); + else { + PyErr_SetString(PyExc_TypeError, + "OutputDescriptor takes zero or one PyOutputDescriptor or dictionary arguments."); + return NULL; + } + } + return (PyObject *) self; +} + + +/* DESTRUCTOR: delete type object */ +static void +OutputDescriptorObject_dealloc(OutputDescriptorObject *self) +{ + Py_XDECREF(self->dict); + PyObject_Del(self); +} + + +/* Set attributes */ +static int +OutputDescriptor_setattr(OutputDescriptorObject *self, char *name, PyObject *v) +{ + if (v == NULL) { + int rv = PyDict_DelItemString(self->dict, name); + if (rv < 0) + PyErr_SetString(PyExc_AttributeError,"non-existing OutputDescriptor attribute"); + return rv; + } + else + return PyDict_SetItemString(self->dict, name, v); +} + + +/* Get attributes */ +static PyObject * +OutputDescriptor_getattr(OutputDescriptorObject *self, char *name) +{ + if (self->dict != NULL) { + PyObject *v = PyDict_GetItemString(self->dict, name); + if (v != NULL) + { + Py_INCREF(v); + return v; + } + } + return NULL; +} + + +/* String representation */ +static PyObject * +OutputDescriptor_repr(PyObject *self) +{ + OutputDescriptorObject* v = (OutputDescriptorObject*)self; + if (v->dict) return PyDict_Type.tp_repr((PyObject *)v->dict); + else return PyString_FromString("OutputDescriptor()"); +} + +#define OutputDescriptor_alloc PyType_GenericAlloc +#define OutputDescriptor_free PyObject_Del + + +/* REAL-TIME TYPE OBJECT */ + +PyTypeObject OutputDescriptor_Type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "vampy.OutputDescriptor",/*tp_name*/ + sizeof(OutputDescriptorObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)OutputDescriptorObject_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc)OutputDescriptor_getattr, /*tp_getattr*/ + (setattrfunc)OutputDescriptor_setattr, /*tp_setattr*/ + 0, /*tp_compare*/ + OutputDescriptor_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ //TypeObject Methods + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + OutputDescriptor_alloc, /*tp_alloc*/ + OutputDescriptor_new, /*tp_new*/ + OutputDescriptor_free, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* PyOutputDescriptor C++ API */ + diff -r 000000000000 -r 27bab3a16c9a PyOutputDescriptor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyOutputDescriptor.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,33 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#ifndef _PYOUTPUTDESCRIPTOR_H_ +#define _PYOUTPUTDESCRIPTOR_H_ + +#include "vamp-sdk/Plugin.h" + +typedef struct { + PyObject_HEAD + PyObject *dict; +} OutputDescriptorObject; + +PyAPI_DATA(PyTypeObject) OutputDescriptor_Type; + +#define PyOutputDescriptor_CheckExact(v) ((v)->ob_type == &OutputDescriptor_Type) +#define PyOutputDescriptor_Check(v) PyObject_TypeCheck(v, &OutputDescriptor_Type) + +/* PyOutputDescriptor C++ API */ + + +///fast macro version as per API convention +#define PyOutputDescriptor_AS_DICT(v) ((const OutputDescriptorObject* const) (v))->dict + +#endif diff -r 000000000000 -r 27bab3a16c9a PyParameterDescriptor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyParameterDescriptor.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,154 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include +#include "PyParameterDescriptor.h" +#include "vamp-sdk/Plugin.h" +#include +#include "PyTypeInterface.h" + +using namespace std; +using namespace Vamp; +using Vamp::Plugin; + +/* ParameterDescriptor Object's Methods */ +//these objects have no callable methods + +/* PyParameterDescriptor methods implementing protocols */ +// these functions are called by the interpreter automatically + +/* New ParameterDescriptor object */ +static PyObject * +ParameterDescriptor_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + + ParameterDescriptorObject *self = + (ParameterDescriptorObject*)type->tp_alloc(type, 0); + + if (self == NULL) return NULL; + self->dict = PyDict_New(); + if (self->dict == NULL) return NULL; + + /// allow copying objects + if (args and PyTuple_Size(args) == 1) { + PyObject* arg = PyTuple_GET_ITEM(args,0); + if (PyParameterDescriptor_CheckExact(arg)) + PyDict_Merge(self->dict,PyParameterDescriptor_AS_DICT(arg),0); + else if (PyDict_CheckExact(arg)) + PyDict_Merge(self->dict,arg,0); + else { + PyErr_SetString(PyExc_TypeError, + "Object takes zero or one ParameterDescriptor or dictionary arguments."); + return NULL; + } + } + return (PyObject *) self; +} + + +/* DESTRUCTOR: delete type object */ +static void +ParameterDescriptorObject_dealloc(ParameterDescriptorObject *self) +{ + Py_XDECREF(self->dict); + PyObject_Del(self); +} + + +/* Set attributes */ +static int +ParameterDescriptor_setattr(ParameterDescriptorObject *self, char *name, PyObject *v) +{ + if (v == NULL) { + int rv = PyDict_DelItemString(self->dict, name); + if (rv < 0) + PyErr_SetString(PyExc_AttributeError,"non-existing ParameterDescriptor attribute"); + return rv; + } + else + return PyDict_SetItemString(self->dict, name, v); +} + + +/* Get attributes */ +static PyObject * +ParameterDescriptor_getattr(ParameterDescriptorObject *self, char *name) +{ + if (self->dict != NULL) { + PyObject *v = PyDict_GetItemString(self->dict, name); + if (v != NULL) + { + Py_INCREF(v); + return v; + } + } + return NULL; +} + + +/* String representation */ +static PyObject * +ParameterDescriptor_repr(PyObject *self) +{ + ParameterDescriptorObject* v = (ParameterDescriptorObject*)self; + if (v->dict) return PyDict_Type.tp_repr((PyObject *)v->dict); + else return PyString_FromString("ParameterDescriptor()"); +} + +#define ParameterDescriptor_alloc PyType_GenericAlloc +#define ParameterDescriptor_free PyObject_Del + +PyTypeObject ParameterDescriptor_Type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "vampy.ParameterDescriptor",/*tp_name*/ + sizeof(ParameterDescriptorObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)ParameterDescriptorObject_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc)ParameterDescriptor_getattr, /*tp_getattr*/ + (setattrfunc)ParameterDescriptor_setattr, /*tp_setattr*/ + 0, /*tp_compare*/ + ParameterDescriptor_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ //TypeObject Methods + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + ParameterDescriptor_alloc,/*tp_alloc*/ + ParameterDescriptor_new,/*tp_new*/ + ParameterDescriptor_free,/*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* PyParameterDescriptor C++ API */ + diff -r 000000000000 -r 27bab3a16c9a PyParameterDescriptor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyParameterDescriptor.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,33 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#ifndef _PYPARAMETERDESCRIPTOR_H_ +#define _PYPARAMETERDESCRIPTOR_H_ + +#include "vamp-sdk/Plugin.h" + +typedef struct { + PyObject_HEAD + PyObject *dict; +} ParameterDescriptorObject; + +PyAPI_DATA(PyTypeObject) ParameterDescriptor_Type; + +#define PyParameterDescriptor_CheckExact(v) ((v)->ob_type == &ParameterDescriptor_Type) +#define PyParameterDescriptor_Check(v) PyObject_TypeCheck(v, &ParameterDescriptor_Type) + +/* PyParameterDescriptor C++ API */ + + +///fast macro version as per API convention +#define PyParameterDescriptor_AS_DICT(v) ((const ParameterDescriptorObject* const) (v))->dict + +#endif diff -r 000000000000 -r 27bab3a16c9a PyPlugScanner.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyPlugScanner.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,315 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + + +#include "PyPlugScanner.h" +#include +#include +//#include "vamp-hostsdk/PluginHostAdapter.h" + +#ifdef _WIN32 +#include +#include +#define pathsep ("\\") +#else +#include +#include +#define pathsep ("/") +#endif +#define joinPath(a,b) ( (a)+pathsep+(b) ) + +using std::string; +using std::vector; +using std::cerr; +using std::endl; +using std::find; + +PyPlugScanner::PyPlugScanner() +{ + +} + +PyPlugScanner *PyPlugScanner::m_instance = NULL; +bool PyPlugScanner::m_hasInstance = false; + +PyPlugScanner* +PyPlugScanner::getInstance() +{ + if (!m_hasInstance) { + m_instance = new PyPlugScanner(); + m_hasInstance = true; + } + return m_instance; +} + +void +PyPlugScanner::setPath(vector path) +{ + m_path=path; +} + +// We assume that each script on the path has one valid class +vector +PyPlugScanner::getPyPlugs() +{ + //for_each m_path listFiles then return vector + //key format: FullPathString/FileName.py:ClassName + + bool getCompiled = true; + char* getPyc = getenv("VAMPY_COMPILED"); + if (getPyc) { + string value(getPyc); + cerr << "VAMPY_COMPILED=" << value << endl; + getCompiled = value.compare("1")?false:true; + } + + vector pyPlugs; + string pluginKey; + PyObject *pyClass; + + for (size_t i = 0; i < m_path.size(); ++i) { + + vector files = listFiles(m_path[i],"py"); + + /// recognise byte compiled plugins + if (getCompiled) { + vector compiled_files = listFiles(m_path[i],"pyc"); + mergeFileLists(compiled_files,files); + } + + for (vector::iterator fi = files.begin(); + fi != files.end(); ++fi) { + string script = *fi; + if (!script.empty()) { + string classname=script.substr(0,script.rfind('.')); + pluginKey=joinPath(m_path[i],script)+":"+classname; + pyClass = getScriptClass(m_path[i],classname); + if (pyClass == NULL) + cerr << "Warning: Syntax error in VamPy plugin: " + << classname << ". Avoiding plugin." << endl; + else { + pyPlugs.push_back(pluginKey); + m_pyClasses.push_back(pyClass); + } + //pyPlugs.push_back(pluginKey); + } + } + } + +return pyPlugs; + +} + +/// insert python byte code names (.pyc) if a .py file can not be found +/// The interpreter automatically generates byte code files and executes +/// them if they exist. Therefore, we prefer .py files, but we allow +/// (relatively) closed source distributions by recognising .pyc files. +void +PyPlugScanner::mergeFileLists(vector &pyc, vector &py) +{ + for (vector::iterator pycit = pyc.begin(); + pycit != pyc.end(); ++pycit) { + // cerr << *pycit; + string pyc_name = *pycit; + string py_name = pyc_name.substr(0,pyc_name.rfind('.')) + ".py"; + vector::iterator pyit = find (py.begin(), py.end(), py_name); + if (pyit == py.end()) py.push_back(pyc_name); + } + +} + + +//For now return one class object found in each script +vector +PyPlugScanner::getPyClasses() +{ +return m_pyClasses; + +} + +//Validate +//This should not be called more than once! +PyObject* +PyPlugScanner::getScriptClass(string path, string classname) +{ + + //Add plugin path to active Python Path + string pyCmd = "import sys\nsys.path.append('" + path + "')\n"; + PyRun_SimpleString(pyCmd.c_str()); + + //Assign an object to the source code + PyObject *pySource = PyString_FromString(classname.c_str()); + + //Import it as a module into the py interpreter + PyObject *pyModule = PyImport_Import(pySource); + PyObject* pyError = PyErr_Occurred(); + if (! pyError == 0) { + cerr << "ERROR: error importing source: " << classname << endl; + PyErr_Print(); + Py_DECREF(pySource); + Py_CLEAR(pyModule); // safer if pyModule==NULL + return NULL; + } + Py_DECREF(pySource); + + //Read the dictionary object holding the namespace of the module (borrowed reference) + PyObject *pyDict = PyModule_GetDict(pyModule); + Py_DECREF(pyModule); + + //Get the PluginClass from the module (borrowed reference) + PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str()); + + //Check if class is present and a callable method is implemented + if (pyClass && PyCallable_Check(pyClass)) { + + return pyClass; + } + else { + cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl + << "Hint: plugin source filename and plugin class name must be the same." << endl; + PyErr_Print(); + return NULL; + } +} + + + +// Return a list of files in dir with given extension +// Code taken from hostext/PluginLoader.cpp +vector +PyPlugScanner::listFiles(string dir, string extension) +{ + vector files; + +#ifdef _WIN32 + + string expression = dir + "\\*." + extension; + WIN32_FIND_DATA data; + HANDLE fh = FindFirstFile(expression.c_str(), &data); + if (fh == INVALID_HANDLE_VALUE) return files; + + bool ok = true; + while (ok) { + files.push_back(data.cFileName); + ok = FindNextFile(fh, &data); + } + + FindClose(fh); + +#else + + size_t extlen = extension.length(); + DIR *d = opendir(dir.c_str()); + if (!d) return files; + + struct dirent *e = 0; + while ((e = readdir(d))) { + + if (!e->d_name) continue; + + size_t len = strlen(e->d_name); + if (len < extlen + 2 || + e->d_name + len - extlen - 1 != "." + extension) { + continue; + } + //cerr << "pyscripts: " << e->d_name << endl; + files.push_back(e->d_name); + } + + closedir(d); +#endif + + return files; +} + + +//!!! It would probably be better to actually call +// PluginHostAdapter::getPluginPath. That would mean this "plugin" +// needs to link against vamp-hostsdk, but that's probably acceptable +// as it is sort of a host as well. + +// std::vector +// PyPlugScanner::getAllValidPath() +// { +// Vamp::PluginHostAdapter host_adapter( ??? ); +// return host_adapter.getPluginPath(); +// } + +// tried to implement it, but found a bit confusing how to +// instantiate the host adapter here... + + +//Return correct plugin directories as per platform +//Code taken from vamp-sdk/PluginHostAdapter.cpp +std::vector +PyPlugScanner::getAllValidPath() +{ + + std::vector path; + std::string envPath; + + char *cpath = getenv("VAMP_PATH"); + if (cpath) envPath = cpath; + +#ifdef _WIN32 +#define PATH_SEPARATOR ';' +#define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins" +#else +#define PATH_SEPARATOR ':' +#ifdef __APPLE__ +#define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp" +#else +#define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp" +#endif +#endif + + if (envPath == "") { + envPath = DEFAULT_VAMP_PATH; + char *chome = getenv("HOME"); + if (chome) { + std::string home(chome); + std::string::size_type f; + while ((f = envPath.find("$HOME")) != std::string::npos && + f < envPath.length()) { + envPath.replace(f, 5, home); + } + } +#ifdef _WIN32 + char *cpfiles = getenv("ProgramFiles"); + if (!cpfiles) cpfiles = "C:\\Program Files"; + std::string pfiles(cpfiles); + std::string::size_type f; + while ((f = envPath.find("%ProgramFiles%")) != std::string::npos && + f < envPath.length()) { + envPath.replace(f, 14, pfiles); + } +#endif + } + + std::string::size_type index = 0, newindex = 0; + + while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) { + path.push_back(envPath.substr(index, newindex - index)); + index = newindex + 1; + } + + path.push_back(envPath.substr(index)); + + //can add an extra path for vampy plugins + char* extraPath = getenv("VAMPY_EXTPATH"); + if (extraPath) { + string vampyPath(extraPath); + cerr << "VAMPY_EXTPATH=" << vampyPath << endl; + path.push_back(vampyPath); + } + + return path; +} diff -r 000000000000 -r 27bab3a16c9a PyPlugScanner.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyPlugScanner.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,80 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006-2007 Chris Cannam and QMUL. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _VAMP_PYPLUG_SCANNER_H_ +#define _VAMP_PYPLUG_SCANNER_H_ + +#include +#include +#include +#include +#include +//#include + +class PyPlugScanner +{ +public: + ~PyPlugScanner() { m_hasInstance = false; } + static PyPlugScanner *getInstance(); + std::vector getPyPlugs(); + std::vector getPyClasses(); + void setPath(std::vector path); + std::vector getAllValidPath(); + +protected: + PyPlugScanner(); + PyObject *getScriptClass(std::string path, std::string classname); + std::vector listFiles(std::string dir, std::string ext); + void mergeFileLists(std::vector &s, std::vector &t); + + static bool m_hasInstance; + static PyPlugScanner *m_instance; + std::string m_dir; + std::vector m_path; + std::vector m_pyClasses; +}; + +#endif + diff -r 000000000000 -r 27bab3a16c9a PyPlugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyPlugin.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,426 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include +#include "PyPlugin.h" +#include "PyTypeInterface.h" +#include +#include "PyExtensionModule.h" + + +#ifdef _WIN32 +#define PATHSEP ('\\') +#else +#define PATHSEP ('/') +#endif + +using std::string; +using std::vector; +using std::cerr; +using std::endl; +using std::map; + +Mutex PyPlugin::m_pythonInterpreterMutex; + +PyPlugin::PyPlugin(std::string pluginKey, float inputSampleRate, PyObject *pyClass, int &instcount) : + Plugin(inputSampleRate), + m_pyClass(pyClass), + m_instcount(instcount), + m_stepSize(0), + m_blockSize(0), + m_channels(0), + m_plugin(pluginKey), + m_class(pluginKey.substr(pluginKey.rfind(':')+1,pluginKey.size()-1)), + m_path((pluginKey.substr(0,pluginKey.rfind(PATHSEP)))), + m_processType(not_implemented), + m_pyProcess(NULL), + m_inputDomain(TimeDomain), + m_quitOnErrorFlag(false), + m_debugFlag(false) +{ + m_ti.setInputSampleRate(inputSampleRate); + MutexLocker locker(&m_pythonInterpreterMutex); + cerr << "Creating instance " << m_instcount << " of " << pluginKey << endl; + + // Create an instance + Py_INCREF(m_pyClass); + PyObject *pyInputSampleRate = PyFloat_FromDouble(inputSampleRate); + PyObject *args = PyTuple_Pack(1, pyInputSampleRate); + m_pyInstance = PyObject_Call(m_pyClass, args, NULL); + + if (!m_pyInstance || PyErr_Occurred()) { + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + Py_DECREF(m_pyClass); + Py_CLEAR(args); + Py_CLEAR(pyInputSampleRate); + cerr << "PyPlugin::PyPlugin: Failed to create Python plugin instance for key \"" + << pluginKey << "\" (is the 1-arg class constructor from sample rate correctly provided?)" << endl; + throw std::string("Constructor failed"); + } + Py_INCREF(m_pyInstance); + Py_DECREF(args); + Py_DECREF(pyInputSampleRate); + + m_instcount++; + + // query and decode vampy flags + m_vampyFlags = getBinaryFlags("vampy_flags",vf_NULL); + + m_debugFlag = (bool) (m_vampyFlags & vf_DEBUG); + m_quitOnErrorFlag = (bool) (m_vampyFlags & vf_QUIT); + bool st_flag = (bool) (m_vampyFlags & vf_STRICT); + m_useRealTimeFlag = (bool) (m_vampyFlags & vf_REALTIME); + + if (m_debugFlag) cerr << "Debug messages ON for Vampy plugin: " << m_class << endl; + else cerr << "Debug messages OFF for Vampy plugin: " << m_class << endl; + + if (m_debugFlag && m_quitOnErrorFlag) cerr << "Quit on type error ON for: " << m_class << endl; + + if (m_debugFlag && st_flag) cerr << "Strict type conversion ON for: " << m_class << endl; + m_ti.setStrictTypingFlag(st_flag); + +} + +PyPlugin::~PyPlugin() +{ + MutexLocker locker(&m_pythonInterpreterMutex); + m_instcount--; + // cerr << "Deleting plugin instance. Count: " << m_instcount << endl; + + if (m_pyInstance) Py_DECREF(m_pyInstance); + //we increase the class refcount before creating an instance + if (m_pyClass) Py_DECREF(m_pyClass); + if (m_pyProcess) Py_CLEAR(m_pyProcess); + +#ifdef _DEBUG + cerr << "PyPlugin::PyPlugin:" << m_class << " instance " << m_instcount << " deleted." << endl; +#endif +} + +string +PyPlugin::getIdentifier() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + string rString="vampy-xxx"; + if (!m_debugFlag) return genericMethodCall("getIdentifier",rString); + + rString = genericMethodCall("getIdentifier",rString); + if (rString == "vampy-xxx") + cerr << "Warning: Plugin must return a unique identifier." << endl; + return rString; +} + +string +PyPlugin::getName() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + string rString="VamPy Plugin (Noname)"; + return genericMethodCall("getName",rString); +} + +string +PyPlugin::getDescription() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + string rString="Not given. (Hint: Implement getDescription method.)"; + return genericMethodCall("getDescription",rString); +} + + +string +PyPlugin::getMaker() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + string rString="VamPy Plugin."; + return genericMethodCall("getMaker",rString); +} + +int +PyPlugin::getPluginVersion() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + size_t rValue=2; + return genericMethodCall("getPluginVersion",rValue); +} + +string +PyPlugin::getCopyright() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + string rString="Licence information not available."; + return genericMethodCall("getCopyright",rString); +} + + +bool +PyPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_inputDomain = getInputDomain(); + + //Note: placing Mutex before the calls above causes deadlock !! + MutexLocker locker(&m_pythonInterpreterMutex); + + m_stepSize = stepSize; + m_blockSize = blockSize; + m_channels = channels; + + //query the process implementation type + //two optional flags can be used: 'use_numpy_interface' or 'use_legacy_interface' + //if they are not provided, we fall back to the original method + setProcessType(); + + return genericMethodCallArgs("initialise",channels,stepSize,blockSize); +} + +void +PyPlugin::reset() +{ + MutexLocker locker(&m_pythonInterpreterMutex); + genericMethodCall("reset"); +} + +PyPlugin::InputDomain +PyPlugin::getInputDomain() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + return genericMethodCall("getInputDomain",m_inputDomain); +} + +size_t +PyPlugin::getPreferredBlockSize() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + size_t rValue = 0; + return genericMethodCall("getPreferredBlockSize",rValue); +} + +size_t +PyPlugin::getPreferredStepSize() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + size_t rValue = 0; + return genericMethodCall("getPreferredStepSize",rValue); +} + +size_t +PyPlugin::getMinChannelCount() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + size_t rValue = 1; + return genericMethodCall("getMinChannelCount",rValue); +} + +size_t +PyPlugin::getMaxChannelCount() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + size_t rValue = 1; + return genericMethodCall("getMaxChannelCount",rValue); +} + +PyPlugin::OutputList +PyPlugin::getOutputDescriptors() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + OutputList list; + return genericMethodCall("getOutputDescriptors",list); +} + +PyPlugin::ParameterList +PyPlugin::getParameterDescriptors() const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + ParameterList list; +#ifdef _DEBUG + ///Note: This function is often called first by the host. + if (!m_pyInstance) {cerr << "Error: pyInstance is NULL" << endl; return list;} +#endif + + return genericMethodCall("getParameterDescriptors",list); +} + +void PyPlugin::setParameter(std::string paramid, float newval) +{ + MutexLocker locker(&m_pythonInterpreterMutex); + genericMethodCallArgs("setParameter",paramid,newval); +} + +float PyPlugin::getParameter(std::string paramid) const +{ + MutexLocker locker(&m_pythonInterpreterMutex); + return genericMethodCallArgs("getParameter",paramid); +} + +#ifdef _DEBUG_VALUES +static int proccounter = 0; +#endif + +PyPlugin::FeatureSet +PyPlugin::process(const float *const *inputBuffers,Vamp::RealTime timestamp) +{ + MutexLocker locker(&m_pythonInterpreterMutex); + +#ifdef _DEBUG_VALUES + /// we only need this if we'd like to see what frame a set of values belong to + cerr << "[Vampy::call] process, frame:" << proccounter << endl; + proccounter++; +#endif + + if (m_blockSize == 0 || m_channels == 0) { + cerr << "ERROR: PyPlugin::process: " + << "Plugin has not been initialised" << endl; + return FeatureSet(); + } + + if (m_processType == not_implemented) { + cerr << "ERROR: In Python plugin [" << m_class + << "] No process implementation found. Returning empty feature set." << endl; + return FeatureSet(); + } + + return processMethodCall(inputBuffers,timestamp); + +} + +PyPlugin::FeatureSet +PyPlugin::getRemainingFeatures() +{ + MutexLocker locker(&m_pythonInterpreterMutex); + FeatureSet rValue; + return genericMethodCall("getRemainingFeatures",rValue); +} + +bool +PyPlugin::getBooleanFlag(char flagName[], bool defValue = false) const +{ + bool rValue = defValue; + if (PyObject_HasAttrString(m_pyInstance,flagName)) + { + PyObject *pyValue = PyObject_GetAttrString(m_pyInstance,flagName); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + } else { + rValue = m_ti.PyValue_To_Bool(pyValue); + if (m_ti.error) { + Py_CLEAR(pyValue); + typeErrorHandler(flagName); + rValue = defValue; + } else Py_DECREF(pyValue); + } + } + if (m_debugFlag) cerr << FLAG_VALUE << endl; + return rValue; +} + +int +PyPlugin::getBinaryFlags(char flagName[], eVampyFlags defValue = vf_NULL) const +{ + int rValue = defValue; + if (PyObject_HasAttrString(m_pyInstance,flagName)) + { + PyObject *pyValue = PyObject_GetAttrString(m_pyInstance,flagName); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + } else { + rValue |= (int) m_ti.PyValue_To_Size_t(pyValue); + if (m_ti.error) { + Py_CLEAR(pyValue); + typeErrorHandler(flagName); + rValue = defValue; + } else Py_DECREF(pyValue); + } + } + if (m_debugFlag) cerr << FLAG_VALUE << endl; + return rValue; +} + + +void +PyPlugin::setProcessType() +{ + //quering process implementation type + char legacyMethod[]="process"; + char numpyMethod[]="processN"; + + if (PyObject_HasAttrString(m_pyInstance,legacyMethod) && + m_processType == 0) + { + m_processType = legacyProcess; + m_pyProcess = PyString_FromString(legacyMethod); + m_pyProcessCallable = PyObject_GetAttr(m_pyInstance,m_pyProcess); + } + + if (PyObject_HasAttrString(m_pyInstance,numpyMethod) && + m_processType == 0) + { + m_processType = numpy_bufferProcess; + m_pyProcess = PyString_FromString(numpyMethod); + m_pyProcessCallable = PyObject_GetAttr(m_pyInstance,m_pyProcess); + } + + // These flags are optional. If provided, they override the + // implementation type making the use of the odd processN() + // function redundant. + // However, the code above provides backward compatibility. + + if (m_vampyFlags & vf_BUFFER) { + m_processType = numpy_bufferProcess; + if (m_debugFlag) cerr << "Process using (numpy) buffer interface." << endl; + } + + if (m_vampyFlags & vf_ARRAY) { +#ifdef HAVE_NUMPY + m_processType = numpy_arrayProcess; + if (m_debugFlag) cerr << "Process using numpy array interface." << endl; +#else + cerr << "Error: This version of vampy was compiled without numpy support, " + << "however the vf_ARRAY flag is set for plugin: " << m_class << endl + << "The default behaviour is: passing a python list of samples for each channel in process() " + << "or a list of memory buffers in processN(). " << endl + << "This can be used create numpy arrays using the numpy.frombuffer() command." << endl; +#endif + } + + if (!m_processType) + { + m_processType = not_implemented; + m_pyProcess = NULL; + m_pyProcessCallable = NULL; + char method[]="initialise::setProcessType"; + cerr << PLUGIN_ERROR << " No process implementation found. Plugin will do nothing." << endl; + } +} + +void +PyPlugin::typeErrorHandler(char *method) const +{ + bool strict = false; + while (m_ti.error) { + PyTypeInterface::ValueError e = m_ti.getError(); + cerr << PLUGIN_ERROR << e.str() << endl; + if (e.strict) strict = true; + // e.print(); + } + /// quit on hard errors like accessing NULL pointers or strict type conversion + /// errors IF the user sets the quitOnErrorFlag in the plugin. + /// Otherwise most errors will go unnoticed apart from + /// a messages in the terminal. + /// It would be best if hosts could catch an exception instead + /// and display something meaningful to the user. + if (strict && m_quitOnErrorFlag) exit(EXIT_FAILURE); +} + diff -r 000000000000 -r 27bab3a16c9a PyPlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyPlugin.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,491 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _PYTHON_WRAPPER_PLUGIN_H_ +#define _PYTHON_WRAPPER_PLUGIN_H_ + +#define _CLASS_METHOD_ m_class << "::" << method +#define PLUGIN_ERROR "ERROR: In Vampy plugin [" << _CLASS_METHOD_ << "]" << endl << "Cause: " +#define DEBUG_NAME "[Vampy::call] " << _CLASS_METHOD_ << " " +#define DEAFULT_RETURN "Method [" << _CLASS_METHOD_ << "] is not implemented. Returning default value." +#define FLAG_VALUE "Flag: " << flagName << ": " << ((rValue==0)?"False":"True") + +#include +#include "PyExtensionModule.h" +#include "PyTypeInterface.h" +#include "vamp-sdk/Plugin.h" +#include "Mutex.h" + +using std::string; +using std::cerr; +using std::endl; + +enum eProcessType { + not_implemented, + legacyProcess, + numpyProcess, + numpy_bufferProcess, + numpy_arrayProcess + }; + +class PyPlugin : public Vamp::Plugin +{ +public: + PyPlugin(std::string plugin,float inputSampleRate, PyObject *pyClass, int &instcount); + virtual ~PyPlugin(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const; + size_t getPreferredBlockSize() const; + size_t getPreferredStepSize() const; + size_t getMinChannelCount() const; + size_t getMaxChannelCount() const; + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + OutputList getOutputDescriptors() const; + ParameterList getParameterDescriptors() const; + float getParameter(std::string paramid) const; + void setParameter(std::string paramid, float newval); + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + static Mutex m_pythonInterpreterMutex; + PyObject *m_pyClass; + PyObject *m_pyInstance; + int &m_instcount; + size_t m_stepSize; + size_t m_blockSize; + size_t m_channels; + std::string m_plugin; + std::string m_class; + std::string m_path; + eProcessType m_processType; + PyObject *m_pyProcess; + PyObject *m_pyProcessCallable; + mutable InputDomain m_inputDomain; + PyTypeInterface m_ti; + int m_vampyFlags; + bool m_quitOnErrorFlag; + bool m_debugFlag; + bool m_useRealTimeFlag; + + void setProcessType(); + + FeatureSet processMethodCall(const float *const *inputBuffers,Vamp::RealTime timestamp); + + bool getBooleanFlag(char flagName[],bool) const; + int getBinaryFlags(char flagName[], eVampyFlags) const; + void typeErrorHandler(char *method) const; + + /// simple 'void return' call with no args + void genericMethodCall(char *method) const + { + if (m_debugFlag) cerr << DEBUG_NAME << endl; + if ( PyObject_HasAttrString(m_pyInstance,method) ) + { + PyObject *pyValue = PyObject_CallMethod(m_pyInstance, method, NULL); + if (!pyValue) { + cerr << PLUGIN_ERROR << "Failed to call method." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + } + } + } + + /// 'no arg with default return value' call + template + RET &genericMethodCall(char *method, RET &rValue) const + { + if (m_debugFlag) cerr << DEBUG_NAME << endl; + if ( PyObject_HasAttrString(m_pyInstance,method) ) + { + PyObject *pyValue = PyObject_CallMethod(m_pyInstance, method, NULL); + if (!pyValue) { + cerr << PLUGIN_ERROR << "Failed to call method." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + return rValue; + } + + /// convert the returned value + m_ti.PyValue_To_rValue(pyValue,rValue); + if (!m_ti.error) { + Py_DECREF(pyValue); + } else { + Py_CLEAR(pyValue); + typeErrorHandler(method); + } + return rValue; + } + if (m_debugFlag) cerr << DEAFULT_RETURN << endl; + return rValue; + } + + /// unary call + template + RET genericMethodCallArgs(char *method, A1 arg1) const + { + RET rValue = RET(); + if (m_debugFlag) cerr << DEBUG_NAME << endl; + if (!PyObject_HasAttrString(m_pyInstance,method)) { + if (m_debugFlag) cerr << DEAFULT_RETURN << endl; + return rValue; + } + + /// prepare arguments for fast method call + PyObject *pyMethod = m_ti.PyValue_From_CValue(method); + PyObject *pyCallable = PyObject_GetAttr(m_pyInstance,pyMethod); + PyObject* pyArgs = PyTuple_New(1); + if (!(pyArgs && pyCallable && pyMethod)) { + cerr << PLUGIN_ERROR << "Failed to prepare argument for calling method." << endl; + Py_CLEAR(pyMethod); + Py_CLEAR(pyCallable); + Py_CLEAR(pyArgs); + return rValue; + } + + PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1); + if (m_ti.error) { + cerr << PLUGIN_ERROR << "Failed to convert argument for calling method." << endl; + typeErrorHandler(method); + Py_CLEAR(pyMethod); + Py_CLEAR(pyCallable); + Py_CLEAR(pyArg1); + Py_CLEAR(pyArgs); + return rValue; + } + + PyTuple_SET_ITEM(pyArgs, 0, pyArg1); + Py_INCREF(pyArg1); + + /// call the method + PyObject *pyValue = PyObject_Call(pyCallable,pyArgs,NULL); + if (!pyValue) + { + cerr << PLUGIN_ERROR << "Failed to call method." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + Py_CLEAR(pyMethod); + Py_CLEAR(pyCallable); + Py_CLEAR(pyArg1); + Py_CLEAR(pyArgs); + return rValue; + } + + Py_DECREF(pyMethod); + Py_DECREF(pyCallable); + Py_DECREF(pyArg1); + Py_DECREF(pyArgs); + + /// convert the returned value + m_ti.PyValue_To_rValue(pyValue,rValue); + if (!m_ti.error) { + Py_DECREF(pyValue); + } else { + Py_CLEAR(pyValue); + typeErrorHandler(method); + } + return rValue; + } + + /// binary call + template + RET genericMethodCallArgs(char *method, A1 arg1, A2 arg2) const + { + RET rValue = RET(); + if (m_debugFlag) cerr << DEBUG_NAME << endl; + if (!PyObject_HasAttrString(m_pyInstance,method)) { + if (m_debugFlag) cerr << DEAFULT_RETURN << endl; + return rValue; + } + + /// prepare arguments for fast method call + PyObject *pyMethod = m_ti.PyValue_From_CValue(method); + PyObject *pyCallable = PyObject_GetAttr(m_pyInstance,pyMethod); + PyObject* pyArgs = PyTuple_New(2); + if (!(pyArgs && pyCallable && pyMethod)) { + cerr << PLUGIN_ERROR << "Failed to prepare arguments for calling method." << endl; + Py_CLEAR(pyMethod); + Py_CLEAR(pyCallable); + Py_CLEAR(pyArgs); + return rValue; + } + + PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1); + PyObject *pyArg2 = m_ti.PyValue_From_CValue(arg2); + if (m_ti.error) { + cerr << PLUGIN_ERROR << "Failed to convert arguments for calling method." << endl; + typeErrorHandler(method); + Py_CLEAR(pyMethod); + Py_CLEAR(pyCallable); + Py_CLEAR(pyArg1); + Py_CLEAR(pyArg2); + Py_CLEAR(pyArgs); + return rValue; + } + + PyTuple_SET_ITEM(pyArgs, 0, pyArg1); + Py_INCREF(pyArg1); + PyTuple_SET_ITEM(pyArgs, 1, pyArg2); + Py_INCREF(pyArg2); + + // calls the method + PyObject *pyValue = PyObject_Call(pyCallable,pyArgs,NULL); + if (!pyValue) + { + cerr << PLUGIN_ERROR << "Failed to call method." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + Py_CLEAR(pyMethod); + Py_CLEAR(pyCallable); + Py_CLEAR(pyArg1); + Py_CLEAR(pyArg2); + Py_CLEAR(pyArgs); + return rValue; + } + + Py_DECREF(pyMethod); + Py_DECREF(pyCallable); + Py_DECREF(pyArg1); + Py_DECREF(pyArg2); + Py_DECREF(pyArgs); + + /// convert the returned value + m_ti.PyValue_To_rValue(pyValue,rValue); + if (!m_ti.error) { + Py_DECREF(pyValue); + } else { + Py_CLEAR(pyValue); + typeErrorHandler(method); + } + return rValue; + } + + /// trenary call + template + RET genericMethodCallArgs(char *method, A1 arg1, A2 arg2, A3 arg3) const + { + RET rValue = RET(); + if (m_debugFlag) cerr << DEBUG_NAME << endl; + if (!PyObject_HasAttrString(m_pyInstance,method)) { + if (m_debugFlag) cerr << DEAFULT_RETURN << endl; + return rValue; + } + + /// prepare arguments for fast method call + PyObject *pyMethod = m_ti.PyValue_From_CValue(method); + PyObject *pyCallable = PyObject_GetAttr(m_pyInstance,pyMethod); + PyObject* pyArgs = PyTuple_New(3); + if (!(pyArgs && pyCallable && pyMethod)) { + cerr << PLUGIN_ERROR << "Failed to prepare arguments for calling method." << endl; + Py_CLEAR(pyMethod); + Py_CLEAR(pyCallable); + Py_CLEAR(pyArgs); + return rValue; + } + + PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1); + PyObject *pyArg2 = m_ti.PyValue_From_CValue(arg2); + PyObject *pyArg3 = m_ti.PyValue_From_CValue(arg3); + if (m_ti.error) { + cerr << PLUGIN_ERROR << "Failed to convert arguments for calling method." << endl; + typeErrorHandler(method); + Py_CLEAR(pyMethod); + Py_CLEAR(pyCallable); + Py_CLEAR(pyArg1); + Py_CLEAR(pyArg2); + Py_CLEAR(pyArg3); + Py_CLEAR(pyArgs); + return rValue; + } + + /// Optimization: Pack args in a tuple to avoid va_list parsing. + PyTuple_SET_ITEM(pyArgs, 0, pyArg1); + Py_INCREF(pyArg1); + PyTuple_SET_ITEM(pyArgs, 1, pyArg2); + Py_INCREF(pyArg2); + PyTuple_SET_ITEM(pyArgs, 2, pyArg3); + Py_INCREF(pyArg3); + + // PyObject *pyValue = PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyArg1,pyArg2,pyArg3,NULL); + /// fast method call + PyObject *pyValue = PyObject_Call(pyCallable,pyArgs,NULL); + if (!pyValue) + { + cerr << PLUGIN_ERROR << "Failed to call method." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + Py_CLEAR(pyMethod); + Py_CLEAR(pyCallable); + Py_CLEAR(pyArg1); + Py_CLEAR(pyArg2); + Py_CLEAR(pyArg3); + Py_CLEAR(pyArgs); + return rValue; + } + + Py_DECREF(pyMethod); + Py_DECREF(pyCallable); + Py_DECREF(pyArg1); + Py_DECREF(pyArg2); + Py_DECREF(pyArg3); + Py_DECREF(pyArgs); + + /// convert the returned value + m_ti.PyValue_To_rValue(pyValue,rValue); + if (!m_ti.error) { + Py_DECREF(pyValue); + } else { + Py_CLEAR(pyValue); + typeErrorHandler(method); + } + return rValue; + } + +}; + +/// optimised process call +inline PyPlugin::FeatureSet +PyPlugin::processMethodCall(const float *const *inputBuffers,Vamp::RealTime timestamp) +{ + + /// Optimizations: 1) we avoid ...ObjArg functions since we know + /// the number of arguments, and we don't like va_list parsing + /// in the process. 2) Also: we're supposed to incref args, + /// but instead, we let the arguments tuple steal the references + /// and decref them when it is deallocated. + /// 3) all conversions are now using the fast sequence protocol + /// (indexing the underlying object array). + + FeatureSet rFeatureSet; + PyObject *pyChannelList = NULL; + + if (m_processType == numpy_bufferProcess) { + pyChannelList = m_ti.InputBuffers_As_SharedMemoryList(inputBuffers,m_channels,m_blockSize); + } + + if (m_processType == legacyProcess) { + pyChannelList = m_ti.InputBuffers_As_PythonLists(inputBuffers,m_channels,m_blockSize,m_inputDomain); + } + +#ifdef HAVE_NUMPY + if (m_processType == numpy_arrayProcess) { + pyChannelList = m_ti.InputBuffers_As_NumpyArray(inputBuffers,m_channels,m_blockSize,m_inputDomain); + } +#endif + +/// we don't expect these to fail unless out of memory (which is very unlikely on modern systems) +#ifdef _DEBUG + if (!pyChannelList) { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + std::string method = PyString_AsString(m_pyProcess); + cerr << PLUGIN_ERROR << "Failed to create channel list." << endl; + return rFeatureSet; + } +#endif + + PyObject *pyTimeStamp = NULL; + + if (m_useRealTimeFlag) { + //(1) pass TimeStamp as PyRealTime object + pyTimeStamp = PyRealTime_FromRealTime(timestamp); + + } else { + //(2) pass TimeStamp as frame count (long Sample Count) + pyTimeStamp = PyLong_FromLong(Vamp::RealTime::realTime2Frame + (timestamp, (unsigned int) m_inputSampleRate)); + } + + +#ifdef _DEBUG + if (!pyTimeStamp) { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + std::string method = PyString_AsString(m_pyProcess); + cerr << PLUGIN_ERROR << "Failed to create RealTime time stamp." << endl; + Py_DECREF(pyChannelList); + return rFeatureSet; + } +#endif + + /// Old method: Call python process (returns new reference) + /// PyObject *pyValue = PyObject_CallMethodObjArgs + /// (m_pyInstance,m_pyProcess,pyChannelList,pyTimeStamp,NULL); + + PyObject *pyArgs = PyTuple_New(2); + PyTuple_SET_ITEM(pyArgs, 0, pyChannelList); + PyTuple_SET_ITEM(pyArgs, 1, pyTimeStamp); + + /// Call python process (returns new reference) {kwArgs = NULL} + PyObject *pyValue = PyObject_Call(m_pyProcessCallable,pyArgs,NULL); + + if (!pyValue) { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + std::string method = PyString_AsString(m_pyProcess); + cerr << PLUGIN_ERROR << "An error occurred while evaluating Python process." << endl; + Py_CLEAR(pyValue); + Py_CLEAR(pyArgs); + return rFeatureSet; + } + + rFeatureSet = m_ti.PyValue_To_FeatureSet(pyValue); + if (!m_ti.error) { + Py_DECREF(pyValue); + Py_DECREF(pyArgs); + } else { + typeErrorHandler(PyString_AsString(m_pyProcess)); + Py_CLEAR(pyValue); + Py_CLEAR(pyArgs); + } + return rFeatureSet; +} + +#endif diff -r 000000000000 -r 27bab3a16c9a PyRealTime.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyRealTime.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,356 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include +#include "PyRealTime.h" +#include "vamp-sdk/Plugin.h" +#include + +using namespace std; +using namespace Vamp; +using Vamp::Plugin; +using Vamp::RealTime; + + +/* CONSTRUCTOR: New RealTime object from sec and nsec */ +static PyObject* +RealTime_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + unsigned int sec = 0; + unsigned int nsec = 0; + double unary = 0; + const char *fmt = NULL; + + if ( + /// new RealTime from ('format',float) e.g. ('seconds',2.34123) + !PyArg_ParseTuple(args, "|sd:RealTime.new ", + (const char *) &fmt, + (double *) &unary) && + + /// new RealTime from (sec{int},nsec{int}) e.g. (2,34) + !PyArg_ParseTuple(args, "|II:RealTime.new ", + (unsigned int*) &sec, + (unsigned int*) &nsec) + + ) { + PyErr_SetString(PyExc_TypeError, + "RealTime initialised with wrong arguments."); + return NULL; + } + + // PyErr_Clear(); + + // RealTimeObject *self = PyObject_New(RealTimeObject, &RealTime_Type); + RealTimeObject *self = (RealTimeObject*)type->tp_alloc(type, 0); + + if (self == NULL) return NULL; + + self->rt = NULL; + + if (sec == 0 && nsec == 0 && fmt == 0) + self->rt = new RealTime(); + else if (fmt == 0) + self->rt = new RealTime(sec,nsec); + else { + /// new RealTime from seconds or milliseconds: i.e. >>>RealTime('seconds',12.3) + if (!string(fmt).compare("float") || + !string(fmt).compare("seconds")) + self->rt = new RealTime( + RealTime::fromSeconds((double) unary)); + + if (!string(fmt).compare("milliseconds")) { + self->rt = new RealTime( + RealTime::fromSeconds((double) unary / 1000.0)); } + } + + if (!self->rt) { + PyErr_SetString(PyExc_TypeError, + "RealTime initialised with wrong arguments."); + return NULL; + } + + return (PyObject *) self; +} + +/* DESTRUCTOR: delete type object */ +static void +RealTimeObject_dealloc(RealTimeObject *self) +{ + if (self->rt) delete self->rt; //delete the C object + PyObject_Del(self); //delete the Python object (original) + /// this requires PyType_Ready() which fills ob_type + // self->ob_type->tp_free((PyObject*)self); +} + +/* RealTime Object's Methods */ +//these are internals not exposed by the module but the object + +/* Returns a Tuple containing sec and nsec values */ +static PyObject * +RealTime_values(RealTimeObject *self) +{ + return Py_BuildValue("(ii)",self->rt->sec,self->rt->nsec); +} + +/* Returns a Text representation */ +static PyObject * +RealTime_toString(RealTimeObject *self, PyObject *args) +{ + return Py_BuildValue("s",self->rt->toText().c_str()); +} + +/* Frame representation */ +static PyObject * +RealTime_toFrame(PyObject *self, PyObject *args) +{ + unsigned int samplerate; + + if ( !PyArg_ParseTuple(args, "I:realtime.toFrame object ", + (unsigned int *) &samplerate )) { + PyErr_SetString(PyExc_ValueError,"Integer Sample Rate Required."); + return NULL; + } + + return Py_BuildValue("k", + RealTime::realTime2Frame( + *(const RealTime*) ((RealTimeObject*)self)->rt, + (unsigned int) samplerate)); +} + +/* Conversion of realtime to a double precision floating point value */ +/* ...in Python called by e.g. float(realtime) */ +static PyObject * +RealTime_float(PyObject *s) +{ + double drt = ((double) ((RealTimeObject*)s)->rt->sec + + (double)((double) ((RealTimeObject*)s)->rt->nsec)/1000000000); + return PyFloat_FromDouble(drt); +} + + +/* Type object's (RealTime) methods table */ +static PyMethodDef RealTime_methods[] = +{ + {"values", (PyCFunction)RealTime_values, METH_NOARGS, + PyDoc_STR("values() -> Tuple of sec,nsec representation.")}, + + {"toString", (PyCFunction)RealTime_toString, METH_NOARGS, + PyDoc_STR("toString() -> Return a user-readable string to the nearest millisecond in a form like HH:MM:SS.mmm")}, + + {"toFrame", (PyCFunction)RealTime_toFrame, METH_VARARGS, + PyDoc_STR("toFrame(samplerate) -> Sample count for given sample rate.")}, + + {"toFloat", (PyCFunction)RealTime_float, METH_NOARGS, + PyDoc_STR("toFloat() -> Floating point representation.")}, + + {NULL, NULL} /* sentinel */ +}; + + +/* Methods implementing protocols */ +// these functions are called by the interpreter + +/* Object Protocol */ + +static int +RealTime_setattr(RealTimeObject *self, char *name, PyObject *value) +{ + + if ( !string(name).compare("sec")) { + self->rt->sec= (int) PyInt_AS_LONG(value); + return 0; + } + + if ( !string(name).compare("nsec")) { + self->rt->nsec= (int) PyInt_AS_LONG(value); + return 0; + } + + return -1; +} + +static PyObject * +RealTime_getattr(RealTimeObject *self, char *name) +{ + + if ( !string(name).compare("sec") ) { + return PyInt_FromSsize_t( + (Py_ssize_t) self->rt->sec); + } + + if ( !string(name).compare("nsec") ) { + return PyInt_FromSsize_t( + (Py_ssize_t) self->rt->nsec); + } + + return Py_FindMethod(RealTime_methods, + (PyObject *)self, name); +} + +/* String representation called by e.g. str(realtime), print realtime*/ +static PyObject * +RealTime_repr(PyObject *self) +{ + return Py_BuildValue("s", + ((RealTimeObject*)self)->rt->toString().c_str()); +} + + +/* Number Protocol */ +/// Only add and substract make sense, or what about the +/// square root of Monday morning? +/// Divide by integer maybe for durations? + +static PyObject * +RealTime_add(PyObject *s, PyObject *w) +{ + RealTimeObject *result = + PyObject_New(RealTimeObject, &RealTime_Type); + if (result == NULL) return NULL; + + result->rt = new RealTime( + *((RealTimeObject*)s)->rt + *((RealTimeObject*)w)->rt); + return (PyObject*)result; +} + +static PyObject * +RealTime_subtract(PyObject *s, PyObject *w) +{ + RealTimeObject *result = + PyObject_New(RealTimeObject, &RealTime_Type); + if (result == NULL) return NULL; + + result->rt = new RealTime( + *((RealTimeObject*)s)->rt - *((RealTimeObject*)w)->rt); + return (PyObject*)result; +} + +static PyNumberMethods realtime_as_number = +{ + RealTime_add, /*nb_add*/ + RealTime_subtract, /*nb_subtract*/ + 0, /*nb_multiply*/ + 0, /*nb_divide*/ + 0, /*nb_remainder*/ + 0, /*nb_divmod*/ + 0, /*nb_power*/ + 0, /*nb_neg*/ + 0, /*nb_pos*/ + 0, /*(unaryfunc)array_abs,*/ + 0, /*nb_nonzero*/ + 0, /*nb_invert*/ + 0, /*nb_lshift*/ + 0, /*nb_rshift*/ + 0, /*nb_and*/ + 0, /*nb_xor*/ + 0, /*nb_or*/ + 0, /*nb_coerce*/ + 0, /*nb_int*/ + 0, /*nb_long*/ + (unaryfunc)RealTime_float,/*nb_float*/ + 0, /*nb_oct*/ + 0, /*nb_hex*/ +}; + +/* REAL-TIME TYPE OBJECT */ + +#define RealTime_alloc PyType_GenericAlloc +#define RealTime_free PyObject_Del + +/* Doc:: 10.3 Type Objects */ /* static */ +PyTypeObject RealTime_Type = +{ + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "vampy.RealTime", /*tp_name*/ + sizeof(RealTimeObject), /*tp_basicsize*/ + 0,//sizeof(RealTime), /*tp_itemsize*/ + /* methods */ + (destructor)RealTimeObject_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc)RealTime_getattr, /*tp_getattr*/ + (setattrfunc)RealTime_setattr, /*tp_setattr*/ + 0, /*tp_compare*/ + RealTime_repr, /*tp_repr*/ + &realtime_as_number, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0,//(ternaryfunc)RealTime_new, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "RealTime Object", /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + RealTime_methods, /*tp_methods*/ //TypeObject Methods + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + RealTime_alloc, /*tp_alloc*/ + RealTime_new, /*tp_new*/ + RealTime_free, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + + + +/* PyRealTime C++ API */ + +/*PyRealTime from RealTime pointer +PyObject* +PyRealTime_FromRealTime(Vamp::RealTime *rt) { + + RealTimeObject *self = + PyObject_New(RealTimeObject, &RealTime_Type); + if (self == NULL) return NULL; + + self->rt = new RealTime(*rt); + return (PyObject*) self; +}*/ + + +/*PyRealTime from RealTime*/ +PyObject* +PyRealTime_FromRealTime(Vamp::RealTime& rt) { + + RealTimeObject *self = + PyObject_New(RealTimeObject, &RealTime_Type); + if (self == NULL) return NULL; + + self->rt = new RealTime(rt); + return (PyObject*) self; +} + +/*RealTime* from PyRealTime*/ +const Vamp::RealTime* +PyRealTime_AsRealTime (PyObject *self) { + + RealTimeObject *s = (RealTimeObject*) self; + + if (!PyRealTime_Check(s)) { + PyErr_SetString(PyExc_TypeError, "RealTime Object Expected."); + cerr << "in call PyRealTime_AsPointer(): RealTime Object Expected. " << endl; + return NULL; } + return s->rt; +}; + diff -r 000000000000 -r 27bab3a16c9a PyRealTime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyRealTime.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,39 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#ifndef _PYREALTIME_H_ +#define _PYREALTIME_H_ + +#include "vamp-sdk/Plugin.h" + +typedef struct { + PyObject_HEAD + Vamp::RealTime *rt; +} RealTimeObject; + +PyAPI_DATA(PyTypeObject) RealTime_Type; + +#define PyRealTime_CheckExact(v) ((v)->ob_type == &RealTime_Type) +#define PyRealTime_Check(v) PyObject_TypeCheck(v, &RealTime_Type) +///fast macro version as per API convention +#define PyRealTime_AS_REALTIME(v) ((const RealTimeObject* const) (v))->rt + +/* PyRealTime C++ API */ + + +PyAPI_FUNC(PyObject *) +PyRealTime_FromRealTime(Vamp::RealTime&); + +PyAPI_FUNC(const Vamp::RealTime*) +PyRealTime_AsRealTime (PyObject *self); + + +#endif diff -r 000000000000 -r 27bab3a16c9a PyTypeInterface.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyTypeInterface.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,1180 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include + +#ifdef HAVE_NUMPY +#define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API +#define NO_IMPORT_ARRAY +#include "numpy/arrayobject.h" +#endif + +#include "PyTypeInterface.h" +#include "PyRealTime.h" +#include "PyExtensionModule.h" +#include +#include +#include +#ifndef SIZE_T_MAX +#define SIZE_T_MAX ((size_t) -1) +#endif + +using std::string; +using std::vector; +using std::cerr; +using std::endl; +using std::map; + +static std::map outKeys; +static std::map parmKeys; +static std::map sampleKeys; +static std::map ffKeys; +static bool isMapInitialised = false; + +/* Note: NO FUNCTION IN THIS CLASS SHOULD ALTER REFERENCE COUNTS + (EXCEPT FOR TEMPORARY PYTHON OBJECTS)! */ + +PyTypeInterface::PyTypeInterface() : + m_strict(false), + m_error(false), + error(m_error) // const public reference for easy access +{ +} + +PyTypeInterface::~PyTypeInterface() +{ +} + +/// floating point numbers (TODO: check numpy.float128) +float +PyTypeInterface::PyValue_To_Float(PyObject* pyValue) const +{ + // convert float + if (pyValue && PyFloat_Check(pyValue)) + //TODO: check for limits here (same on most systems) + return (float) PyFloat_AS_DOUBLE(pyValue); + + if (pyValue == NULL) + { + setValueError("Error while converting float object.",m_strict); + return 0.0; + } + + // in strict mode we will not try harder + if (m_strict) { + setValueError("Strict conversion error: object is not float.",m_strict); + return 0.0; + } + + // convert other objects supporting the number protocol + if (PyNumber_Check(pyValue)) + { + PyObject* pyFloat = PyNumber_Float(pyValue); // new ref + if (!pyFloat) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting " + PyValue_Get_TypeName(pyValue) + " object to float.",m_strict); + return 0.0; + } + float rValue = (float) PyFloat_AS_DOUBLE(pyFloat); + Py_DECREF(pyFloat); + return rValue; + } +/* + // convert other objects supporting the number protocol + if (PyNumber_Check(pyValue)) + { + // PEP353: Py_ssize_t is size_t but signed ! + // This will work up to numpy.float64 + Py_ssize_t rValue = PyNumber_AsSsize_t(pyValue,NULL); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + setValueError("Error while converting integer object.",m_strict); + return 0.0; + } + if (rValue > (Py_ssize_t)FLT_MAX || rValue < (Py_ssize_t)FLT_MIN) + { + setValueError("Overflow error. Object can not be converted to float.",m_strict); + return 0.0; + } + return (float) rValue; + } +*/ + // convert string + if (PyString_Check(pyValue)) + { + PyObject* pyFloat = PyFloat_FromString(pyValue,NULL); + if (!pyFloat) + { + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + setValueError("String value can not be converted to float.",m_strict); + return 0.0; + } + float rValue = (float) PyFloat_AS_DOUBLE(pyFloat); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + Py_CLEAR(pyFloat); + setValueError("Error while converting float object.",m_strict); + return 0.0; + } + Py_DECREF(pyFloat); + return rValue; + } + + // convert the first element of any iterable sequence (for convenience and backwards compatibility) + if (PySequence_Check(pyValue) and PySequence_Size(pyValue) > 0) + { + PyObject* item = PySequence_GetItem(pyValue,0); + if (item) + { + float rValue = this->PyValue_To_Float(item); + if (!m_error) { + Py_DECREF(item); + return rValue; + } else { + Py_CLEAR(item); + std::string msg = "Could not convert sequence element to float. "; + setValueError(msg,m_strict); + return 0.0; + } + } + } + + // give up + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + std::string msg = "Conversion from " + PyValue_Get_TypeName(pyValue) + " to float is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_Float failed. " << msg << endl; +#endif + return 0.0; +} + +/// size_t (unsigned integer types) +size_t +PyTypeInterface::PyValue_To_Size_t(PyObject* pyValue) const +{ + // convert objects supporting the number protocol + if (PyNumber_Check(pyValue)) + { + if (m_strict && !PyInt_Check(pyValue) && !PyLong_Check(pyValue)) + setValueError("Strict conversion error: object is not integer type.",m_strict); + // Note: this function handles Bool,Int,Long,Float + // speed is not critical in the use of this type by Vamp + // PEP353: Py_ssize_t is size_t but signed ! + Py_ssize_t rValue = PyInt_AsSsize_t(pyValue); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + setValueError("Error while converting integer object.",m_strict); + return 0; + } + if ((unsigned long)rValue > SIZE_T_MAX || (unsigned long)rValue < 0) + { + setValueError("Overflow error. Object can not be converted to size_t.",m_strict); + return 0; + } + return (size_t) rValue; + } + + // in strict mode we will not try harder and throw an exception + // then the caller should decide what to do with it + if (m_strict) { + setValueError("Strict conversion error: object is not integer.",m_strict); + return 0; + } + + // convert string + if (PyString_Check(pyValue)) + { + PyObject* pyLong = PyNumber_Long(pyValue); + if (!pyLong) + { + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + setValueError("String object can not be converted to size_t.",m_strict); + return 0; + } + size_t rValue = this->PyValue_To_Size_t(pyLong); + if (!m_error) { + Py_DECREF(pyLong); + return rValue; + } else { + Py_CLEAR(pyLong); + setValueError ("Error converting string to size_t.",m_strict); + return 0; + } + } + + // convert the first element of iterable sequences + if (PySequence_Check(pyValue) and PySequence_Size(pyValue) > 0) + { + PyObject* item = PySequence_GetItem(pyValue,0); + if (item) + { + size_t rValue = this->PyValue_To_Size_t(item); + if (!m_error) { + Py_DECREF(item); + return rValue; + } else { + Py_CLEAR(item); + setValueError("Could not convert sequence element to size_t. ",m_strict); + return 0; + } + } + } + + // give up + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to size_t is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_Size_t failed. " << msg << endl; +#endif + return 0; +} + +/// long and int +long +PyTypeInterface::PyValue_To_Long(PyObject* pyValue) const +{ + // most common case: convert int (faster) + if (pyValue && PyInt_Check(pyValue)) { + // if the object is not NULL and verified, this macro just extracts the value. + return PyInt_AS_LONG(pyValue); + } + + // long + if (PyLong_Check(pyValue)) { + long rValue = PyLong_AsLong(pyValue); + if (PyErr_Occurred()) { + PyErr_Print(); PyErr_Clear(); + setValueError("Error while converting long object.",m_strict); + return 0; + } + return rValue; + } + + if (m_strict) { + setValueError("Strict conversion error: object is not integer or long integer.",m_strict); + return 0; + } + + // convert all objects supporting the number protocol + if (PyNumber_Check(pyValue)) + { + // Note: this function handles Bool,Int,Long,Float + // PEP353: Py_ssize_t is size_t but signed ! + Py_ssize_t rValue = PyInt_AsSsize_t(pyValue); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + setValueError("Error while converting integer object.",m_strict); + return 0; + } + if (rValue > LONG_MAX || rValue < LONG_MIN) + { + setValueError("Overflow error. Object can not be converted to size_t.",m_strict); + return 0; + } + return (long) rValue; + } + + // convert string + if (PyString_Check(pyValue)) + { + PyObject* pyLong = PyNumber_Long(pyValue); + if (!pyLong) + { + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + setValueError("String object can not be converted to long.",m_strict); + return 0; + } + long rValue = this->PyValue_To_Long(pyLong); + if (!m_error) { + Py_DECREF(pyLong); + return rValue; + } else { + Py_CLEAR(pyLong); + setValueError ("Error converting string to long.",m_strict); + return 0; + } + } + + // convert the first element of iterable sequences + if (PySequence_Check(pyValue) and PySequence_Size(pyValue) > 0) + { + PyObject* item = PySequence_GetItem(pyValue,0); + if (item) + { + size_t rValue = this->PyValue_To_Long(item); + if (!m_error) { + Py_DECREF(item); + return rValue; + } else { + Py_CLEAR(item); + setValueError("Could not convert sequence element to long. ",m_strict); + return 0; + } + } + } + + // give up + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to long is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_Long failed. " << msg << endl; +#endif + return 0; +} + + +bool +PyTypeInterface::PyValue_To_Bool(PyObject* pyValue) const +{ + // convert objects supporting the number protocol + // Note: PyBool is a subclass of PyInt + if (PyNumber_Check(pyValue)) + { + if (m_strict && !PyBool_Check(pyValue)) + setValueError + ("Strict conversion error: object is not boolean type.",m_strict); + + // Note: this function handles Bool,Int,Long,Float + Py_ssize_t rValue = PyInt_AsSsize_t(pyValue); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + setValueError ("Error while converting boolean object.",m_strict); + } + if (rValue != 1 && rValue != 0) + { + setValueError ("Overflow error. Object can not be converted to boolean.",m_strict); + } + return (bool) rValue; + } + + if (m_strict) { + setValueError ("Strict conversion error: object is not numerical type.",m_strict); + return false; + } + + // convert iterables: the rule is the same as in the interpreter: + // empty sequence evaluates to False, anything else is True + if (PySequence_Check(pyValue)) + { + return PySequence_Size(pyValue)?true:false; + } + + // give up + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to boolean is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_Bool failed. " << msg << endl; +#endif + return false; +} + +/// string and objects that support .__str__() +/// TODO: check unicode objects +std::string +PyTypeInterface::PyValue_To_String(PyObject* pyValue) const +{ + // convert string + if (PyString_Check(pyValue)) + { + char *cstr = PyString_AS_STRING(pyValue); + if (!cstr) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting string object.",m_strict); + return std::string(); + } + return std::string(cstr); + } + // TODO: deal with unicode here (argh!) + + // in strict mode we will not try harder + if (m_strict) { + setValueError("Strict conversion error: object is not string.",m_strict); + return std::string(); + } + + // accept None as empty string + if (pyValue == Py_None) return std::string(); + + // convert list or tuple: empties are turned into empty strings conventionally + if (PyList_Check(pyValue) || PyTuple_Check(pyValue)) + { + if (!PySequence_Size(pyValue)) return std::string(); + PyObject* item = PySequence_GetItem(pyValue,0); + if (item) + { + std::string rValue = this->PyValue_To_String(item); + if (!m_error) { + Py_DECREF(item); + return rValue; + } else { + Py_CLEAR(item); + setValueError("Could not convert sequence element to string.",m_strict); + return std::string(); + } + } + } + + // convert any other object that has .__str__() or .__repr__() + PyObject* pyString = PyObject_Str(pyValue); + if (pyString && !PyErr_Occurred()) + { + std::string rValue = this->PyValue_To_String(pyString); + if (!m_error) { + Py_DECREF(pyString); + return rValue; + } else { + Py_CLEAR(pyString); + std::string msg = "Object " + this->PyValue_Get_TypeName(pyValue) +" can not be represented as string. "; + setValueError (msg,m_strict); + return std::string(); + } + } + + // give up + PyErr_Print(); PyErr_Clear(); + std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to string is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_String failed. " << msg << endl; +#endif + return std::string(); +} + +/* C Values to Py Values */ + + +PyObject* +PyTypeInterface::PyValue_From_CValue(const char* cValue) const +{ + // returns new reference +#ifdef _DEBUG + if (!cValue) { + std::string msg = "PyTypeInterface::PyValue_From_CValue: Null pointer encountered while converting from const char* ."; + cerr << msg << endl; + setValueError(msg,m_strict); + return NULL; + } +#endif + PyObject *pyValue = PyString_FromString(cValue); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting from char* or string.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_From_CValue: Interpreter failed to convert from const char*" << endl; +#endif + return NULL; + } + return pyValue; +} + +PyObject* +PyTypeInterface::PyValue_From_CValue(size_t cValue) const +{ + // returns new reference + PyObject *pyValue = PyInt_FromSsize_t((Py_ssize_t)cValue); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting from size_t.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_From_CValue: Interpreter failed to convert from size_t" << endl; +#endif + return NULL; + } + return pyValue; +} + +PyObject* +PyTypeInterface::PyValue_From_CValue(double cValue) const +{ + // returns new reference + PyObject *pyValue = PyFloat_FromDouble(cValue); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting from float or double.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_From_CValue: Interpreter failed to convert from float or double" << endl; +#endif + return NULL; + } + return pyValue; +} + +PyObject* +PyTypeInterface::PyValue_From_CValue(bool cValue) const +{ + // returns new reference + PyObject *pyValue = PyBool_FromLong((long)cValue); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting from bool.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_From_CValue: Interpreter failed to convert from bool" << endl; +#endif + return NULL; + } + return pyValue; +} + + +/* Sequence Types to C++ Types */ + +//convert Python list to C++ vector of strings +std::vector +PyTypeInterface::PyValue_To_StringVector (PyObject *pyList) const +{ + + std::vector Output; + std::string ListElement; + PyObject *pyString = NULL; + + if (PyList_Check(pyList)) { + + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyList); ++i) { + //Get next list item (Borrowed Reference) + pyString = PyList_GET_ITEM(pyList,i); + ListElement = (string) PyString_AsString(PyObject_Str(pyString)); + Output.push_back(ListElement); + } + return Output; + } +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_StringVector: Warning: Value is not list of strings." << endl; +#endif + + /// Assume a single value that can be casted as string + /// this allows to write e.g. Feature.label = 5.2 instead of ['5.2'] + Output.push_back(PyValue_To_String(pyList)); + if (m_error) { + std::string msg = "Value is not list of strings nor can be casted as string. "; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_StringVector failed. " << msg << endl; +#endif + } + return Output; +} + +//convert PyFeature.value (typically a list or numpy array) to C++ vector of floats +std::vector +PyTypeInterface::PyValue_To_FloatVector (PyObject *pyValue) const +{ + +#ifdef HAVE_NUMPY + // there are four types of values we may receive from a numpy process: + // * a python scalar, + // * an array scalar, (e.g. numpy.float32) + // * an array with nd = 0 (0D array) + // * an array with nd > 0 + + /// check for scalars + if (PyArray_CheckScalar(pyValue) || PyFloat_Check(pyValue)) { + + std::vector Output; + + // we rely on the behaviour the scalars are either floats + // or support the number protocol + // TODO: a potential optimisation is to handle them directly + Output.push_back(PyValue_To_Float(pyValue)); + return Output; + } + + /// numpy array + if (PyArray_CheckExact(pyValue)) + return PyArray_To_FloatVector(pyValue); + +#endif + + /// python list of floats (backward compatible) + if (PyList_Check(pyValue)) { + return PyList_To_FloatVector(pyValue); + } + + std::vector Output; + + /// finally assume a single value supporting the number protocol + /// this allows to write e.g. Feature.values = 5 instead of [5.00] + Output.push_back(PyValue_To_Float(pyValue)); + if (m_error) { + std::string msg = "Value is not list or array of floats nor can be casted as float. "; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_FloatVector failed." << msg << endl; +#endif + } + return Output; +} + +//convert a list of python floats +std::vector +PyTypeInterface::PyList_To_FloatVector (PyObject *inputList) const +{ + std::vector Output; + +#ifdef _DEBUG + // This is a low level function normally called from + // PyValue_To_FloatVector(). Checking for list is not required. + if (!PyList_Check(inputList)) { + std::string msg = "Value is not list."; + setValueError(msg,true); + cerr << "PyTypeInterface::PyList_To_FloatVector failed. " << msg << endl; + return Output; + } +#endif + + float ListElement; + PyObject *pyFloat = NULL; + PyObject **pyObjectArray = PySequence_Fast_ITEMS(inputList); + + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(inputList); ++i) { + + // pyFloat = PyList_GET_ITEM(inputList,i); + pyFloat = pyObjectArray[i]; + +#ifdef _DEBUG + if (!pyFloat) { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + cerr << "PyTypeInterface::PyList_To_FloatVector: Could not obtain list element: " + << i << " PyList_GetItem returned NULL! Skipping value." << endl; + continue; + } +#endif + + // ListElement = (float) PyFloat_AS_DOUBLE(pyFloat); + ListElement = PyValue_To_Float(pyFloat); + + +#ifdef _DEBUG_VALUES + cerr << "value: " << ListElement << endl; +#endif + Output.push_back(ListElement); + } + return Output; +} + +#ifdef HAVE_NUMPY +std::vector +PyTypeInterface::PyArray_To_FloatVector (PyObject *pyValue) const +{ + std::vector Output; + +#ifdef _DEBUG + // This is a low level function, normally called from + // PyValue_To_FloatVector(). Checking the array here is not required. + if (!PyArray_Check(pyValue)) { + std::string msg = "Object has no array interface."; + setValueError(msg,true); + cerr << "PyTypeInterface::PyArray_To_FloatVector failed. " << msg << endl; + return Output; + } +#endif + + PyArrayObject* pyArray = (PyArrayObject*) pyValue; + PyArray_Descr* descr = pyArray->descr; + + /// check raw data and descriptor pointers + if (pyArray->data == 0 || descr == 0) { + std::string msg = "NumPy array with NULL data or descriptor pointer encountered."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyArray_To_FloatVector failed. Error: " << msg << endl; +#endif + return Output; + } + + /// check dimensions + if (pyArray->nd != 1) { + std::string msg = "NumPy array must be a one dimensional vector."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyArray_To_FloatVector failed. Error: " << msg << " Dims: " << (int) pyArray->nd << endl; +#endif + return Output; + } + +#ifdef _DEBUG_VALUES + cerr << "PyTypeInterface::PyArray_To_FloatVector: Numpy array verified." << endl; +#endif + + /// check strides (useful if array is not continuous) + size_t strides = *((size_t*) pyArray->strides); + + /// convert the array + switch (descr->type_num) + { + case NPY_FLOAT : // dtype='float32' + return PyArray_Convert(pyArray->data,pyArray->dimensions[0],strides); + case NPY_DOUBLE : // dtype='float64' + return PyArray_Convert(pyArray->data,pyArray->dimensions[0],strides); + case NPY_INT : // dtype='int' + return PyArray_Convert(pyArray->data,pyArray->dimensions[0],strides); + case NPY_LONG : // dtype='long' + return PyArray_Convert(pyArray->data,pyArray->dimensions[0],strides); + default : + std::string msg = "Unsupported value type in NumPy array object."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyArray_To_FloatVector failed. Error: " << msg << endl; +#endif + return Output; + } +} +#endif + + +/// FeatureSet (an integer map of OutputLists) +Vamp::Plugin::FeatureSet +PyTypeInterface::PyValue_To_FeatureSet(PyObject* pyValue) const +{ + Vamp::Plugin::FeatureSet rFeatureSet; + + /// Convert PyFeatureSet + if (PyFeatureSet_CheckExact(pyValue)) { + + Py_ssize_t pyPos = 0; + PyObject *pyKey, *pyDictValue; // Borrowed References + int key; + // bool it_error = false; + + m_error = false; + while (PyDict_Next(pyValue, &pyPos, &pyKey, &pyDictValue)) + { + key = (int) PyInt_AS_LONG(pyKey); +#ifdef _DEBUG_VALUES + cerr << "key: '" << key << "' value: '" << PyValue_To_String(pyDictValue) << "' " << endl; +#endif + // DictValue -> Vamp::FeatureList + PyValue_To_rValue(pyDictValue,rFeatureSet[key]); + if (m_error) { + // it_error = true; + lastError() << " in output number: " << key; + } + } + // if (it_error) m_error = true; + if (!m_errorQueue.empty()) { + setValueError("Error while converting FeatureSet.",m_strict); + } + return rFeatureSet; + } + + /// Convert Python list (backward compatibility) + if (PyList_Check(pyValue)) { + + PyObject *pyFeatureList; // This will be borrowed reference + + //Parse Output List for each element (FeatureSet) + m_error = false; + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyValue); ++i) { + //Get i-th FeatureList (Borrowed Reference) + pyFeatureList = PyList_GET_ITEM(pyValue,i); + PyValue_To_rValue(pyFeatureList,rFeatureSet[i]); + if (m_error) { + lastError() << " in output number: " << i; + } + } + if (!m_errorQueue.empty()) m_error = true; + return rFeatureSet; + } + + /// accept no return values + if (pyValue == Py_None) return rFeatureSet; + + /// give up + std::string msg = "Unsupported return type. Expected list or vampy.FeatureSet(). "; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_FeatureSet failed. Error: " << msg << endl; +#endif + return rFeatureSet; +} + +Vamp::RealTime +PyTypeInterface::PyValue_To_RealTime(PyObject* pyValue) const +{ +// We accept integer sample counts (for backward compatibility) +// or PyRealTime objects and convert them to Vamp::RealTime + + if (PyRealTime_CheckExact(pyValue)) + { + /// just create a copy of the wrapped object + return Vamp::RealTime(*PyRealTime_AS_REALTIME(pyValue)); + } + + // assume integer sample count + long sampleCount = PyValue_To_Long(pyValue); + if (m_error) { + std::string msg = "Unexpected value passed as RealTime.\nMust be vampy.RealTime type or integer sample count."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_RealTime failed. " << msg << endl; +#endif + return Vamp::RealTime(); + } + +#ifdef _DEBUG_VALUES + Vamp::RealTime rt = + Vamp::RealTime::frame2RealTime(sampleCount,m_inputSampleRate ); + cerr << "RealTime: " << (long)sampleCount << ", ->" << rt.toString() << endl; + return rt; +#else + return Vamp::RealTime::frame2RealTime(sampleCount,m_inputSampleRate ); +#endif + +} + +Vamp::Plugin::OutputDescriptor::SampleType +PyTypeInterface::PyValue_To_SampleType(PyObject* pyValue) const +{ + /// convert simulated enum values + /// { OneSamplePerStep,FixedSampleRate,VariableSampleRate } + if (PyInt_CheckExact(pyValue)) { + long lst = PyInt_AS_LONG(pyValue); + if (lst<0 || lst>2) { + setValueError("Overflow error. SampleType has to be one of { OneSamplePerStep,FixedSampleRate,VariableSampleRate }\n(an integer in the range of 0..2) or a string value naming the type.",m_strict); + return Vamp::Plugin::OutputDescriptor::SampleType(); + } + return (Vamp::Plugin::OutputDescriptor::SampleType) lst; + } + + /// convert string (backward compatible) + if (PyString_CheckExact(pyValue)) { + Vamp::Plugin::OutputDescriptor::SampleType st; + st = (Vamp::Plugin::OutputDescriptor::SampleType) sampleKeys[PyValue_To_String(pyValue)]; + if (m_error) { + std::string msg = "Unexpected value passed as SampleType. Must be one of { OneSamplePerStep,FixedSampleRate,VariableSampleRate }\n(an integer in the range of 0..2) or a string value naming the type."; + setValueError(msg,m_strict); + return Vamp::Plugin::OutputDescriptor::SampleType(); + } + return st; + } + + /// give up + std::string msg = "Unsupported return type. Expected one of { OneSamplePerStep,FixedSampleRate,VariableSampleRate }\n(an integer in the range of 0..2) or a string value naming the type."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_SampleType failed. Error: " << msg << endl; +#endif + return Vamp::Plugin::OutputDescriptor::SampleType(); +} + +Vamp::Plugin::InputDomain +PyTypeInterface::PyValue_To_InputDomain(PyObject* pyValue) const +{ + /// convert simulated enum values { TimeDomain,FrequencyDomain } + if (PyInt_CheckExact(pyValue)) { + long lst = PyInt_AS_LONG(pyValue); + if (lst!=0 && lst!=1) { + setValueError("Overflow error. InputDomain has to be one of { TimeDomain,FrequencyDomain }\n(an integer in the range of 0..1) or a string value naming the type.",m_strict); + return Vamp::Plugin::InputDomain(); + } + return (Vamp::Plugin::InputDomain) lst; + } + + /// convert string (backward compatible) + if (PyString_CheckExact(pyValue)) { + Vamp::Plugin::InputDomain id; + id = (PyValue_To_String(pyValue) == "FrequencyDomain")?Vamp::Plugin::FrequencyDomain:Vamp::Plugin::TimeDomain; + if (m_error) + { + std::string msg = "Unexpected value passed as SampleType. Must be one of { TimeDomain,FrequencyDomain }\n(an integer in the range of 0..1) or a string value naming the type."; + setValueError(msg,m_strict); + return Vamp::Plugin::InputDomain(); + } + return id; + } + + /// give up + std::string msg = "Unsupported return type. Expected one of { TimeDomain,FrequencyDomain }\n(an integer in the range of 0..1) or a string value naming the type."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_InputDomain failed. Error: " << msg << endl; +#endif + return Vamp::Plugin::InputDomain(); +} + + +/// OutputDescriptor +void +PyTypeInterface::SetValue(Vamp::Plugin::OutputDescriptor& od, std::string& key, PyObject* pyValue) const +{ + switch (outKeys[key]) + { + case o::not_found: + setValueError("Unknown key in Vamp OutputDescriptor",m_strict); + cerr << "Unknown key in Vamp OutputDescriptor: " << key << endl; + break; + case o::identifier: + _convert(pyValue,od.identifier); + break; + case o::name: + _convert(pyValue,od.name); + break; + case o::description: + _convert(pyValue,od.description); + break; + case o::unit: + _convert(pyValue,od.unit); + break; + case o::hasFixedBinCount: + _convert(pyValue,od.hasFixedBinCount); + break; + case o::binCount: + _convert(pyValue,od.binCount); + break; + case o::binNames: + _convert(pyValue,od.binNames); + break; + case o::hasKnownExtents: + _convert(pyValue,od.hasKnownExtents); + break; + case o::minValue: + _convert(pyValue,od.minValue); + break; + case o::maxValue: + _convert(pyValue,od.maxValue); + break; + case o::isQuantized: + _convert(pyValue,od.isQuantized); + break; + case o::quantizeStep: + _convert(pyValue,od.quantizeStep); + break; + case o::sampleType: + _convert(pyValue,od.sampleType); + break; + case o::sampleRate: + _convert(pyValue,od.sampleRate); + break; + case o::hasDuration: + _convert(pyValue,od.hasDuration); + break; + default: + setValueError("Unknown key in Vamp OutputDescriptor",m_strict); + cerr << "Invalid key in Vamp OutputDescriptor: " << key << endl; + } +} + +/// ParameterDescriptor +void +PyTypeInterface::SetValue(Vamp::Plugin::ParameterDescriptor& pd, std::string& key, PyObject* pyValue) const +{ + switch (parmKeys[key]) + { + case p::not_found : + setValueError("Unknown key in Vamp ParameterDescriptor",m_strict); + cerr << "Unknown key in Vamp ParameterDescriptor: " << key << endl; + break; + case p::identifier: + _convert(pyValue,pd.identifier); + break; + case p::name: + _convert(pyValue,pd.name); + break; + case p::description: + _convert(pyValue,pd.description); + break; + case p::unit: + _convert(pyValue,pd.unit); + break; + case p::minValue: + _convert(pyValue,pd.minValue); + break; + case p::maxValue: + _convert(pyValue,pd.maxValue); + break; + case p::defaultValue: + _convert(pyValue,pd.defaultValue); + break; + case p::isQuantized: + _convert(pyValue,pd.isQuantized); + break; + case p::quantizeStep: + _convert(pyValue,pd.quantizeStep); + break; + default : + setValueError("Unknown key in Vamp ParameterDescriptor",m_strict); + cerr << "Invalid key in Vamp ParameterDescriptor: " << key << endl; + } +} + +/// Feature (it's like a Descriptor) +bool +PyTypeInterface::SetValue(Vamp::Plugin::Feature& feature, std::string& key, PyObject* pyValue) const +{ + bool found = true; + switch (ffKeys[key]) + { + case unknown : + setValueError("Unknown key in Vamp Feature",m_strict); + cerr << "Unknown key in Vamp Feature: " << key << endl; + found = false; + break; + case hasTimestamp: + _convert(pyValue,feature.hasTimestamp); + break; + case timestamp: + _convert(pyValue,feature.timestamp); + break; + case hasDuration: + _convert(pyValue,feature.hasDuration); + break; + case duration: + _convert(pyValue,feature.duration); + break; + case values: + _convert(pyValue,feature.values); + break; + case label: + _convert(pyValue,feature.label); + break; + default: + setValueError("Unknown key in Vamp Feature",m_strict); + found = false; + } + return found; +} + + +/* Error handling */ + +void +PyTypeInterface::setValueError (std::string message, bool strict) const +{ + m_error = true; + m_errorQueue.push(ValueError(message,strict)); +} + +/// return a reference to the last error or creates a new one. +PyTypeInterface::ValueError& +PyTypeInterface::lastError() const +{ + m_error = false; + if (!m_errorQueue.empty()) return m_errorQueue.back(); + else { + m_errorQueue.push(ValueError("Type conversion error.",m_strict)); + return m_errorQueue.back(); + } +} + +/// helper function to iterate over the error message queue: +/// pops the oldest item +PyTypeInterface::ValueError +PyTypeInterface::getError() const +{ + if (!m_errorQueue.empty()) { + PyTypeInterface::ValueError e = m_errorQueue.front(); + m_errorQueue.pop(); + if (m_errorQueue.empty()) m_error = false; + return e; + } + else { + m_error = false; + return PyTypeInterface::ValueError(); + } +} + +/* Utilities */ + +/// get the type name of an object +std::string +PyTypeInterface::PyValue_Get_TypeName(PyObject* pyValue) const +{ + PyObject *pyType = PyObject_Type(pyValue); + if (!pyType) + { + cerr << "Warning: Object type name could not be found." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + return std::string ("< unknown type >"); + } + PyObject *pyString = PyObject_Str(pyType); + if (!pyString) + { + cerr << "Warning: Object type name could not be found." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + Py_CLEAR(pyType); + return std::string ("< unknown type >"); + } + char *cstr = PyString_AS_STRING(pyString); + if (!cstr) + { + cerr << "Warning: Object type name could not be found." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + Py_DECREF(pyType); + Py_CLEAR(pyString); + return std::string("< unknown type >"); + } + Py_DECREF(pyType); + Py_DECREF(pyString); + return std::string(cstr); + +} + +bool +PyTypeInterface::initMaps() const +{ + + if (isMapInitialised) return true; + + outKeys["identifier"] = o::identifier; + outKeys["name"] = o::name; + outKeys["description"] = o::description; + outKeys["unit"] = o::unit; + outKeys["hasFixedBinCount"] = o::hasFixedBinCount; + outKeys["binCount"] = o::binCount; + outKeys["binNames"] = o::binNames; + outKeys["hasKnownExtents"] = o::hasKnownExtents; + outKeys["minValue"] = o::minValue; + outKeys["maxValue"] = o::maxValue; + outKeys["isQuantized"] = o::isQuantized; + outKeys["quantizeStep"] = o::quantizeStep; + outKeys["sampleType"] = o::sampleType; + outKeys["sampleRate"] = o::sampleRate; + outKeys["hasDuration"] = o::hasDuration; + + sampleKeys["OneSamplePerStep"] = OneSamplePerStep; + sampleKeys["FixedSampleRate"] = FixedSampleRate; + sampleKeys["VariableSampleRate"] = VariableSampleRate; + + ffKeys["hasTimestamp"] = hasTimestamp; + ffKeys["timestamp"] = timestamp; // this is the correct one + ffKeys["timeStamp"] = timestamp; // backward compatible + ffKeys["hasDuration"] = hasDuration; + ffKeys["duration"] = duration; + ffKeys["values"] = values; + ffKeys["label"] = label; + + parmKeys["identifier"] = p::identifier; + parmKeys["name"] = p::name; + parmKeys["description"] = p::description; + parmKeys["unit"] = p::unit; + parmKeys["minValue"] = p::minValue; + parmKeys["maxValue"] = p::maxValue; + parmKeys["defaultValue"] = p::defaultValue; + parmKeys["isQuantized"] = p::isQuantized; + parmKeys["quantizeStep"] = p::quantizeStep; + + isMapInitialised = true; + return true; +} diff -r 000000000000 -r 27bab3a16c9a PyTypeInterface.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyTypeInterface.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,661 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +/* +PyTypeInterface: Type safe conversion utilities between Python types +and basic C/C++ types and Vamp API types. +*/ + +#ifndef _PY_TYPE_INTERFACE_H_ +#define _PY_TYPE_INTERFACE_H_ +#include +#ifdef HAVE_NUMPY +#define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API +#define NO_IMPORT_ARRAY +#include "numpy/arrayobject.h" +#endif +#include "PyExtensionModule.h" +#include +#include +#include +#include +#include "vamp-sdk/Plugin.h" + +using std::cerr; +using std::endl; + +#ifdef HAVE_NUMPY +enum eArrayDataType { + dtype_float32 = (int) NPY_FLOAT, + dtype_complex64 = (int) NPY_CFLOAT + }; +#endif + +namespace o { +enum eOutDescriptors { + not_found, + identifier, + name, + description, + unit, + hasFixedBinCount, + binCount, + binNames, + hasKnownExtents, + minValue, + maxValue, + isQuantized, + quantizeStep, + sampleType, + sampleRate, + hasDuration, + endNode + }; +} + +namespace p { +enum eParmDescriptors { + not_found, + identifier, + name, + description, + unit, + minValue, + maxValue, + defaultValue, + isQuantized, + quantizeStep + }; +} + +enum eSampleTypes { + OneSamplePerStep, + FixedSampleRate, + VariableSampleRate + }; + +enum eFeatureFields { + unknown, + hasTimestamp, + timestamp, + hasDuration, + duration, + values, + label + }; + +/* C++ mapping of PyNone Type */ +struct NoneType {}; + +class PyTypeInterface +{ +public: + PyTypeInterface(); + ~PyTypeInterface(); + + // Data + class ValueError + { + public: + ValueError() {} + ValueError(std::string m, bool s) : message(m),strict(s) {} + std::string location; + std::string message; + bool strict; + std::string str() const { + return (location.empty()) ? message : message + "\nLocation: " + location;} + void print() const { cerr << str() << endl; } + template ValueError &operator<< (const V& v) + { + std::ostringstream ss; + ss << v; + location += ss.str(); + return *this; + } + }; + + // Utilities + void setStrictTypingFlag(bool b) {m_strict = b;} + ValueError getError() const; + std::string PyValue_Get_TypeName(PyObject*) const; + bool initMaps() const; + + // Basic type conversion: Python to C++ + float PyValue_To_Float(PyObject*) const; + size_t PyValue_To_Size_t(PyObject*) const; + bool PyValue_To_Bool(PyObject*) const; + std::string PyValue_To_String(PyObject*) const; + long PyValue_To_Long(PyObject*) const; + // int PyValue_To_Int(PyObject* pyValue) const; + + + // C++ to Python + PyObject *PyValue_From_CValue(const char*) const; + PyObject *PyValue_From_CValue(const std::string& x) const { return PyValue_From_CValue(x.c_str()); } + PyObject *PyValue_From_CValue(size_t) const; + PyObject *PyValue_From_CValue(double) const; + PyObject *PyValue_From_CValue(float x) const { return PyValue_From_CValue((double)x); } + PyObject *PyValue_From_CValue(bool) const; + + // Sequence types + std::vector PyValue_To_StringVector (PyObject*) const; + std::vector PyValue_To_FloatVector (PyObject*) const; + std::vector PyList_To_FloatVector (PyObject*) const; + + // Input buffers to Python + PyObject* InputBuffers_As_PythonLists(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype); + PyObject* InputBuffers_As_SharedMemoryList(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize); + + // Numpy types +#ifdef HAVE_NUMPY + std::vector PyArray_To_FloatVector (PyObject *pyValue) const; + PyObject* InputBuffers_As_NumpyArray(const float *const *inputBuffers, const size_t&, const size_t&, const Vamp::Plugin::InputDomain& dtype); +#endif + + + + +/* Template functions */ + + + /// Common wrappers to set values in Vamp API structs. (to be used in template functions) + void SetValue(Vamp::Plugin::OutputDescriptor& od, std::string& key, PyObject* pyValue) const; + void SetValue(Vamp::Plugin::ParameterDescriptor& od, std::string& key, PyObject* pyValue) const; + bool SetValue(Vamp::Plugin::Feature& od, std::string& key, PyObject* pyValue) const; + PyObject* GetDescriptor_As_Dict(PyObject* pyValue) const + { + if PyFeature_CheckExact(pyValue) return PyFeature_AS_DICT(pyValue); + if PyOutputDescriptor_CheckExact(pyValue) return PyOutputDescriptor_AS_DICT(pyValue); + if PyParameterDescriptor_CheckExact(pyValue) return PyParameterDescriptor_AS_DICT(pyValue); + return NULL; + } + + //returns e.g. Vamp::Plugin::OutputDescriptor or Vamp::Plugin::Feature + template + RET PyValue_To_VampDescriptor(PyObject* pyValue) const + { + PyObject* pyDict; + + // Descriptors encoded as dicts + pyDict = GetDescriptor_As_Dict(pyValue); + if (!pyDict) pyDict = pyValue; + + // TODO: support full mapping protocol as fallback. + if (!PyDict_Check(pyDict)) { + setValueError("Error while converting descriptor or feature object.\nThe value is neither a dictionary nor a Vamp Feature or Descriptor type.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_VampDescriptor failed. Error: Unexpected return type." << endl; +#endif + return RET(); + } + + Py_ssize_t pyPos = 0; + PyObject *pyKey, *pyDictValue; + initMaps(); + int errors = 0; + m_error = false; + RET rd; + + //Python Dictionary Iterator: + while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyDictValue)) + { + std::string key = PyValue_To_String(pyKey); +#ifdef _DEBUG_VALUES + cerr << "key: '" << key << "' value: '" << PyValue_To_String(pyDictValue) << "' " << endl; +#endif + SetValue(rd,key,pyDictValue); + if (m_error) { + errors++; + lastError() << "attribute '" << key << "'";// << " of " << getDescriptorId(rd); + } + } + if (errors) { + lastError() << " of " << getDescriptorId(rd); + m_error = true; +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_VampDescriptor: Warning: Value error in descriptor." << endl; +#endif + } + return rd; + } + + /// Convert a sequence (tipically list) of PySomething to + /// OutputList,ParameterList or FeatureList + /// + template + RET PyValue_To_VampList(PyObject* pyValue) const + { + RET list; // e.g. Vamp::Plugin::OutputList + ELEM element; // e.g. Vamp::Plugin::OutputDescriptor + + /// convert lists (ParameterList, OutputList, FeatureList) + if (PyList_Check(pyValue)) { + PyObject *pyDict; //This reference will be borrowed + m_error = false; int errors = 0; + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyValue); ++i) { + //Get i-th Vamp output descriptor (Borrowed Reference) + pyDict = PyList_GET_ITEM(pyValue,i); + element = PyValue_To_VampDescriptor(pyDict); + if (m_error) errors++; + // Check for empty Feature/Descriptor as before? + list.push_back(element); + } + if (errors) m_error=true; + return list; + } + + /// convert other types implementing the sequence protocol + if (PySequence_Check(pyValue)) { + PyObject *pySequence = PySequence_Fast(pyValue,"Returned value can not be converted to list or tuple."); + PyObject **pyElements = PySequence_Fast_ITEMS(pySequence); + m_error = false; int errors = 0; + for (Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(pySequence); ++i) + { + element = PyValue_To_VampDescriptor(pyElements[i]); + if (m_error) errors++; + list.push_back(element); + } + if (errors) m_error=true; + Py_XDECREF(pySequence); + return list; + } + + // accept None as an empty list + if (pyValue == Py_None) return list; + + // in strict mode, returning a single value is not allowed + if (m_strict) { + setValueError("Strict conversion error: object is not list or iterable sequence.",m_strict); + return list; + } + + /// try to insert single, non-iterable values. i.e. feature <- [feature] + element = PyValue_To_VampDescriptor(pyValue); + if (m_error) { + setValueError("Could not insert returned value to Vamp List.",m_strict); + return list; + } + list.push_back(element); + return list; + +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_VampList failed. Expected iterable return type." << endl; +#endif + + } + + /// Convert DTYPE type 1D NumpyArray to std::vector + template + std::vector PyArray_Convert(char* raw_data_ptr, long length, size_t strides) const + { + std::vector rValue; + + /// check if the array is continuous, if not use strides info + if (sizeof(DTYPE)!=strides) { +#ifdef _DEBUG_VALUES + cerr << "Warning: discontinuous numpy array. Strides: " << strides << " bytes. sizeof(dtype): " << sizeof(DTYPE) << endl; +#endif + char* data = (char*) raw_data_ptr; + for (long i = 0; i PyArray0D_Convert(PyArrayInterface *ai) const + { + std::vector rValue; + if ((ai->typekind) == *"f") + rValue.push_back((float)*(double*)(ai->data)); + else { + setValueError("Unsupported NumPy data type.",m_strict); + return rValue; + } +#ifdef _DEBUG_VALUES + cerr << "value: " << rValue[0] << endl; +#endif + return rValue; + } + + //Vamp specific types + Vamp::Plugin::FeatureSet PyValue_To_FeatureSet(PyObject*) const; + inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::FeatureSet &r) const + { r = this->PyValue_To_FeatureSet(pyValue); } + + Vamp::RealTime PyValue_To_RealTime(PyObject*) const; + inline void PyValue_To_rValue(PyObject *pyValue, Vamp::RealTime &r) const + { r = this->PyValue_To_RealTime(pyValue); } + + Vamp::Plugin::OutputDescriptor::SampleType PyValue_To_SampleType(PyObject*) const; + + Vamp::Plugin::InputDomain PyValue_To_InputDomain(PyObject*) const; + inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::InputDomain &r) const + { r = this->PyValue_To_InputDomain(pyValue); } + + + /* Overloaded PyValue_To_rValue() to support generic functions */ + inline void PyValue_To_rValue(PyObject *pyValue, float &defValue) const + { float tmp = this->PyValue_To_Float(pyValue); + if(!m_error) defValue = tmp; } + inline void PyValue_To_rValue(PyObject *pyValue, size_t &defValue) const + { size_t tmp = this->PyValue_To_Size_t(pyValue); + if(!m_error) defValue = tmp; } + inline void PyValue_To_rValue(PyObject *pyValue, bool &defValue) const + { bool tmp = this->PyValue_To_Bool(pyValue); + if(!m_error) defValue = tmp; } + inline void PyValue_To_rValue(PyObject *pyValue, std::string &defValue) const + { std::string tmp = this->PyValue_To_String(pyValue); + if(!m_error) defValue = tmp; } + /*used by templates where we expect no return value, if there is one it will be ignored*/ + inline void PyValue_To_rValue(PyObject *pyValue, NoneType &defValue) const + { if (m_strict && pyValue != Py_None) + setValueError("Strict conversion error: Expected 'None' type.",m_strict); + } + + /* convert sequence types to Vamp List types */ + inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::OutputList &r) const + { r = this->PyValue_To_VampList(pyValue); } + inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::ParameterList &r) const + { r = this->PyValue_To_VampList(pyValue); } + inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::FeatureList &r) const + { r = this->PyValue_To_VampList(pyValue); } + + /// this is only needed for RealTime->Frame conversion + void setInputSampleRate(float inputSampleRate) + { m_inputSampleRate = (unsigned int) inputSampleRate; } + +private: + bool m_strict; + mutable bool m_error; + mutable std::queue m_errorQueue; + unsigned int m_inputSampleRate; + + void setValueError(std::string,bool) const; + ValueError& lastError() const; + + /* Overloaded _convert(), bypasses error checking to avoid doing it twice in internals. */ + inline void _convert(PyObject *pyValue,float &r) const + { r = PyValue_To_Float(pyValue); } + inline void _convert(PyObject *pyValue,size_t &r) const + { r = PyValue_To_Size_t(pyValue); } + inline void _convert(PyObject *pyValue,bool &r) const + { r = PyValue_To_Bool(pyValue); } + inline void _convert(PyObject *pyValue,std::string &r) const + { r = PyValue_To_String(pyValue); } + inline void _convert(PyObject *pyValue,std::vector &r) const + { r = PyValue_To_StringVector(pyValue); } + inline void _convert(PyObject *pyValue,std::vector &r) const + { r = PyValue_To_FloatVector(pyValue); } + inline void _convert(PyObject *pyValue,Vamp::RealTime &r) const + { r = PyValue_To_RealTime(pyValue); } + inline void _convert(PyObject *pyValue,Vamp::Plugin::OutputDescriptor::SampleType &r) const + { r = PyValue_To_SampleType(pyValue); } + // inline void _convert(PyObject *pyValue,Vamp::Plugin::InputDomain &r) const + // { r = PyValue_To_InputDomain(pyValue); } + + + /* Identify descriptors for error reporting */ + inline std::string getDescriptorId(Vamp::Plugin::OutputDescriptor d) const + {return std::string("Output Descriptor '") + d.identifier +"' ";} + inline std::string getDescriptorId(Vamp::Plugin::ParameterDescriptor d) const + {return std::string("Parameter Descriptor '") + d.identifier +"' ";} + inline std::string getDescriptorId(Vamp::Plugin::Feature f) const + {return std::string("Feature (") + f.label + ")"; } + +public: + const bool& error; + +}; + +/* Convert Sample Buffers to Python */ + +/// passing the sample buffers as buitin python lists +/// Optimization: using fast sequence protocol +inline PyObject* +PyTypeInterface::InputBuffers_As_PythonLists(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype) +{ + //create a list of lists (new references) + PyObject *pyChannelList = PyList_New((Py_ssize_t) channels); + + // Pack samples into a Python List Object + // pyFloat/pyComplex types will always be new references, + // they will be freed when the lists are deallocated. + + PyObject **pyChannelListArray = PySequence_Fast_ITEMS(pyChannelList); + for (size_t i=0; i < channels; ++i) { + + PyObject *pySampleList = PyList_New((Py_ssize_t) blockSize); + PyObject **pySampleListArray = PySequence_Fast_ITEMS(pySampleList); + + // Note: passing a complex list crashes the C-style plugin + // when it tries to convert it to a numpy array directly. + // This plugin will be obsolete, but we have to find a way + // to prevent such crash. + + switch (Vamp::Plugin::TimeDomain) //(dtype) + { + case Vamp::Plugin::TimeDomain : + + for (size_t j = 0; j < blockSize; ++j) { + PyObject *pyFloat=PyFloat_FromDouble( + (double) inputBuffers[i][j]); + pySampleListArray[j] = pyFloat; + } + break; + + case Vamp::Plugin::FrequencyDomain : + + size_t k = 0; + for (size_t j = 0; j < blockSize/2; ++j) { + PyObject *pyComplex=PyComplex_FromDoubles( + (double) inputBuffers[i][k], + (double) inputBuffers[i][k+1]); + pySampleListArray[j] = pyComplex; + k += 2; + } + break; + + } + pyChannelListArray[i] = pySampleList; + } + return pyChannelList; +} + +/// numpy buffer interface: passing the sample buffers as shared memory buffers +/// Optimization: using sequence protocol for creating the buffer list +inline PyObject* +PyTypeInterface::InputBuffers_As_SharedMemoryList(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize) +{ + //create a list of buffers (returns new references) + PyObject *pyChannelList = PyList_New((Py_ssize_t) channels); + PyObject **pyChannelListArray = PySequence_Fast_ITEMS(pyChannelList); + + // Expose memory using the Buffer Interface. + // This will pass a pointer which can be recasted in Python code + // as complex or float array using Numpy's frombuffer() method + // (this will not copy values just keep the starting adresses + // for each channel in a list) + Py_ssize_t bufferSize = (Py_ssize_t) sizeof(float) * blockSize; + + for (size_t i=0; i < channels; ++i) { + PyObject *pyBuffer = PyBuffer_FromMemory + ((void *) (float *) inputBuffers[i],bufferSize); + pyChannelListArray[i] = pyBuffer; + } + return pyChannelList; +} + + +/// numpy array interface: passing the sample buffers as 2D numpy array +/// Optimization: using array API (needs numpy headers) +#ifdef HAVE_NUMPY +inline PyObject* +PyTypeInterface::InputBuffers_As_NumpyArray(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype) +{ +/* +NOTE: We create a list of 1D Numpy arrays for each channel instead +of a matrix, because the address space of inputBuffers doesn't seem +to be continuous. Although the array strides could be calculated for +2 channels (i.e. inputBuffers[1] - inputBuffers[0]) i'm not sure +if this can be trusted, especially for more than 2 channels. + + cerr << "First channel: " << inputBuffers[0][0] << " address: " << inputBuffers[0] << endl; + if (channels == 2) + cerr << "Second channel: " << inputBuffers[1][0] << " address: " << inputBuffers[1] << endl; + +*/ + + // create a list of arrays (returns new references) + PyObject *pyChannelList = PyList_New((Py_ssize_t) channels); + PyObject **pyChannelListArray = PySequence_Fast_ITEMS(pyChannelList); + + // Expose memory using the Numpy Array Interface. + // This will wrap an array objects around the data. + // (will not copy values just steal the starting adresses) + + int arraySize, typenum; + + switch (dtype) + { + case Vamp::Plugin::TimeDomain : + typenum = dtype_float32; //NPY_FLOAT; + arraySize = (int) blockSize; + break; + + case Vamp::Plugin::FrequencyDomain : + typenum = dtype_complex64; //NPY_CFLOAT; + arraySize = (int) blockSize / 2; + break; + + default : + cerr << "PyTypeInterface::InputBuffers_As_NumpyArray: Error: Unsupported numpy array data type." << endl; + return pyChannelList; + } + + // size for each dimension + npy_intp ndims[1]={arraySize}; + + for (size_t i=0; i < channels; ++i) { + PyObject *pyChannelArray = + //args: (dimensions, size in each dim, type kind, pointer to continuous array) + PyArray_SimpleNewFromData(1, ndims, typenum, (void*) inputBuffers[i]); + // make it read-only: set all flags to false except NPY_C_CONTIGUOUS + ((PyArrayObject*)pyChannelArray)->flags = NPY_C_CONTIGUOUS; + pyChannelListArray[i] = pyChannelArray; + } + return pyChannelList; +} +#endif + + + +#ifdef NUMPY_REFERENCE +/// This should be all we need to compile without direct dependency, +/// but we don't do that. (it may not work on some platforms) +typedef struct { + int two; /* contains the integer 2 -- simple sanity check */ + int nd; /* number of dimensions */ + char typekind; /* kind in array --- character code of typestr */ + int itemsize; /* size of each element */ + int flags; /* flags indicating how the data should be interpreted */ + /* must set ARR_HAS_DESCR bit to validate descr */ + Py_intptr_t *shape; /* A length-nd array of shape information */ + Py_intptr_t *strides; /* A length-nd array of stride information */ + void *data; /* A pointer to the first element of the array */ + PyObject *descr; /* NULL or data-description (same as descr key */ + /* of __array_interface__) -- must set ARR_HAS_DESCR */ + /* flag or this will be ignored. */ +} PyArrayInterface; + +typedef struct PyArrayObject { + PyObject_HEAD + char *data; /* pointer to raw data buffer */ + int nd; /* number of dimensions, also called ndim */ + npy_intp *dimensions; /* size in each dimension */ + npy_intp *strides; /* bytes to jump to get to the + next element in each dimension */ + PyObject *base; /* This object should be decref'd + upon deletion of array */ + /* For views it points to the original array */ + /* For creation from buffer object it points + to an object that shold be decref'd on + deletion */ + /* For UPDATEIFCOPY flag this is an array + to-be-updated upon deletion of this one */ + PyArray_Descr *descr; /* Pointer to type structure */ + int flags; /* Flags describing array -- see below*/ + PyObject *weakreflist; /* For weakreferences */ +} PyArrayObject; + +typedef struct _PyArray_Descr { + PyObject_HEAD + PyTypeObject *typeobj; /* the type object representing an + instance of this type -- should not + be two type_numbers with the same type + object. */ + char kind; /* kind for this type */ + char type; /* unique-character representing this type */ + char byteorder; /* '>' (big), '<' (little), '|' + (not-applicable), or '=' (native). */ + char hasobject; /* non-zero if it has object arrays + in fields */ + int type_num; /* number representing this type */ + int elsize; /* element size for this type */ + int alignment; /* alignment needed for this type */ + struct _arr_descr \ + *subarray; /* Non-NULL if this type is + is an array (C-contiguous) + of some other type + */ + PyObject *fields; /* The fields dictionary for this type */ + /* For statically defined descr this + is always Py_None */ + + PyObject *names; /* An ordered tuple of field names or NULL + if no fields are defined */ + + PyArray_ArrFuncs *f; /* a table of functions specific for each + basic data descriptor */ +} PyArray_Descr; + +enum NPY_TYPES { NPY_BOOL=0, + NPY_BYTE, NPY_UBYTE, + NPY_SHORT, NPY_USHORT, + NPY_INT, NPY_UINT, + NPY_LONG, NPY_ULONG, + NPY_LONGLONG, NPY_ULONGLONG, + NPY_FLOAT, NPY_DOUBLE, NPY_LONGDOUBLE, + NPY_CFLOAT, NPY_CDOUBLE, NPY_CLONGDOUBLE, + NPY_OBJECT=17, + NPY_STRING, NPY_UNICODE, + NPY_VOID, + NPY_NTYPES, + NPY_NOTYPE, + NPY_CHAR, /* special flag */ + NPY_USERDEF=256 /* leave room for characters */ +}; +#endif /*NUMPY_REFERENCE*/ +#endif diff -r 000000000000 -r 27bab3a16c9a README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,90 @@ + + * VamPy is an API wrapper for Vamp. It allows for writing Vamp + plugins in Python with or without Numpy support. + +WHAT IS IT FOR? + + This wrapper is for writing Vamp plugins in Python which + can do the same as a native C++ plugin, plus a lot more if + you're using advanced Python modules. + + This may be an easier way to get into Vamp development. + You can use it for prototyping your plugin before writing + it in C++. + + +WHY PYTHON? + + Python is a general purpose high level scripting language. + It is interpreted, so you don't need to compile your plugins. + It has very high level libraries. e.g. you can stream audio from your VamPy plugin (it works for me...) + Supports functional programming. + +UPDATE IN THIS VERSION: + + * Two-way Numpy Support + * Embedded extension module exposing Vamp defined names + e.g. ParameterDescriptor. This allows easier porting to C++. + * Support RealTime time stamps + * Support byte compiled Python scripts (.pyc) + * Environment variables: VAMPY_COMPILED, VAMPY_EXTPATH + * Flags to control type conversion and error reporting for development + * Flexible type inference to take advantage of Python's loose typing + * Full error checking for all Python/C API calls + * Various optimisations and speed-ups + +PREVIOUS VAMPY README: + +HOW DOES IT WORK: + + (1) Make sure you have Python installed. + (2) Compile the C++ source or ask me for a binary (MacOS). + (3) Copy the library in your Vamp plugin directory like any other Vamp plugins: + eg. /Library/Audio/Plug-Ins/Vamp + (4) Write some python plugins and copy them to the same place. + (5) Each plugin must contain a single class with the same name as your script file. + e.g. PyZeroCrossing.py -> calss PyZeroCrossing + -Scripts with syntax errors in them are ignored. + -Scripts not having the same class as the filename are ignored. (Python is case sensitive!) + -Other unknown scripts are likely to cause a crash. (Don't put other python scripts in your Vamp directory.) + + +COMPILING AND LINKING: + + (1) make sure Python.h is included wherever it is on your machine + (2) the plugin needs to be linked against the Python binary: e.g.: ld -lpython2.5 + + example on on MacOSX: + g++ -I../vamp-plugin-sdk -O2 -Wall -I/usr/include/python2.5 -c -o PyPlugin.o PyPlugin.cpp + g++ -I../vamp-plugin-sdk -O2 -Wall -I/usr/include/python2.5 -c -o PyPlugScanner.o PyPlugScanner.cpp + g++ -I../vamp-plugin-sdk -O2 -Wall -I/usr/include/python2.5 -c -o pyvamp-main.o pyvamp-main.cpp + g++ -I../vamp-plugin-sdk -O2 -Wall -I/usr/include/python2.5 -c -o Mutex.o Mutex.cpp + g++ -shared PyPlugin.o PyPlugScanner.o pyvamp-main.o Mutex.o -o vampy.dylib -L../vamp-plugin-sdk/vamp-sdk -lvamp-sdk -dynamiclib -lpython2.5 -lpthread + + + (3) There is a Makefile that compiles this plugin by default. + + +LIMITATIONS: + + This is mainly a proof of concept. The implementation is not the most efficient, but using NumPy it's very reasonable. + Only tested on MacOSX and Linux (by Chris), but in theory should work on other platforms with small fixes. + + Error checking is not yet fully complete. + You better not make a mistake in your Python script, although in most cases you can see what the problem is + if you start the host (e.g. Sonic Visualiser) from a command line interface. + The wrapper plugin is quite verbose and outputs error messages. + + +TODO: * needs more complete error checking (almost done) + * needs correct implementation of Python threading (done) + * more efficient data conversion using the buffering interface or ctypes (done) + * Vamp 'programs' not implemented + * support multiple classes per script in scanner + * ensure proper cleanup, (host does a good job though) + +HISTORY: + added support for NumPy arrays in processN() + framecount is now passed also to legacy process() and fixed resulting bugs in the PyZeroCrossing plugin + added two examples which use Frequency Domain input in processN() + diff -r 000000000000 -r 27bab3a16c9a VamPy.vcproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VamPy.vcproj Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r 27bab3a16c9a vamp-plugin.map --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vamp-plugin.map Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,4 @@ +{ + global: vampGetPluginDescriptor; + local: *; +}; diff -r 000000000000 -r 27bab3a16c9a vampy-main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vampy-main.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,256 @@ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include + +#ifdef HAVE_NUMPY +#define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API +#include "numpy/arrayobject.h" +#endif + +#include "vamp/vamp.h" +#include "vamp-sdk/PluginAdapter.h" +#include "PyPlugScanner.h" +#include "PyPlugin.h" +#include "PyExtensionModule.h" +#include "PyExtensionManager.h" + + +#ifdef _WIN32 +#define pathsep ('\\') +#include +#include +#else +#define pathsep ('/') +#include +#include +#endif + +using std::cerr; +using std::endl; +using std::string; +using std::vector; + +static int adinstcount; +static int totinstcount; + +class PyPluginAdapter : public Vamp::PluginAdapterBase +{ +public: + PyPluginAdapter(std::string pyPlugId, PyObject* pyClass) : + PluginAdapterBase(), + m_plug(pyPlugId), + m_pyClass(pyClass), + m_failed(false) + { + cerr << "PyPluginAdapter:ctor:"<< adinstcount << ": " << m_plug << endl; + adinstcount++; + } + + ~PyPluginAdapter() + { + } + + bool failed() { return m_failed; } + std::string getPlugKey() { return m_plug; } + +protected: + Vamp::Plugin *createPlugin(float inputSampleRate) + { + try { + PyPlugin *plugin = new PyPlugin(m_plug, inputSampleRate, m_pyClass, totinstcount); + return plugin; + } catch (...) { + cerr << "PyPluginAdapter::createPlugin: Failed to construct PyPlugin" << endl; + // any plugin with syntax errors will fail to construct + m_failed = true; + return 0; + } + } + + std::string m_plug; + PyObject *m_pyClass; + bool m_failed; +}; + +static void array_API_initialiser() +{ +/// numpy C-API requirement +#ifdef HAVE_NUMPY + import_array(); + if(NPY_VERSION != PyArray_GetNDArrayCVersion()) + cerr << "Warning: Numpy ABI version mismatch. (Build version: " + << NPY_VERSION << " Runtime version: " << PyArray_GetNDArrayCVersion() << ")" << endl; +#endif +} + + +static std::vector adapters; +static bool haveScannedPlugins = false; + +static bool tryPreload(string name) +{ +#ifdef _WIN32 + void *lib = LoadLibrary(name.c_str()); + if (!lib) { + return false; + } +#else + void *lib = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (!lib) { + return false; + } +#endif + return true; +} + +static bool preloadPython() +{ +#ifdef _WIN32 + // this doesn't seem to be necessary at all on Windows + return true; +#endif + + string pyver = Py_GetVersion(); + int dots = 2; + string shortver; + for (size_t i = 0; i < pyver.length(); ++i) { + if (pyver[i] == '.') { + if (--dots == 0) { + shortver = pyver.substr(0, i); + break; + } + } + } + cerr << "Short version: " << shortver << endl; + // this is useful to find out where the loaded library might be loaded from + cerr << "Python exec prefix: " << Py_GetExecPrefix() << endl; + + vector pfxs; + pfxs.push_back(""); + pfxs.push_back(string(Py_GetExecPrefix()) + "/lib/"); + pfxs.push_back(string(Py_GetExecPrefix()) + "/"); + pfxs.push_back("/usr/lib/"); + pfxs.push_back("/usr/local/lib/"); + char buffer[5]; + + // hahaha! grossness is like a brother to us +#ifdef __APPLE__ + for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) { + for (int minor = 8; minor >= 0; --minor) { + sprintf(buffer, "%d", minor); + if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib." + buffer)) return true; + } + if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib")) return true; + if (tryPreload(pfxs[pfxidx] + string("libpython.dylib"))) return true; + } +#else + for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) { + for (int minor = 8; minor >= 0; --minor) { + sprintf(buffer, "%d", minor); + if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so." + buffer)) return true; + } + if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so")) return true; + if (tryPreload(pfxs[pfxidx] + string("libpython.so"))) return true; + } +#endif + + return false; +} + + +static PyExtensionManager pyExtensionManager; + +const VampPluginDescriptor +*vampGetPluginDescriptor(unsigned int version,unsigned int index) +{ + if (version < 1) return 0; + + int isPythonInitialized = Py_IsInitialized(); + cerr << "# isPythonInitialized: " << isPythonInitialized << endl; + cerr << "# haveScannedPlugins: " << haveScannedPlugins << endl; + + if (!haveScannedPlugins) { + + if (!isPythonInitialized){ + + if (!preloadPython()) + cerr << "Warning: Could not preload Python. Dynamic loading in scripts will fail." << endl; + if (PyImport_AppendInittab("vampy",initvampy) != 0) + cerr << "Warning: Extension module could not be added to module inittab." << endl; + Py_Initialize(); + initvampy(); +#ifdef _DEBUG + cerr << "# isPythonInitialized after initialize: " << Py_IsInitialized() << endl; +#endif + } + + vector pyPlugs; + vector pyPath; + vector pyClasses; + static PyPlugScanner *scanner; + + //Scanning Plugins + cerr << "Scanning Vampy Plugins" << endl; + scanner = PyPlugScanner::getInstance(); + + // added env. varable support VAMPY_EXTPATH + pyPath=scanner->getAllValidPath(); + scanner->setPath(pyPath); + + // added env. variable support: + // VAMPY_COMPILED=1 to recognise .pyc files (default is 1) + pyPlugs = scanner->getPyPlugs(); + + cerr << "Found " << pyPlugs.size() << " Scripts." << endl; + //TODO: should this support multiple classes per script (?) + pyClasses = scanner->getPyClasses(); + cerr << "Found " << pyClasses.size() << " Classes." << endl; + + for (size_t i = 0; i < pyPlugs.size(); ++i) { + adapters.push_back( new PyPluginAdapter(pyPlugs[i],pyClasses[i])); + } + pyExtensionManager.setPlugModuleNames(pyPlugs); + pyExtensionManager.initExtension(); + array_API_initialiser(); + haveScannedPlugins=true; + } + +#ifdef _DEBUG + cerr << "Accessing adapter index: " << index << " (adapters: " << adapters.size() << ")" << endl; +#endif + + if (indexgetDescriptor(); + + if (adapters[index]->failed()) { + cerr << "\nERROR: [in vampGetPluginDescriptor] Removing adapter of: \n'" + << adapters[index]->getPlugKey() << "'\n" + << "The plugin has failed to construct. Hint: Check __init__() function." << endl; + pyExtensionManager.deleteModuleName(adapters[index]->getPlugKey()); + delete adapters[index]; + adapters.erase(adapters.begin()+index); + return 0; + } + + return tmp; + + } else return 0; +} + + + + + + + +