fazekasgy@37: '''PySpectralFeatures.py - Example plugin demonstrates 
fazekasgy@37: how to calculate and return simple low-level spectral 
fazekasgy@37: descriptors (curves) using Numpy and the buffer interface.
fazekasgy@37: 
fazekasgy@37: Outputs: 
fazekasgy@37: 1) Spectral Centroid
cannam@59: 2) Spectral Crest Factor
fazekasgy@37: 3) Spectral Band-width
fazekasgy@37: 4) Spectral Difference (first order)
fazekasgy@37: 
fazekasgy@37: Centre for Digital Music, Queen Mary University of London.
fazekasgy@37: Copyright (C) 2009 Gyorgy Fazekas, QMUL. (See Vamp sources 
fazekasgy@37: for licence information.)
fazekasgy@37: 
fazekasgy@37: '''
fazekasgy@37: 
fazekasgy@37: from numpy import *
fazekasgy@37: from vampy import *
fazekasgy@37: 
fazekasgy@37: class PySpectralFeatures: 
fazekasgy@37: 	
fazekasgy@37: 	def __init__(self,inputSampleRate):
fazekasgy@37: 
fazekasgy@37: 		# flags:
Chris@67: 		self.vampy_flags = vf_BUFFER | vf_REALTIME
fazekasgy@37: 
fazekasgy@37: 		self.m_inputSampleRate = inputSampleRate
fazekasgy@37: 		self.m_stepSize = 0
fazekasgy@37: 		self.m_blockSize = 0
fazekasgy@37: 		self.m_channels = 0
fazekasgy@37: 		self.threshold = 0.05
fazekasgy@37: 		return None
fazekasgy@37: 		
fazekasgy@37: 	def initialise(self,channels,stepSize,blockSize):
fazekasgy@37: 		self.m_channels = channels
fazekasgy@37: 		self.m_stepSize = stepSize		
fazekasgy@37: 		self.m_blockSize = blockSize
fazekasgy@47: 		self.prevMag = zeros((blockSize/2))
fazekasgy@37: 		return True
fazekasgy@37: 		
fazekasgy@37: 	def reset(self):
fazekasgy@37: 		# reset any initial conditions
Chris@68: 		self.prevMag = zeros((self.m_blockSize/2))
fazekasgy@37: 		return None
fazekasgy@37: 	
fazekasgy@37: 	def getMaker(self):
fazekasgy@37: 		return 'Vampy Example Plugins'
fazekasgy@37: 	
fazekasgy@37: 	def getName(self):
fazekasgy@37: 		return 'Vampy Spectral Features'
fazekasgy@37: 		
fazekasgy@37: 	def getIdentifier(self):
fazekasgy@37: 		return 'vampy-sf3'
fazekasgy@37: 
fazekasgy@37: 	def getDescription(self):
fazekasgy@37: 		return 'A collection of low-level spectral descriptors.'
Chris@69: 
Chris@69:         def getCopyright(self):
Chris@69:                 return 'Plugin By George Fazekas. Freely redistributable example plugin (BSD license)'
Chris@69: 
fazekasgy@37: 	def getMaxChannelCount(self):
fazekasgy@37: 		return 1
fazekasgy@37: 		
fazekasgy@37: 	def getInputDomain(self):
fazekasgy@37: 		return FrequencyDomain
fazekasgy@37: 			
fazekasgy@37: 	def getOutputDescriptors(self):
fazekasgy@37: 
fazekasgy@37: 		#Generic values are the same for all
fazekasgy@37: 		Generic = OutputDescriptor()
fazekasgy@37: 		Generic.hasFixedBinCount=True
fazekasgy@37: 		Generic.binCount=1
fazekasgy@37: 		Generic.hasKnownExtents=False
fazekasgy@37: 		Generic.isQuantized=False
fazekasgy@37: 		Generic.sampleType = OneSamplePerStep
fazekasgy@37: 		Generic.unit = 'Hz'		
fazekasgy@37: 		
fazekasgy@37: 		#Spectral centroid etc...
fazekasgy@37: 		SC = OutputDescriptor(Generic)
fazekasgy@37: 		SC.identifier = 'vampy-sc'
fazekasgy@37: 		SC.name = 'Spectral Centroid'
fazekasgy@37: 		SC.description ='Spectral Centroid (Brightness)'
fazekasgy@37: 				
fazekasgy@37: 		SCF = OutputDescriptor(Generic)
fazekasgy@37: 		SCF.identifier = 'vampy-scf'
fazekasgy@37: 		SCF.name = 'Spectral Crest Factor'
fazekasgy@37: 		SCF.description = 'Spectral Crest (Tonality)'
fazekasgy@37: 		SCF.unit = 'v'
fazekasgy@37: 
fazekasgy@37: 		BW = OutputDescriptor(Generic)
fazekasgy@37: 		BW.identifier = 'vampy-bw'
fazekasgy@37: 		BW.name = 'Band Width'
fazekasgy@37: 		BW.description = 'Spectral Band Width'
fazekasgy@37: 		
fazekasgy@37: 		SD = OutputDescriptor(Generic)
fazekasgy@37: 		SD.identifier = 'vampy-sd'
fazekasgy@37: 		SD.name = 'Spectral Difference'
fazekasgy@37: 		SD.description = 'Eucledian distance of successive magnitude spectra.'
fazekasgy@37: 			
fazekasgy@37: 		#return a tuple, list or OutputList(SC,SCF,BW)
fazekasgy@37: 		return OutputList(SC,SCF,BW,SD)
fazekasgy@37: 
fazekasgy@37: 	def getParameterDescriptors(self):
fazekasgy@37: 		
fazekasgy@37: 		threshold = ParameterDescriptor()
fazekasgy@37: 		threshold.identifier='threshold'
fazekasgy@37: 		threshold.name='Noise threshold'
Chris@68: 		threshold.description='Magnitude below which a process block will be disregarded and zeroes returned'
fazekasgy@37: 		threshold.unit='v'
fazekasgy@37: 		threshold.minValue=0
fazekasgy@37: 		threshold.maxValue=1
fazekasgy@37: 		threshold.defaultValue=0.05
fazekasgy@37: 		threshold.isQuantized=False
fazekasgy@37: 		
fazekasgy@37: 		return ParameterList(threshold)
fazekasgy@37: 
fazekasgy@37: 	def setParameter(self,paramid,newval):
fazekasgy@37: 		if paramid == 'threshold' :
fazekasgy@37: 			self.threshold = newval
fazekasgy@37: 		return
fazekasgy@37: 		
fazekasgy@37: 	def getParameter(self,paramid):
fazekasgy@37: 		if paramid == 'threshold' :
fazekasgy@37: 			return self.threshold
fazekasgy@37: 		else:
fazekasgy@37: 			return 0.0
fazekasgy@37: 
fazekasgy@37: 
fazekasgy@37:     # using the numpy memory buffer interface: 
fazekasgy@37: 	# flag : vf_BUFFER (or implement processN)
fazekasgy@37: 	# NOTE: Vampy can now pass numpy arrays directly using 
fazekasgy@37: 	# the flag vf_ARRAY (see MFCC plugin for example)
fazekasgy@37: 	def process(self,membuffer,timestamp):
fazekasgy@37: 
fazekasgy@37: 		fftsize = self.m_blockSize
fazekasgy@37: 		sampleRate = self.m_inputSampleRate
fazekasgy@37: 
fazekasgy@37: 		#for time domain plugins use the following line:
fazekasgy@37: 		#audioSamples = frombuffer(membuffer[0],float32)
fazekasgy@37: 
fazekasgy@37: 		#for frequency domain plugins use:
fazekasgy@37: 		complexSpectrum =  frombuffer(membuffer[0],complex64,-1,8)
fazekasgy@37: 
fazekasgy@37: 		# meaning of the parameters above:
fazekasgy@37: 		# complex64 : data type of the created numpy array
fazekasgy@37: 		# -1 : convert the whole buffer 
fazekasgy@37: 		#  8 : skip the DC component (2*32bit / 8bit = 8byte)
fazekasgy@37: 
fazekasgy@37: 		magnitudeSpectrum = abs(complexSpectrum) / (fftsize*0.5)
fazekasgy@37: 		#phaseSpectrum = angle(complexSpectrum)
fazekasgy@37: 		
fazekasgy@37: 		freq = array(range(1,len(complexSpectrum)+1)) \
fazekasgy@37: 		* sampleRate / fftsize
fazekasgy@37: 		
fazekasgy@37: 		# return features in a FeatureSet()
fazekasgy@37: 		output_featureSet = FeatureSet()
fazekasgy@37: 
fazekasgy@37: 		tpower = sum(magnitudeSpectrum)
fazekasgy@37: 
fazekasgy@37: 		if tpower > self.threshold : 
fazekasgy@37: 			centroid = sum(freq * magnitudeSpectrum) / tpower 
fazekasgy@37: 			crest = max(magnitudeSpectrum)  / tpower
fazekasgy@37: 			bw = sum( abs(freq - centroid) * magnitudeSpectrum ) / tpower
fazekasgy@37: 			normMag = magnitudeSpectrum / tpower			
fazekasgy@37: 			sd = sqrt(sum(power((normMag - self.prevMag),2)))
fazekasgy@37: 			self.prevMag = normMag
fazekasgy@37: 		else :
fazekasgy@37: 			centroid = 0.0
fazekasgy@37: 			crest = 0.0
fazekasgy@37: 			bw = 0.0
fazekasgy@37: 			sd = 0.0
fazekasgy@37: 			
fazekasgy@37: 		# Any value resulting from the process can be returned.
fazekasgy@37: 		# It is no longer necessary to wrap single values into lists
fazekasgy@37: 		# and convert numpy.floats to python floats,
fazekasgy@37: 		# however a FeatureList() (or python list) can be returned
fazekasgy@37: 		# if more than one feature is calculated per frame.
fazekasgy@37: 		# The feature values can be e.g. int, float, list or array.
fazekasgy@37: 		
fazekasgy@37: 		output_featureSet[0] = Feature(centroid)
fazekasgy@37: 		output_featureSet[1] = Feature(crest)
fazekasgy@37: 		output_featureSet[2] = Feature(bw)
fazekasgy@37: 		output_featureSet[3] = Feature(sd)
fazekasgy@37: 
fazekasgy@37: 		return output_featureSet