Mercurial > hg > syncopation-dataset
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