christopher@45
|
1 '''
|
christopher@45
|
2 Author: Chunyang Song
|
christopher@45
|
3 Institution: Centre for Digital Music, Queen Mary University of London
|
christopher@45
|
4 '''
|
christopher@45
|
5
|
christopher@45
|
6 from basic_functions import concatenate, repeat, subdivide, ceiling, get_rhythm_category
|
christopher@45
|
7 from parameter_setter import are_parameters_valid
|
christopher@45
|
8
|
christopher@45
|
9
|
christopher@45
|
10
|
christopher@45
|
11
|
christopher@50
|
12 # Each terminal node contains two properties: its node type (note or rest) and its metrical weight.
|
christopher@45
|
13 class Node:
|
christopher@45
|
14 def __init__(self,nodeType,metricalWeight):
|
christopher@45
|
15 self.nodeType = nodeType
|
christopher@45
|
16 self.metricalWeight = metricalWeight
|
christopher@45
|
17
|
christopher@45
|
18 # This function will recurse the tree for a binary sequence and return a sequence containing the terminal nodes in time order.
|
christopher@50
|
19 def recursive_tree(binarySequence, subdivisionSequence, weightSequence, metricalWeight, level, Lmax):
|
christopher@45
|
20 # If matching to a Note type, add to terminal nodes
|
christopher@50
|
21 output = list()
|
christopher@45
|
22 if binarySequence == concatenate([1],repeat([0],len(binarySequence)-1)):
|
christopher@50
|
23 output.append(Node('N',metricalWeight))
|
christopher@45
|
24
|
christopher@45
|
25 # If matching to a Rest type, add to terminal nodes
|
christopher@45
|
26 elif binarySequence == repeat([0],len(binarySequence)):
|
christopher@50
|
27 output.append(Node('R',metricalWeight))
|
christopher@50
|
28
|
christopher@50
|
29 elif level+1 == Lmax:
|
christopher@50
|
30 print "WARNING: LHL tree recursion descended to Lmax, returning a note node but result will not be fully accurate. Check the rhythm pattern under test and/or specify larger Lmax to rectify the problem."
|
christopher@50
|
31 output.append(Node('N',metricalWeight))
|
christopher@45
|
32
|
christopher@45
|
33 # Keep subdividing by the subdivisor of the next level
|
christopher@50
|
34 else:
|
christopher@45
|
35 subBinarySequences = subdivide(binarySequence, subdivisionSequence[level+1])
|
christopher@45
|
36 subWeightSequences = concatenate([metricalWeight],repeat([weightSequence[level+1]],subdivisionSequence[level+1]-1))
|
christopher@45
|
37 for a in range(len(subBinarySequences)):
|
christopher@50
|
38 output = output + recursive_tree(subBinarySequences[a], subdivisionSequence, weightSequence, subWeightSequences[a], level+1, Lmax)
|
christopher@50
|
39
|
christopher@50
|
40 return output
|
christopher@45
|
41
|
christopher@45
|
42 def get_syncopation(bar, parameters = None):
|
christopher@45
|
43 syncopation = None
|
christopher@50
|
44 naughtyglobal = 0
|
christopher@45
|
45
|
christopher@45
|
46 binarySequence = bar.get_binary_sequence()
|
christopher@45
|
47 subdivisionSequence = bar.get_subdivision_sequence()
|
christopher@45
|
48
|
christopher@45
|
49 # LHL can only measure monorhythms
|
christopher@45
|
50 if get_rhythm_category(binarySequence, subdivisionSequence) == 'poly':
|
christopher@45
|
51 print 'Warning: LHL model detects polyrhythms so returning None.'
|
christopher@50
|
52 elif bar.is_empty():
|
christopher@50
|
53 print 'LHL model detects empty bar so returning -1.'
|
christopher@50
|
54 syncopation = -1
|
christopher@45
|
55 else:
|
christopher@45
|
56 # set defaults
|
christopher@50
|
57 Lmax = 10
|
christopher@45
|
58 weightSequence = range(0,-Lmax-1,-1)
|
christopher@45
|
59 # if parameters are specified by users, check their validities and update parameters if valid
|
christopher@45
|
60 if parameters!= None:
|
christopher@45
|
61 if 'Lmax' in parameters:
|
christopher@45
|
62 Lmax = parameters['Lmax']
|
christopher@45
|
63 if 'W' in parameters:
|
christopher@45
|
64 weightSequence = parameters['W']
|
christopher@45
|
65
|
christopher@45
|
66 if not are_parameters_valid(Lmax, weightSequence, subdivisionSequence):
|
christopher@45
|
67 print 'Error: the given parameters are not valid.'
|
christopher@45
|
68 else:
|
christopher@50
|
69
|
christopher@50
|
70 # For the rhythm in the current bar, process its tree structure and store the terminal nodes
|
christopher@50
|
71 terminalNodes = recursive_tree(ceiling(binarySequence), subdivisionSequence, weightSequence, weightSequence[0],0, Lmax)
|
christopher@50
|
72
|
christopher@50
|
73 # save the terminal nodes on the current bar so that
|
christopher@50
|
74 # the next bar can access them...
|
christopher@50
|
75 bar.LHLterminalNodes = terminalNodes
|
christopher@50
|
76
|
christopher@50
|
77 # If there is rhythm in the previous bar and we've already processed it
|
christopher@45
|
78 prevbar = bar.get_previous_bar()
|
christopher@50
|
79 if prevbar != None and prevbar.is_empty() != True:
|
christopher@50
|
80 # get its LHL tree if it has one
|
christopher@50
|
81 try:
|
christopher@50
|
82 prevbarNodes = prevbar.LHLterminalNodes
|
christopher@50
|
83 except AttributeError:
|
christopher@50
|
84 prevbarNodes = []
|
christopher@50
|
85
|
christopher@50
|
86 # find the final note node in the previous bar:
|
christopher@50
|
87 if len(prevbarNodes)>0:
|
christopher@50
|
88 i = len(prevbarNodes) - 1
|
christopher@45
|
89 # Only keep the last note-type node
|
christopher@50
|
90 while prevbarNodes[i].nodeType != 'N' and i>=0:
|
christopher@50
|
91 i = i-1
|
christopher@50
|
92 # prepend the note to the terminal node list for this bar
|
christopher@50
|
93 terminalNodes = [ prevbarNodes[i] ] + terminalNodes
|
christopher@50
|
94
|
christopher@45
|
95
|
christopher@50
|
96 # Search for the NR pairs that contribute to syncopation, then add the weight-difference to the NRpairSyncopation list
|
christopher@45
|
97 NRpairSyncopation = []
|
christopher@45
|
98 for i in range(len(terminalNodes)-1,0,-1):
|
christopher@45
|
99 if terminalNodes[i].nodeType == 'R':
|
christopher@45
|
100 for j in range(i-1, -1, -1):
|
christopher@45
|
101 if (terminalNodes[j].nodeType == 'N') & (terminalNodes[i].metricalWeight >= terminalNodes[j].metricalWeight):
|
christopher@45
|
102 NRpairSyncopation.append(terminalNodes[i].metricalWeight - terminalNodes[j].metricalWeight)
|
christopher@45
|
103 break
|
christopher@45
|
104
|
christopher@45
|
105 # If there are syncopation, sum all the local syncopation values stored in NRpairSyncopation list
|
christopher@45
|
106 if len(NRpairSyncopation) != 0:
|
christopher@45
|
107 syncopation = sum(NRpairSyncopation)
|
christopher@45
|
108 # If no syncopation, the value is -1;
|
christopher@45
|
109 elif len(terminalNodes) != 0:
|
christopher@45
|
110 syncopation = -1
|
christopher@45
|
111
|
christopher@45
|
112 return syncopation
|
christopher@45
|
113
|
christopher@45
|
114
|