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