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) |