view Syncopation models/Keith.py @ 0:76ce27beba95

Have uploaded the syncopation dataset and audio wavefies.
author Chunyang Song <csong@eecs.qmul.ac.uk>
date Fri, 21 Mar 2014 15:49:46 +0000
parents
children
line wrap: on
line source
'''
Author: Chunyang Song
Institution: Centre for Digital Music, Queen Mary University of London

** Keith Model **

Algorithm:

Only applicable to binary meter (the number of beats is a power of 2).

Calculate the duration (IOI) of each note, d;
Round the d down to the nearest power of 2, D;
Check if the start and the end of note are on-beat (whether divisible by D);
If both on-beat, measure = 0;
If start on-beat but end off-beat, measure = 1;
If start off-beat but end on-beat, measure = 2;
If start and end off-beat, measure = 3;
Syncopation is the sum of measures of all notes.

'''

from MeterStructure import MeterStructure

def roundDownPower2(input):
	lower = 0
	if input >=0:
		i = 0
		lower = pow(2,i)

		while True:
			upper = pow(2,i+1)
			if lower <= input < upper:
				break
			else:
				lower = upper
				i = i+1
	else:
		print 'Invalid input: input is negative'
	return lower
	
def keith(rhythm, time_sig, category, bar):
	ms = MeterStructure(time_sig)
	circle = ms.getCircle(bar)
	l = len(circle)
	# mTemplate represents all the metrical positions for bar number of bars and the downbeat position of the following bar. 
	#For example, the mTemplate for one bar in 4/4 rhythm with lowest level 16th note, mTemplate = [0:16]
	mTemplate = range(l+1)
	
	if len(mTemplate)!=0:

		onsetPos = []
		measures = []
		
		'''
		Note that mTemplate is encoded by 8*bar+1- or 16*bar+1-long digits, rhythm is encoded by 48*bar-long digits,
		therefore we need to normalize the IOI of onsets to the mTemplate scale, by (pos/len(rhythm))*len(mTemplate)
		'''

		# Locate all the onset, store the position of each note into onsetPos
		l = len(rhythm) 
		for i in range(l):	
			if rhythm[i] == 1:    # onset detected
				onsetPos.append(i)
			
		#Calculate the duration of each onset and round it down to nearest power of 2, store into D
		# Then check if the start and end of each onset is on-beat, calculate measures
		n = len(onsetPos)
		for i in range(n):
			start = (onsetPos[i]/ float(l) )* (len(mTemplate)-1)
			if i == n-1: 
				end = mTemplate[-1]	# The duration of the last note is the its distance to the first beat in next bar
			else:
				end = (onsetPos[i+1]/ float (l) ) * (len(mTemplate)-1)	# the duration of note is its distance to the next note

			d = end - start
			D = roundDownPower2(d)
			if start % D ==0 and end % D ==0:
				measures.append(0)
			elif start % D ==0 and end % D != 0:
				measures.append(1)
			elif start % D != 0 and end % D == 0:
				measures.append(2)
			else:
				measures.append(3)

		syncopation = sum(measures)
		return syncopation
	
	else:
		return -1


# Retrieve the stimuli
f = file('stimuli.txt')

#Calculate syncopation for each rhythm pattern
while True:
	line = f.readline().split(';')
	if len(line) == 1:
		 break
	else:
		sti_name = line[0]
		rhythmString = line[1].split()
		time_sig = line[2]
		category = line[3]
		bar = int(line[4])
		
		rhythm = map(int,rhythmString[0].split(','))

		print sti_name, keith(rhythm, time_sig, category, bar)