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)