annotate 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
rev   line source
csong@0 1 '''
csong@0 2 Author: Chunyang Song
csong@0 3 Institution: Centre for Digital Music, Queen Mary University of London
csong@0 4
csong@0 5 ** Longuet-Higgins and Lee's Model (LHL) **
csong@0 6
csong@0 7 Algorithm:
csong@0 8
csong@0 9 Only applicable to simple rhtyhms.
csong@0 10
csong@0 11 Generate tree structure:
csong@0 12 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];
csong@0 13 2) If after subdivision, a node has at least one branch that is full rest or one note, then it cannot be subdivided;
csong@0 14 3) Weight of node, initialized as 0, decrease by 1 with increasing depth of the tree.
csong@0 15
csong@0 16 Assign each node in the tree three attributes - weight, pos, event;
csong@0 17 Search for Note-Rest pairs that Note's weight is no more than Rest's weight;
csong@0 18 Syncopation for such a pair is the difference in weights;
csong@0 19 Total Syncopation value for non-syncopation rhythms is -1; for syncopated rhythms is the sum of syncopation of all notes.
csong@0 20
csong@0 21 In order to evaluate LHL models with the others, re-scale LHL's measures to non-negative.
csong@0 22 Apart from the immeasuable rhythms, add 1 to each syncopation value.
csong@0 23
csong@0 24 '''
csong@0 25 from MeterStructure import MeterStructure
csong@0 26
csong@0 27 class Node:
csong@0 28 def __init__(self, pos, event):
csong@0 29 self.pos = pos
csong@0 30 self.event = event
csong@0 31
csong@0 32 def assignWeight(self, time_sig):
csong@0 33 ms = MeterStructure(time_sig)
csong@0 34 # Retrieve the LHL meter hierarchy in one bar;
csong@0 35 #(e.g. weight = [0, -4, -3, -4, -2, -4, -3, -4, -1, -4, -3, -4, -2, -4, -3, -4] in 4/4 meter)
csong@0 36 weights = ms.getLHLWeights(1)
csong@0 37
csong@0 38 # find the metrical weight of the note that locates at certain position(self.pos)
csong@0 39 # index is the corresponding position of "self.pos" in the "weights" list
csong@0 40 index = int(((abs(self.pos)%48)/48.0)*len(weights))
csong@0 41 self.weight = weights[index]
csong@0 42
csong@0 43 class Tree:
csong@0 44 def __init__(self):
csong@0 45 self.nodes = []
csong@0 46
csong@0 47 def add(self, Node):
csong@0 48 self.nodes.append (Node)
csong@0 49
csong@0 50 def getWeight(self, time_sig):
csong@0 51 for n in self.nodes:
csong@0 52 n.assignWeight(time_sig)
csong@0 53
csong@0 54 def display(self):
csong@0 55 print 'Pos, Event, Weight'
csong@0 56 for n in self.nodes:
csong@0 57 print '%02d %s %02d' % (n.pos, n.event, n.weight)
csong@0 58
csong@0 59
csong@0 60 def subdivide(sequence, segments_num):
csong@0 61 subSeq = []
csong@0 62 if len(sequence) % segments_num != 0:
csong@0 63 print 'Error: rhythm segment cannot be equally subdivided.'
csong@0 64 else:
csong@0 65 n = len(sequence) / segments_num
csong@0 66 start , end = 0, n
csong@0 67 for i in range(segments_num):
csong@0 68 subSeq.append(sequence[start : end])
csong@0 69 start = end
csong@0 70 end = end + n
csong@0 71
csong@0 72 return subSeq
csong@0 73
csong@0 74
csong@0 75 def eventType (sequence):
csong@0 76 if not(1 in sequence):
csong@0 77 event = 'R' # Full rest
csong@0 78 elif sequence[0] == 1 and not(1 in sequence[1:]):
csong@0 79 event = 'N' # Only one on-beat note
csong@0 80 else:
csong@0 81 event = 'D' # Divisable
csong@0 82
csong@0 83 return event
csong@0 84
csong@0 85 def splitInto2 (sequence, tree, pos, isRecursive):
csong@0 86
csong@0 87 subs = subdivide(sequence, 2)
csong@0 88
csong@0 89 for s in subs:
csong@0 90 if eventType(s) == 'R' or eventType(s) == 'N':
csong@0 91 #print 'test', s, eventType(s), pos
csong@0 92 tree.add( Node(pos, eventType(s)) )
csong@0 93 else:
csong@0 94 if isRecursive:
csong@0 95 #print 'test', s, eventType(s), pos
csong@0 96 splitInto2 (s, tree, pos, True)
csong@0 97 else:
csong@0 98 splitInto3 (s, tree, pos)
csong@0 99
csong@0 100 pos = pos + len(sequence) / 2
csong@0 101
csong@0 102 return tree
csong@0 103
csong@0 104 def splitInto3 (sequence, tree, pos):
csong@0 105
csong@0 106 subs = subdivide(sequence, 3)
csong@0 107
csong@0 108 for s in subs:
csong@0 109 if eventType(s) == 'R' or eventType(s) == 'N':
csong@0 110 #print 'test', s, eventType(s), pos
csong@0 111 tree.add( Node(pos, eventType(s)) )
csong@0 112
csong@0 113 else:
csong@0 114 splitInto2 (s, tree, pos, True)
csong@0 115
csong@0 116 pos = pos + len(sequence) / 3
csong@0 117
csong@0 118 return tree
csong@0 119
csong@0 120
csong@0 121 def createTree(rhythm, time_sig, bar):
csong@0 122 t = Tree()
csong@0 123 # The root is the rhtyhm in a entire bar, has weight 0, at position 0
csong@0 124 weight, pos, event = 0 , 0, eventType(rhythm)
csong@0 125 root = Node (pos, event)
csong@0 126 if '2/4' in time_sig:
csong@0 127 t.add ( Node(-24, 'N') ) # Treat the last metronome beat in the previous bar as a sounded note
csong@0 128
csong@0 129 if event == 'D':
csong@0 130 t = splitInto2(rhythm, t, pos, True)
csong@0 131 else:
csong@0 132 t.add (root)
csong@0 133
csong@0 134 elif '4/4' in time_sig:
csong@0 135 t.add ( Node(-12, 'N') ) # Treat the last metronome beat in the previous bar as a sounded note
csong@0 136
csong@0 137 if event == 'D':
csong@0 138 t = splitInto2(rhythm, t, pos, True)
csong@0 139 else:
csong@0 140 t.add (root)
csong@0 141
csong@0 142 elif '3/4' in time_sig:
csong@0 143 t.add ( Node(-8, 'N') ) # Treat the last metronome beat in the previous bar as a sounded note
csong@0 144
csong@0 145 segments = subdivide (rhythm, bar)
csong@0 146
csong@0 147 for s in segments:
csong@0 148 if eventType(s) == 'D':
csong@0 149 t = splitInto3(s, t, pos)
csong@0 150 pos = pos + len(rhythm)/bar
csong@0 151
csong@0 152
csong@0 153 elif '6/8' in time_sig:
csong@0 154 t.add ( Node(-8, 'N') ) # Treat the last metronome beat in the previous bar as a sounded note
csong@0 155
csong@0 156 segments = subdivide (rhythm, bar)
csong@0 157
csong@0 158 for s in segments:
csong@0 159 if eventType(s) == 'D':
csong@0 160 t = splitInto2(s, t, pos, False)
csong@0 161 pos = pos + len(rhythm)/bar
csong@0 162
csong@0 163 else:
csong@0 164 print 'This time signature is not defined. Choose between 4/4, 3/4 or 6/8'
csong@0 165
csong@0 166 return t
csong@0 167
csong@0 168
csong@0 169 def lhl(rhythm, time_sig, category, bar):
csong@0 170 measures = []
csong@0 171
csong@0 172 if 'poly' in category:
csong@0 173 return -1
csong@0 174 else:
csong@0 175 tree = createTree(rhythm, time_sig, bar)
csong@0 176 tree.getWeight(time_sig)
csong@0 177 #tree.display()
csong@0 178
csong@0 179 # find NR-pair that N's weight <= R's weight, add difference in weight to measures[]
csong@0 180 N_weight = tree.nodes[0].weight
csong@0 181 size = len(tree.nodes)
csong@0 182
csong@0 183 for i in range(1, size):
csong@0 184
csong@0 185 if tree.nodes[i].event == 'N':
csong@0 186 N_weight = tree.nodes[i].weight
csong@0 187
csong@0 188 if tree.nodes[i].event == 'R' and tree.nodes[i].weight >= N_weight:
csong@0 189 measures.append(tree.nodes[i].weight - N_weight )
csong@0 190
csong@0 191 # Calculate syncopation
csong@0 192 if len(measures) == 0: # syncopation of non-syncopated rhythm is -1
csong@0 193 syncopation = -1
csong@0 194 else:
csong@0 195 syncopation = sum(measures) # syncopation of syncopated rhythm is the sum of measures[]
csong@0 196
csong@0 197 return syncopation + 1 # For evaluation purpose, scale up by 1 to make scores above 0
csong@0 198
csong@0 199
csong@0 200 # Retrieve the stimuli
csong@0 201 #f = file('stimuli.txt')
csong@0 202 f = file('stimuli_34only.txt')
csong@0 203
csong@0 204 #Calculate syncopation for each rhythm pattern
csong@0 205 while True:
csong@0 206 line = f.readline().split(';')
csong@0 207 if len(line) == 1:
csong@0 208 break
csong@0 209 else:
csong@0 210 sti_name = line[0]
csong@0 211 rhythmString = line[1].split()
csong@0 212 time_sig = line[2]
csong@0 213 category = line[3]
csong@0 214 bar = int(line[4])
csong@0 215
csong@0 216 rhythm = map(int,rhythmString[0].split(','))
csong@0 217
csong@0 218 print sti_name, lhl(rhythm, time_sig, category, bar)