diff Syncopation models/LHL.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 b2da092dc2e0
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Syncopation models/LHL.py	Fri Mar 21 15:49:46 2014 +0000
@@ -0,0 +1,218 @@
+'''
+Author: Chunyang Song
+Institution: Centre for Digital Music, Queen Mary University of London
+
+** Longuet-Higgins and Lee's Model (LHL) **
+
+Algorithm:
+
+Only applicable to simple rhtyhms.
+
+Generate tree structure:
+1) Time_signature decides sequence of subdivisions: 4/4 - [2,2,2,...2], 3/4 - [3,2,2,...2], 6/8 - [2,3,2,2...2]; 
+2) If after subdivision, a node has at least one branch that is full rest or one note, then it cannot be subdivided;
+3) Weight of node, initialized as 0, decrease by 1 with increasing depth of the tree.
+
+Assign each node in the tree three attributes - weight, pos, event;
+Search for Note-Rest pairs that Note's weight is no more than Rest's weight;
+Syncopation for such a pair is the difference in weights; 
+Total Syncopation value for non-syncopation rhythms is -1; for syncopated rhythms is the sum of syncopation of all notes.
+
+In order to evaluate LHL models with the others, re-scale LHL's measures to non-negative.
+Apart from the immeasuable rhythms, add 1 to each syncopation value. 
+
+'''
+from MeterStructure import MeterStructure
+
+class Node:
+	def __init__(self, pos, event):
+		self.pos = pos
+		self.event = event
+
+	def assignWeight(self, time_sig):
+		ms = MeterStructure(time_sig)
+		# Retrieve the LHL meter hierarchy in one bar;
+		#(e.g. weight = [0, -4, -3, -4, -2, -4, -3, -4, -1, -4, -3, -4, -2, -4, -3, -4] in 4/4 meter)
+		weights = ms.getLHLWeights(1)	
+		
+		# find the metrical weight of the note that locates at certain position(self.pos)
+		# index is the corresponding position of "self.pos" in the "weights" list
+		index = int(((abs(self.pos)%48)/48.0)*len(weights))		
+		self.weight = weights[index]
+
+class Tree:
+	def __init__(self):
+		self.nodes = []
+
+	def add(self, Node):
+		self.nodes.append (Node)
+
+	def getWeight(self, time_sig):
+		for n in self.nodes:
+			n.assignWeight(time_sig)
+
+	def display(self):
+		print 'Pos, Event, Weight'
+		for n in self.nodes:
+			print '%02d    %s      %02d' % (n.pos, n.event, n.weight)
+
+
+def subdivide(sequence, segments_num):
+	subSeq = []
+	if len(sequence) % segments_num != 0:
+		print 'Error: rhythm segment cannot be equally subdivided.'
+	else:
+		n = len(sequence) / segments_num
+		start , end = 0, n
+		for i in range(segments_num):
+			subSeq.append(sequence[start : end])
+			start = end
+			end = end + n
+	
+	return subSeq
+
+
+def eventType (sequence):
+	if not(1 in sequence):	
+		event = 'R'  # Full rest
+	elif sequence[0] == 1 and not(1 in sequence[1:]): 
+		event = 'N'  # Only one on-beat note
+	else:
+		event = 'D'  # Divisable
+
+	return event
+
+def splitInto2 (sequence, tree, pos, isRecursive):
+	
+	subs = subdivide(sequence, 2)
+	
+	for s in subs:
+		if eventType(s) == 'R' or eventType(s) == 'N':
+			#print 'test', s, eventType(s), pos
+			tree.add( Node(pos, eventType(s)) )
+		else:
+			if isRecursive:
+				#print 'test', s, eventType(s), pos
+				splitInto2 (s, tree, pos, True)
+			else:
+				splitInto3 (s, tree, pos)
+
+		pos = pos + len(sequence) / 2
+	
+	return tree
+
+def splitInto3 (sequence, tree, pos):
+
+	subs = subdivide(sequence, 3)
+
+	for s in subs:
+		if eventType(s) == 'R' or eventType(s) == 'N':
+			#print 'test', s, eventType(s), pos
+			tree.add( Node(pos, eventType(s)) )
+	
+		else:
+			splitInto2 (s, tree, pos, True)			
+		
+		pos = pos + len(sequence) / 3
+
+	return tree
+
+
+def createTree(rhythm, time_sig, bar):
+	t = Tree()
+	# The root is the rhtyhm in a entire bar, has weight 0, at position 0
+	weight, pos, event = 0 , 0, eventType(rhythm)
+	root = Node (pos, event)
+	if '2/4' in time_sig:
+		t.add ( Node(-24, 'N') )  # Treat the last metronome beat in the previous bar as a sounded note
+	
+		if event == 'D':
+			t = splitInto2(rhythm, t, pos, True)
+		else:
+			t.add (root)
+
+	elif '4/4' in time_sig:
+		t.add ( Node(-12, 'N') )  # Treat the last metronome beat in the previous bar as a sounded note
+	
+		if event == 'D':
+			t = splitInto2(rhythm, t, pos, True)
+		else:
+			t.add (root)
+
+	elif '3/4' in time_sig:
+		t.add ( Node(-8, 'N') )  # Treat the last metronome beat in the previous bar as a sounded note
+
+		segments = subdivide (rhythm, bar)
+
+		for s in segments:
+			if eventType(s) == 'D':
+				t = splitInto3(s, t, pos)
+			pos = pos + len(rhythm)/bar
+
+
+	elif '6/8' in time_sig:
+		t.add ( Node(-8, 'N') )  # Treat the last metronome beat in the previous bar as a sounded note
+		
+		segments = subdivide (rhythm, bar)
+
+		for s in segments:
+			if eventType(s) == 'D': 
+				t = splitInto2(s, t, pos, False)
+			pos = pos + len(rhythm)/bar
+
+	else:
+		print 'This time signature is not defined. Choose between 4/4, 3/4 or 6/8'
+
+	return t
+
+
+def lhl(rhythm, time_sig, category, bar):
+	measures = []
+
+	if 'poly' in category:
+		return -1
+	else:
+		tree = createTree(rhythm, time_sig, bar)	
+		tree.getWeight(time_sig)
+		#tree.display()
+
+		# find NR-pair that N's weight <= R's weight, add difference in weight to measures[] 
+		N_weight = tree.nodes[0].weight
+		size = len(tree.nodes)
+
+		for i in range(1, size):
+		
+			if tree.nodes[i].event == 'N':
+				N_weight = tree.nodes[i].weight
+
+			if tree.nodes[i].event == 'R' and tree.nodes[i].weight >= N_weight:
+				measures.append(tree.nodes[i].weight - N_weight )
+
+		# Calculate syncopation
+		if len(measures) == 0: # syncopation of non-syncopated rhythm is -1
+			syncopation = -1
+		else:
+			syncopation = sum(measures)  # syncopation of syncopated rhythm is the sum of measures[]
+		
+		return syncopation + 1 # For evaluation purpose, scale up by 1 to make scores above 0 
+
+
+# Retrieve the stimuli
+#f = file('stimuli.txt')
+f = file('stimuli_34only.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, lhl(rhythm, time_sig, category, bar)
\ No newline at end of file