fazekasgy@52: '''PySpectralCentroid.py - Example plugin demonstrates fazekasgy@52: how to write a C style plugin using VamPy in pure Python. fazekasgy@52: This plugin also introduces the use of the builtin vampy fazekasgy@52: extension module. fazekasgy@52: fazekasgy@52: The plugin has frequency domain input and is using the Chris@69: legacy interface: the FFT output is passed as a list fazekasgy@52: of complex numbers. fazekasgy@52: fazekasgy@52: Outputs: fazekasgy@52: 1) Spectral centroid fazekasgy@52: fazekasgy@52: Note: This is not the adviced way of writing Vampy plugins now, fazekasgy@52: since the interfaces provided for Numpy are at least 5 times fazekasgy@52: faster. However, this is still a nice and easy to understand fazekasgy@52: example, which also shows how can one write a reasonable fazekasgy@52: plugin without having Numpy installed. fazekasgy@52: fazekasgy@52: Warning: Earlier versions of this plugin are now obsolete. fazekasgy@52: (They were using the legacy interface of Vampy 1 which fazekasgy@52: did not distinquish between time and frequency domain inputs.) fazekasgy@52: fazekasgy@52: Centre for Digital Music, Queen Mary University of London. fazekasgy@52: Copyright (C) 2009 Gyorgy Fazekas, QMUL. (See Vamp sources fazekasgy@52: for licence information.) fazekasgy@52: fazekasgy@52: ''' fazekasgy@52: fazekasgy@52: # import the names we use from vampy fazekasgy@52: from vampy import Feature,FeatureSet,ParameterDescriptor fazekasgy@52: from vampy import OutputDescriptor,FrequencyDomain,OneSamplePerStep fazekasgy@52: fazekasgy@52: from math import sqrt fazekasgy@52: fazekasgy@52: class PySpectralCentroid: fazekasgy@52: fazekasgy@52: def __init__(self,inputSampleRate): fazekasgy@52: self.m_stepSize = 0 fazekasgy@52: self.m_blockSize = 0 fazekasgy@52: self.m_channels = 0 fazekasgy@52: self.previousSample = 0.0 fazekasgy@52: self.m_inputSampleRate = inputSampleRate Chris@68: self.threshold = 0.05 fazekasgy@52: fazekasgy@52: def initialise(self,channels,stepSize,blockSize): fazekasgy@52: self.m_channels = channels fazekasgy@52: self.m_stepSize = stepSize fazekasgy@52: self.m_blockSize = blockSize fazekasgy@52: return True fazekasgy@52: fazekasgy@52: def getMaker(self): fazekasgy@52: return 'Vampy Example Plugins' fazekasgy@52: fazekasgy@52: def getName(self): fazekasgy@52: return 'Spectral Centroid (using legacy process interface)' fazekasgy@52: fazekasgy@52: def getIdentifier(self): fazekasgy@52: return 'vampy-sc3' Chris@69: Chris@69: def getDescription(self): Chris@69: return 'Calculate the linear frequency centroid of the short-time Fourier spectrum' Chris@69: Chris@69: def getCopyright(self): Chris@69: return 'Plugin By George Fazekas. Freely redistributable example plugin (BSD license)' Chris@69: fazekasgy@52: def getMaxChannelCount(self): fazekasgy@52: return 1 fazekasgy@52: fazekasgy@52: def getInputDomain(self): fazekasgy@52: return FrequencyDomain fazekasgy@52: fazekasgy@52: def getOutputDescriptors(self): fazekasgy@52: fazekasgy@52: cod = OutputDescriptor() fazekasgy@52: cod.identifier='vampy-sc3' fazekasgy@52: cod.name='Spectral Centroid' fazekasgy@52: cod.description='Spectral Centroid (Brightness)' fazekasgy@52: cod.unit='' fazekasgy@52: cod.hasFixedBinCount=True fazekasgy@52: cod.binCount=1 fazekasgy@52: cod.hasKnownExtents=False fazekasgy@52: cod.isQuantized=True fazekasgy@52: cod.quantizeStep=1.0 fazekasgy@52: cod.sampleType=OneSamplePerStep fazekasgy@52: return cod fazekasgy@52: fazekasgy@52: def getParameterDescriptors(self): fazekasgy@52: thd = ParameterDescriptor() fazekasgy@52: thd.identifier='threshold' fazekasgy@52: thd.name='Noise threshold' Chris@68: thd.description='Magnitude below which a process block will be disregarded and zero returned' fazekasgy@52: thd.unit='v' fazekasgy@52: thd.minValue=0.0 fazekasgy@52: thd.maxValue=0.5 fazekasgy@52: thd.defaultValue=0.05 fazekasgy@52: thd.isQuantized=False fazekasgy@52: return thd fazekasgy@52: fazekasgy@52: def setParameter(self,paramid,newval): fazekasgy@52: if paramid == 'threshold' : fazekasgy@52: self.threshold = newval fazekasgy@52: return fazekasgy@52: fazekasgy@52: def getParameter(self,paramid): fazekasgy@52: if paramid == 'threshold' : fazekasgy@52: return self.threshold fazekasgy@52: else: fazekasgy@52: return 0.0 fazekasgy@52: fazekasgy@52: def process(self,inputbuffers,timestamp): fazekasgy@52: fazekasgy@52: # this is a 1 channel frequency domain plugin, therefore fazekasgy@52: # inputbuffers contain (block size / 2) + 1 complex numbers fazekasgy@52: # corresponding to the FFT output from DC to Nyquist inclusive fazekasgy@52: fazekasgy@52: cplxArray = inputbuffers[0][:-1] fazekasgy@52: fazekasgy@52: prev = self.previousSample fazekasgy@52: numLin = 0.0 fazekasgy@52: denom = 0.0 fazekasgy@52: centroid = 0.0 fazekasgy@52: fazekasgy@52: output = FeatureSet() fazekasgy@52: fazekasgy@52: pw = 0 fazekasgy@52: for i in xrange(1,len(cplxArray)) : fazekasgy@52: pw = pw + abs(cplxArray[i]) fazekasgy@52: fazekasgy@52: if pw > self.threshold : fazekasgy@52: for i in range(1,(len(cplxArray))) : fazekasgy@52: fazekasgy@52: re = cplxArray[i].real fazekasgy@52: im = cplxArray[i].imag fazekasgy@52: freq = i * self.m_inputSampleRate / self.m_blockSize fazekasgy@52: power = sqrt (re*re + im*im) / (self.m_blockSize/2) fazekasgy@52: denom = denom + power fazekasgy@52: numLin = numLin + freq * power fazekasgy@52: fazekasgy@52: if denom != 0 : fazekasgy@52: centroid = numLin / denom fazekasgy@52: fazekasgy@52: else : fazekasgy@52: centroid = 0.0 fazekasgy@52: fazekasgy@52: output[0] = Feature() fazekasgy@52: output[0].values = centroid fazekasgy@52: output[0].label = str(centroid) fazekasgy@52: fazekasgy@52: return output