christopher@45: ''' christopher@45: Author: Chunyang Song christopher@45: Institution: Centre for Digital Music, Queen Mary University of London christopher@45: ''' christopher@45: christopher@45: from basic_functions import concatenate, repeat, subdivide, ceiling, get_rhythm_category christopher@45: from parameter_setter import are_parameters_valid christopher@45: christopher@45: christopher@45: christopher@45: christopher@50: # Each terminal node contains two properties: its node type (note or rest) and its metrical weight. christopher@45: class Node: christopher@45: def __init__(self,nodeType,metricalWeight): christopher@45: self.nodeType = nodeType christopher@45: self.metricalWeight = metricalWeight christopher@45: christopher@45: # This function will recurse the tree for a binary sequence and return a sequence containing the terminal nodes in time order. christopher@50: def recursive_tree(binarySequence, subdivisionSequence, weightSequence, metricalWeight, level, Lmax): christopher@45: # If matching to a Note type, add to terminal nodes christopher@50: output = list() christopher@45: if binarySequence == concatenate([1],repeat([0],len(binarySequence)-1)): christopher@50: output.append(Node('N',metricalWeight)) christopher@45: christopher@45: # If matching to a Rest type, add to terminal nodes christopher@45: elif binarySequence == repeat([0],len(binarySequence)): christopher@50: output.append(Node('R',metricalWeight)) christopher@50: christopher@50: elif level+1 == Lmax: christopher@50: 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: output.append(Node('N',metricalWeight)) christopher@45: christopher@45: # Keep subdividing by the subdivisor of the next level christopher@50: else: christopher@45: subBinarySequences = subdivide(binarySequence, subdivisionSequence[level+1]) christopher@45: subWeightSequences = concatenate([metricalWeight],repeat([weightSequence[level+1]],subdivisionSequence[level+1]-1)) christopher@45: for a in range(len(subBinarySequences)): christopher@50: output = output + recursive_tree(subBinarySequences[a], subdivisionSequence, weightSequence, subWeightSequences[a], level+1, Lmax) christopher@50: christopher@50: return output christopher@45: christopher@45: def get_syncopation(bar, parameters = None): christopher@45: syncopation = None christopher@50: naughtyglobal = 0 christopher@45: christopher@45: binarySequence = bar.get_binary_sequence() christopher@45: subdivisionSequence = bar.get_subdivision_sequence() christopher@45: christopher@45: # LHL can only measure monorhythms christopher@45: if get_rhythm_category(binarySequence, subdivisionSequence) == 'poly': christopher@45: print 'Warning: LHL model detects polyrhythms so returning None.' christopher@50: elif bar.is_empty(): christopher@50: print 'LHL model detects empty bar so returning -1.' christopher@50: syncopation = -1 christopher@45: else: christopher@45: # set defaults christopher@50: Lmax = 10 christopher@45: weightSequence = range(0,-Lmax-1,-1) christopher@45: # if parameters are specified by users, check their validities and update parameters if valid christopher@45: if parameters!= None: christopher@45: if 'Lmax' in parameters: christopher@45: Lmax = parameters['Lmax'] christopher@45: if 'W' in parameters: christopher@45: weightSequence = parameters['W'] christopher@45: christopher@45: if not are_parameters_valid(Lmax, weightSequence, subdivisionSequence): christopher@45: print 'Error: the given parameters are not valid.' christopher@45: else: christopher@50: christopher@50: # For the rhythm in the current bar, process its tree structure and store the terminal nodes christopher@50: terminalNodes = recursive_tree(ceiling(binarySequence), subdivisionSequence, weightSequence, weightSequence[0],0, Lmax) christopher@50: christopher@50: # save the terminal nodes on the current bar so that christopher@50: # the next bar can access them... christopher@50: bar.LHLterminalNodes = terminalNodes christopher@50: christopher@50: # If there is rhythm in the previous bar and we've already processed it christopher@45: prevbar = bar.get_previous_bar() christopher@50: if prevbar != None and prevbar.is_empty() != True: christopher@50: # get its LHL tree if it has one christopher@50: try: christopher@50: prevbarNodes = prevbar.LHLterminalNodes christopher@50: except AttributeError: christopher@50: prevbarNodes = [] christopher@50: christopher@50: # find the final note node in the previous bar: christopher@50: if len(prevbarNodes)>0: christopher@50: i = len(prevbarNodes) - 1 christopher@45: # Only keep the last note-type node christopher@50: while prevbarNodes[i].nodeType != 'N' and i>=0: christopher@50: i = i-1 christopher@50: # prepend the note to the terminal node list for this bar christopher@50: terminalNodes = [ prevbarNodes[i] ] + terminalNodes christopher@50: christopher@45: christopher@50: # Search for the NR pairs that contribute to syncopation, then add the weight-difference to the NRpairSyncopation list christopher@45: NRpairSyncopation = [] christopher@45: for i in range(len(terminalNodes)-1,0,-1): christopher@45: if terminalNodes[i].nodeType == 'R': christopher@45: for j in range(i-1, -1, -1): christopher@45: if (terminalNodes[j].nodeType == 'N') & (terminalNodes[i].metricalWeight >= terminalNodes[j].metricalWeight): christopher@45: NRpairSyncopation.append(terminalNodes[i].metricalWeight - terminalNodes[j].metricalWeight) christopher@45: break christopher@45: christopher@45: # If there are syncopation, sum all the local syncopation values stored in NRpairSyncopation list christopher@45: if len(NRpairSyncopation) != 0: christopher@45: syncopation = sum(NRpairSyncopation) christopher@45: # If no syncopation, the value is -1; christopher@45: elif len(terminalNodes) != 0: christopher@45: syncopation = -1 christopher@45: christopher@45: return syncopation christopher@45: christopher@45: