Mercurial > hg > syncopation-dataset
diff 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 |
line wrap: on
line diff
--- a/Syncopation models/LHL.py Fri Mar 21 15:49:46 2014 +0000 +++ b/Syncopation models/LHL.py Sun Oct 05 21:52:41 2014 +0100 @@ -1,218 +1,67 @@ ''' Author: Chunyang Song Institution: Centre for Digital Music, Queen Mary University of London +''' -** Longuet-Higgins and Lee's Model (LHL) ** +from basic_functions import concatenate, repeat, subdivide, ceiling -Algorithm: +terminal_nodes = [] # Global variable, storing all the terminal nodes from recursive tree structure in time order -Only applicable to simple rhtyhms. +# Each terminnal node contains two properties: its node type (note or rest) and its metrical weight. +class Node: + def __init__(self,node_type,metrical_weight): + self.node_type = node_type + self.metrical_weight = metrical_weight -Generate tree structure: -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]; -2) If after subdivision, a node has at least one branch that is full rest or one note, then it cannot be subdivided; -3) Weight of node, initialized as 0, decrease by 1 with increasing depth of the tree. +# This function will recurse the tree for a binary sequence and return a sequence containing the terminal nodes in time order. +def recursive_tree(seq, subdivision_seq, weight_seq, metrical_weight, level): + if seq == concatenate([1],repeat([0],len(seq)-1)): # If matching to a Note type, add to terminal nodes + terminal_nodes.append(Node('N',metrical_weight)) -Assign each node in the tree three attributes - weight, pos, event; -Search for Note-Rest pairs that Note's weight is no more than Rest's weight; -Syncopation for such a pair is the difference in weights; -Total Syncopation value for non-syncopation rhythms is -1; for syncopated rhythms is the sum of syncopation of all notes. + elif seq == repeat([0],len(seq)): # If matching to a Rest type, add to terminal nodes + terminal_nodes.append(Node('R',metrical_weight)) -In order to evaluate LHL models with the others, re-scale LHL's measures to non-negative. -Apart from the immeasuable rhythms, add 1 to each syncopation value. + else: # Keep subdividing by the subdivisor of the next level + sub_seq = subdivide(seq, subdivision_seq[level+1]) + sub_weight_seq = concatenate([metrical_weight],repeat([weight_seq[level+1]],subdivision_seq[level+1]-1)) + for a in range(len(sub_seq)): + recursive_tree(sub_seq[a], subdivision_seq, weight_seq, sub_weight_seq[a], level+1) -''' -from MeterStructure import MeterStructure +# This function calculate syncoaption score for LHL model. +def get_syncopation(seq, subdivision_seq, weight_seq, prebar_seq, rhythm_category): + syncopation = None + if rhythm_category == 'poly': + print 'Error: LHL model cannot deal with polyrhythms.' + else: + # If there is rhythm in previous bar, process its tree structure + if prebar_seq != None: + recursive_tree(ceiling(prebar_seq), subdivision_seq, weight_seq, weight_seq[0],0) + + # Only keep the last note-type node + while terminal_nodes[-1].node_type != 'N': + del terminal_nodes[-1] + del terminal_nodes[0:-1] -class Node: - def __init__(self, pos, event): - self.pos = pos - self.event = event + # For the rhythm in the current bar, process its tree structure and store the terminal nodes + recursive_tree(ceiling(seq), subdivision_seq, weight_seq, weight_seq[0],0) + + # for t in terminal_nodes: + # print '<', t.node_type, t.metrical_weight, '>' - def assignWeight(self, time_sig): - ms = MeterStructure(time_sig) - # Retrieve the LHL meter hierarchy in one bar; - #(e.g. weight = [0, -4, -3, -4, -2, -4, -3, -4, -1, -4, -3, -4, -2, -4, -3, -4] in 4/4 meter) - weights = ms.getLHLWeights(1) - - # find the metrical weight of the note that locates at certain position(self.pos) - # index is the corresponding position of "self.pos" in the "weights" list - index = int(((abs(self.pos)%48)/48.0)*len(weights)) - self.weight = weights[index] + # Search for the NR pairs that contribute to syncopation, add the weight-difference to the NR_pair_syncopation list + NR_pair_syncopation = [] + for i in range(len(terminal_nodes)-1,0,-1): + if terminal_nodes[i].node_type == 'R': + for j in range(i-1, -1, -1): + if (terminal_nodes[j].node_type == 'N') & (terminal_nodes[i].metrical_weight >= terminal_nodes[j].metrical_weight): + NR_pair_syncopation.append(terminal_nodes[i].metrical_weight - terminal_nodes[j].metrical_weight) + break + #print NR_pair_syncopation -class Tree: - def __init__(self): - self.nodes = [] + # If no syncopation, the value is -1; otherwise, sum all the local syncopation values stored in NR_pair_syncopation list + if len(NR_pair_syncopation) != 0: + syncopation = sum(NR_pair_syncopation) + elif len(terminal_nodes) != 0: + syncopation = -1 - def add(self, Node): - self.nodes.append (Node) - - def getWeight(self, time_sig): - for n in self.nodes: - n.assignWeight(time_sig) - - def display(self): - print 'Pos, Event, Weight' - for n in self.nodes: - print '%02d %s %02d' % (n.pos, n.event, n.weight) - - -def subdivide(sequence, segments_num): - subSeq = [] - if len(sequence) % segments_num != 0: - print 'Error: rhythm segment cannot be equally subdivided.' - else: - n = len(sequence) / segments_num - start , end = 0, n - for i in range(segments_num): - subSeq.append(sequence[start : end]) - start = end - end = end + n - - return subSeq - - -def eventType (sequence): - if not(1 in sequence): - event = 'R' # Full rest - elif sequence[0] == 1 and not(1 in sequence[1:]): - event = 'N' # Only one on-beat note - else: - event = 'D' # Divisable - - return event - -def splitInto2 (sequence, tree, pos, isRecursive): - - subs = subdivide(sequence, 2) - - for s in subs: - if eventType(s) == 'R' or eventType(s) == 'N': - #print 'test', s, eventType(s), pos - tree.add( Node(pos, eventType(s)) ) - else: - if isRecursive: - #print 'test', s, eventType(s), pos - splitInto2 (s, tree, pos, True) - else: - splitInto3 (s, tree, pos) - - pos = pos + len(sequence) / 2 - - return tree - -def splitInto3 (sequence, tree, pos): - - subs = subdivide(sequence, 3) - - for s in subs: - if eventType(s) == 'R' or eventType(s) == 'N': - #print 'test', s, eventType(s), pos - tree.add( Node(pos, eventType(s)) ) - - else: - splitInto2 (s, tree, pos, True) - - pos = pos + len(sequence) / 3 - - return tree - - -def createTree(rhythm, time_sig, bar): - t = Tree() - # The root is the rhtyhm in a entire bar, has weight 0, at position 0 - weight, pos, event = 0 , 0, eventType(rhythm) - root = Node (pos, event) - if '2/4' in time_sig: - t.add ( Node(-24, 'N') ) # Treat the last metronome beat in the previous bar as a sounded note - - if event == 'D': - t = splitInto2(rhythm, t, pos, True) - else: - t.add (root) - - elif '4/4' in time_sig: - t.add ( Node(-12, 'N') ) # Treat the last metronome beat in the previous bar as a sounded note - - if event == 'D': - t = splitInto2(rhythm, t, pos, True) - else: - t.add (root) - - elif '3/4' in time_sig: - t.add ( Node(-8, 'N') ) # Treat the last metronome beat in the previous bar as a sounded note - - segments = subdivide (rhythm, bar) - - for s in segments: - if eventType(s) == 'D': - t = splitInto3(s, t, pos) - pos = pos + len(rhythm)/bar - - - elif '6/8' in time_sig: - t.add ( Node(-8, 'N') ) # Treat the last metronome beat in the previous bar as a sounded note - - segments = subdivide (rhythm, bar) - - for s in segments: - if eventType(s) == 'D': - t = splitInto2(s, t, pos, False) - pos = pos + len(rhythm)/bar - - else: - print 'This time signature is not defined. Choose between 4/4, 3/4 or 6/8' - - return t - - -def lhl(rhythm, time_sig, category, bar): - measures = [] - - if 'poly' in category: - return -1 - else: - tree = createTree(rhythm, time_sig, bar) - tree.getWeight(time_sig) - #tree.display() - - # find NR-pair that N's weight <= R's weight, add difference in weight to measures[] - N_weight = tree.nodes[0].weight - size = len(tree.nodes) - - for i in range(1, size): - - if tree.nodes[i].event == 'N': - N_weight = tree.nodes[i].weight - - if tree.nodes[i].event == 'R' and tree.nodes[i].weight >= N_weight: - measures.append(tree.nodes[i].weight - N_weight ) - - # Calculate syncopation - if len(measures) == 0: # syncopation of non-syncopated rhythm is -1 - syncopation = -1 - else: - syncopation = sum(measures) # syncopation of syncopated rhythm is the sum of measures[] - - return syncopation + 1 # For evaluation purpose, scale up by 1 to make scores above 0 - - -# Retrieve the stimuli -#f = file('stimuli.txt') -f = file('stimuli_34only.txt') - -#Calculate syncopation for each rhythm pattern -while True: - line = f.readline().split(';') - if len(line) == 1: - break - else: - sti_name = line[0] - rhythmString = line[1].split() - time_sig = line[2] - category = line[3] - bar = int(line[4]) - - rhythm = map(int,rhythmString[0].split(',')) - - print sti_name, lhl(rhythm, time_sig, category, bar) \ No newline at end of file + return syncopation