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