Mercurial > hg > syncopation-dataset
comparison Syncopation models/LHL.py @ 1:b2da092dc2e0
The consolidated syncopation software. Have finished individual model and basic functions. Need to revise the coding in main.py, and add rhythm-input interface.
author | Chunyang Song <csong@eecs.qmul.ac.uk> |
---|---|
date | Sun, 05 Oct 2014 21:52:41 +0100 |
parents | 76ce27beba95 |
children | 031e2ccb1fb6 |
comparison
equal
deleted
inserted
replaced
0:76ce27beba95 | 1:b2da092dc2e0 |
---|---|
1 ''' | 1 ''' |
2 Author: Chunyang Song | 2 Author: Chunyang Song |
3 Institution: Centre for Digital Music, Queen Mary University of London | 3 Institution: Centre for Digital Music, Queen Mary University of London |
4 ''' | |
4 | 5 |
5 ** Longuet-Higgins and Lee's Model (LHL) ** | 6 from basic_functions import concatenate, repeat, subdivide, ceiling |
6 | 7 |
7 Algorithm: | 8 terminal_nodes = [] # Global variable, storing all the terminal nodes from recursive tree structure in time order |
8 | 9 |
9 Only applicable to simple rhtyhms. | 10 # Each terminnal node contains two properties: its node type (note or rest) and its metrical weight. |
11 class Node: | |
12 def __init__(self,node_type,metrical_weight): | |
13 self.node_type = node_type | |
14 self.metrical_weight = metrical_weight | |
10 | 15 |
11 Generate tree structure: | 16 # This function will recurse the tree for a binary sequence and return a sequence containing the terminal nodes in time order. |
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]; | 17 def recursive_tree(seq, subdivision_seq, weight_seq, metrical_weight, level): |
13 2) If after subdivision, a node has at least one branch that is full rest or one note, then it cannot be subdivided; | 18 if seq == concatenate([1],repeat([0],len(seq)-1)): # If matching to a Note type, add to terminal nodes |
14 3) Weight of node, initialized as 0, decrease by 1 with increasing depth of the tree. | 19 terminal_nodes.append(Node('N',metrical_weight)) |
15 | 20 |
16 Assign each node in the tree three attributes - weight, pos, event; | 21 elif seq == repeat([0],len(seq)): # If matching to a Rest type, add to terminal nodes |
17 Search for Note-Rest pairs that Note's weight is no more than Rest's weight; | 22 terminal_nodes.append(Node('R',metrical_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 | 23 |
21 In order to evaluate LHL models with the others, re-scale LHL's measures to non-negative. | 24 else: # Keep subdividing by the subdivisor of the next level |
22 Apart from the immeasuable rhythms, add 1 to each syncopation value. | 25 sub_seq = subdivide(seq, subdivision_seq[level+1]) |
26 sub_weight_seq = concatenate([metrical_weight],repeat([weight_seq[level+1]],subdivision_seq[level+1]-1)) | |
27 for a in range(len(sub_seq)): | |
28 recursive_tree(sub_seq[a], subdivision_seq, weight_seq, sub_weight_seq[a], level+1) | |
23 | 29 |
24 ''' | 30 # This function calculate syncoaption score for LHL model. |
25 from MeterStructure import MeterStructure | 31 def get_syncopation(seq, subdivision_seq, weight_seq, prebar_seq, rhythm_category): |
32 syncopation = None | |
33 if rhythm_category == 'poly': | |
34 print 'Error: LHL model cannot deal with polyrhythms.' | |
35 else: | |
36 # If there is rhythm in previous bar, process its tree structure | |
37 if prebar_seq != None: | |
38 recursive_tree(ceiling(prebar_seq), subdivision_seq, weight_seq, weight_seq[0],0) | |
39 | |
40 # Only keep the last note-type node | |
41 while terminal_nodes[-1].node_type != 'N': | |
42 del terminal_nodes[-1] | |
43 del terminal_nodes[0:-1] | |
26 | 44 |
27 class Node: | 45 # For the rhythm in the current bar, process its tree structure and store the terminal nodes |
28 def __init__(self, pos, event): | 46 recursive_tree(ceiling(seq), subdivision_seq, weight_seq, weight_seq[0],0) |
29 self.pos = pos | 47 |
30 self.event = event | 48 # for t in terminal_nodes: |
49 # print '<', t.node_type, t.metrical_weight, '>' | |
31 | 50 |
32 def assignWeight(self, time_sig): | 51 # Search for the NR pairs that contribute to syncopation, add the weight-difference to the NR_pair_syncopation list |
33 ms = MeterStructure(time_sig) | 52 NR_pair_syncopation = [] |
34 # Retrieve the LHL meter hierarchy in one bar; | 53 for i in range(len(terminal_nodes)-1,0,-1): |
35 #(e.g. weight = [0, -4, -3, -4, -2, -4, -3, -4, -1, -4, -3, -4, -2, -4, -3, -4] in 4/4 meter) | 54 if terminal_nodes[i].node_type == 'R': |
36 weights = ms.getLHLWeights(1) | 55 for j in range(i-1, -1, -1): |
37 | 56 if (terminal_nodes[j].node_type == 'N') & (terminal_nodes[i].metrical_weight >= terminal_nodes[j].metrical_weight): |
38 # find the metrical weight of the note that locates at certain position(self.pos) | 57 NR_pair_syncopation.append(terminal_nodes[i].metrical_weight - terminal_nodes[j].metrical_weight) |
39 # index is the corresponding position of "self.pos" in the "weights" list | 58 break |
40 index = int(((abs(self.pos)%48)/48.0)*len(weights)) | 59 #print NR_pair_syncopation |
41 self.weight = weights[index] | |
42 | 60 |
43 class Tree: | 61 # If no syncopation, the value is -1; otherwise, sum all the local syncopation values stored in NR_pair_syncopation list |
44 def __init__(self): | 62 if len(NR_pair_syncopation) != 0: |
45 self.nodes = [] | 63 syncopation = sum(NR_pair_syncopation) |
64 elif len(terminal_nodes) != 0: | |
65 syncopation = -1 | |
46 | 66 |
47 def add(self, Node): | 67 return syncopation |
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) |