Mercurial > hg > syncopation-dataset
changeset 28:5de1cb45c145
Parameters setting implemented.
author | csong <csong@eecs.qmul.ac.uk> |
---|---|
date | Sun, 12 Apr 2015 22:34:35 +0100 |
parents | ed29ed80635c |
children | 7a1730bbf15a |
files | Syncopation models/LHL.py Syncopation models/SG.py Syncopation models/TMC.py Syncopation models/basic_functions.py Syncopation models/parameter_setter.py Syncopation models/syncopation.py |
diffstat | 6 files changed, 177 insertions(+), 174 deletions(-) [+] |
line wrap: on
line diff
--- a/Syncopation models/LHL.py Sun Apr 12 15:55:12 2015 +0100 +++ b/Syncopation models/LHL.py Sun Apr 12 22:34:35 2015 +0100 @@ -4,6 +4,7 @@ ''' from basic_functions import concatenate, repeat, subdivide, ceiling, get_rhythm_category +from parameter_setter import are_parameters_valid terminalNodes = [] # Global variable, storing all the terminal nodes from recursive tree structure in time order @@ -30,10 +31,6 @@ for a in range(len(subBinarySequences)): recursive_tree(subBinarySequences[a], subdivisionSequence, weightSequence, subWeightSequences[a], level+1) -#!!!! needs fixing -def are_parameters_valid(parameters): - areValid = True -# if 'Lmax' not in parameters : def get_syncopation(bar, parameters = None): del terminalNodes[:] @@ -42,54 +39,55 @@ binarySequence = bar.get_binary_sequence() subdivisionSequence = bar.get_subdivision_sequence() + # LHL can only measure monorhythms if get_rhythm_category(binarySequence, subdivisionSequence) == 'poly': print 'Warning: LHL model detects polyrhythms so returning None.' else: - # If the parameters are not given, use the default settings - if parameters == None: - Lmax = 5 - weightSequence = range(0,-Lmax,-1) # i.e. [0,-1,-2,-3,-4] + # set defaults + Lmax = 5 + weightSequence = range(0,-Lmax,-1) + # if parameters are specified by users, check their validities and update parameters if valid + if parameters!= None: + if 'Lmax' in parameters: + Lmax = parameters['Lmax'] + if 'W' in parameters: + weightSequence = parameters['W'] + + if not are_parameters_valid(Lmax, weightSequence, subdivisionSequence): + print 'Error: the given parameters are not valid.' else: - if are_parameters_valid(parameters): - Lmax = parameters['Lmax'] - weightSequence = parameters['W'] - else: - pass - #raise InvalidParameterError + # If there is rhythm in previous bar, process its tree structure + if bar.get_previous_bar() != None: + prebarBinarySequence = bar.get_previous_bar().get_binary_sequence() + recursive_tree(ceiling(prebarBinarySequence), subdivisionSequence, weightSequence, weightSequence[0],0) + + # Only keep the last note-type node + while terminalNodes[-1].nodeType != 'N': + del terminalNodes[-1] + del terminalNodes[0:-1] - # If there is rhythm in previous bar, process its tree structure - if bar.get_previous_bar() != None: - prebarBinarySequence = bar.get_previous_bar().get_binary_sequence() - recursive_tree(ceiling(prebarBinarySequence), subdivisionSequence, weightSequence, weightSequence[0],0) + # For the rhythm in the current bar, process its tree structure and store the terminal nodes + recursive_tree(ceiling(binarySequence), subdivisionSequence, weightSequence, weightSequence[0],0) - # Only keep the last note-type node - while terminalNodes[-1].nodeType != 'N': - del terminalNodes[-1] - del terminalNodes[0:-1] + # for t in terminalNodes: + # print '<', t.nodeType, t.metricalWeight, '>' - # For the rhythm in the current bar, process its tree structure and store the terminal nodes - recursive_tree(ceiling(binarySequence), subdivisionSequence, weightSequence, weightSequence[0],0) - - # for t in terminalNodes: - # print '<', t.nodeType, t.metricalWeight, '>' + # Search for the NR pairs that contribute to syncopation,then add the weight-difference to the NRpairSyncopation list + NRpairSyncopation = [] + for i in range(len(terminalNodes)-1,0,-1): + if terminalNodes[i].nodeType == 'R': + for j in range(i-1, -1, -1): + if (terminalNodes[j].nodeType == 'N') & (terminalNodes[i].metricalWeight >= terminalNodes[j].metricalWeight): + NRpairSyncopation.append(terminalNodes[i].metricalWeight - terminalNodes[j].metricalWeight) + break + #print NRpairSyncopation - # Search for the NR pairs that contribute to syncopation, - # then add the weight-difference to the NRpairSyncopation list - NRpairSyncopation = [] - for i in range(len(terminalNodes)-1,0,-1): - if terminalNodes[i].nodeType == 'R': - for j in range(i-1, -1, -1): - if (terminalNodes[j].nodeType == 'N') & (terminalNodes[i].metricalWeight >= terminalNodes[j].metricalWeight): - NRpairSyncopation.append(terminalNodes[i].metricalWeight - terminalNodes[j].metricalWeight) - break - #print NRpairSyncopation - - # If there are syncopation, sum all the local syncopation values stored in NRpairSyncopation list - if len(NRpairSyncopation) != 0: - syncopation = sum(NRpairSyncopation) - # If no syncopation, the value is -1; - elif len(terminalNodes) != 0: - syncopation = -1 + # If there are syncopation, sum all the local syncopation values stored in NRpairSyncopation list + if len(NRpairSyncopation) != 0: + syncopation = sum(NRpairSyncopation) + # If no syncopation, the value is -1; + elif len(terminalNodes) != 0: + syncopation = -1 return syncopation
--- a/Syncopation models/SG.py Sun Apr 12 15:55:12 2015 +0100 +++ b/Syncopation models/SG.py Sun Apr 12 22:34:35 2015 +0100 @@ -5,9 +5,7 @@ ''' from basic_functions import get_H, velocity_sequence_to_min_timespan, get_rhythm_category, upsample_velocity_sequence -from TMC import find_L -#def get_syncopation(seq, subdivision_seq, weight_seq, L_max, rhythm_category): def get_syncopation(bar, parameters = None): syncopation = None velocitySequence = bar.get_velocity_sequence() @@ -18,68 +16,70 @@ else: #velocitySequence = velocity_sequence_to_min_timespan(velocitySequence) # converting to the minimum time-span format - # If the parameters are not given, use the default settings - if parameters == None: - Lmax = 5 - weightSequence = range(Lmax+1) # i.e. [0,1,2,3,4,5] + # set the defaults + Lmax = 5 + weightSequence = range(Lmax+1) # i.e. [0,1,2,3,4,5] + if parameters!= None: + if 'Lmax' in parameters: + Lmax = parameters['Lmax'] + if 'W' in parameters: + weightSequence = parameters['W'] + + if not are_parameters_valid(Lmax, weightSequence, subdivisionSequence): + print 'Error: the given parameters are not valid.' else: - if are_parameters_valid(parameters): - Lmax = parameters['Lmax'] - weightSequence = parameters['W'] - else: - pass - #raise InvalidParameterError + # generate the metrical weights of level Lmax, and upsample(stretch) the velocity sequence to match the length of H + H = get_H(weightSequence,subdivisionSequence, Lmax) + velocitySequence = upsample_velocity_sequence(velocitySequence, len(H)) - syncopation = 0 - # generate the metrical weights of level Lmax, and upsample(stretch) the velocity sequence to match the length of H - H = get_H(weightSequence,subdivisionSequence, Lmax) - velocitySequence = upsample_velocity_sequence(velocitySequence, len(H)) + # The ave_dif_neighbours function calculates the (weighted) average of the difference between the note at a certain index and its neighbours in a certain metrical level + def ave_dif_neighbours(index, level): - # The ave_dif_neighbours function calculates the (weighted) average of the difference between the note at a certain index and its neighbours in a certain metrical level - def ave_dif_neighbours(index, level): + averages = [] + parameterGarma = 0.8 + + # The findPre function is to calculate the index of the previous neighbour at a certain metrical level. + def find_pre(index, level): + preIndex = (index - 1)%len(H) # using % is to restrict the index varies within range(0, len(H)) + while(H[preIndex] > level): + preIndex = (preIndex - 1)%len(H) + #print 'preIndex', preIndex + return preIndex - averages = [] - parameterGarma = 0.8 + # The findPost function is to calculate the index of the next neighbour at a certain metrical level. + def find_post(index, level): + postIndex = (index + 1)%len(H) + while(H[postIndex] > level): + postIndex = (postIndex + 1)%len(H) + #print 'postIndex', postIndex + return postIndex + + # The dif function is to calculate a difference level factor between two notes (at note position index1 and index 2) in velocity sequence + def dif(index1,index2): + parameterBeta = 0.5 + dif_v = velocitySequence[index1]-velocitySequence[index2] + dif_h = abs(H[index1]-H[index2]) + dif = dif_v*(parameterBeta*dif_h/4+1-parameterBeta) + #print 'dif', dif + return dif + + # From the highest to the lowest metrical levels where the current note resides, calculate the difference between the note and its neighbours at that level + for l in range(level, max(H)+1): + ave = (parameterGarma*dif(index,find_pre(index,l))+dif(index,find_post(index,l)) )/(1+parameterGarma) + averages.append(ave) + #print 'averages', averages + return averages + + # if the upsampling was successfully done + if velocitySequence != None: + syncopation = 0 + # Calculate the syncopation value for each note + for index in range(len(velocitySequence)): + if velocitySequence[index] != 0: # Onset detected + h = H[index] + # Syncopation potential according to its metrical level, which is equal to the metrical weight + potential = 1 - pow(0.5,h) + level = h # Metrical weight is equal to its metrical level + syncopation += min(ave_dif_neighbours(index, level))*potential - # The findPre function is to calculate the index of the previous neighbour at a certain metrical level. - def find_pre(index, level): - preIndex = (index - 1)%len(H) # using % is to restrict the index varies within range(0, len(H)) - while(H[preIndex] > level): - preIndex = (preIndex - 1)%len(H) - #print 'preIndex', preIndex - return preIndex - - # The findPost function is to calculate the index of the next neighbour at a certain metrical level. - def find_post(index, level): - postIndex = (index + 1)%len(H) - while(H[postIndex] > level): - postIndex = (postIndex + 1)%len(H) - #print 'postIndex', postIndex - return postIndex - - # The dif function is to calculate a difference level factor between two notes (at note position index1 and index 2) in velocity sequence - def dif(index1,index2): - parameterBeta = 0.5 - dif_v = velocitySequence[index1]-velocitySequence[index2] - dif_h = abs(H[index1]-H[index2]) - dif = dif_v*(parameterBeta*dif_h/4+1-parameterBeta) - #print 'dif', dif - return dif - - # From the highest to the lowest metrical levels where the current note resides, calculate the difference between the note and its neighbours at that level - for l in range(level, max(H)+1): - ave = (parameterGarma*dif(index,find_pre(index,l))+dif(index,find_post(index,l)) )/(1+parameterGarma) - averages.append(ave) - #print 'averages', averages - return averages - - # Calculate the syncopation value for each note - for index in range(len(velocitySequence)): - if velocitySequence[index] != 0: # Onset detected - h = H[index] - # Syncopation potential according to its metrical level, which is equal to the metrical weight - potential = 1 - pow(0.5,h) - level = h # Metrical weight is equal to its metrical level - syncopation += min(ave_dif_neighbours(index, level))*potential - return syncopation
--- a/Syncopation models/TMC.py Sun Apr 12 15:55:12 2015 +0100 +++ b/Syncopation models/TMC.py Sun Apr 12 22:34:35 2015 +0100 @@ -5,6 +5,7 @@ ''' from basic_functions import get_H, ceiling, velocity_sequence_to_min_timespan, get_rhythm_category +from parameter_setter import are_parameters_valid # The get_metricity function calculates the metricity for a binary sequence with given sequence of metrical weights in a certain metrical level. def get_metricity(binarySequence, H): @@ -55,30 +56,31 @@ if get_rhythm_category(binarySequence, subdivisionSequence) == 'poly': print 'Warning: TMC model detects polyrhythms so returning None.' else: - binarySequence = velocity_sequence_to_min_timespan(binarySequence) # converting to the minimum time-span format + + # set the defaults + Lmax = 5 + weightSequence = range(Lmax+1,0,-1) # i.e. [6,5,4,3,2,1] + + if parameters!= None: + if 'Lmax' in parameters: + Lmax = parameters['Lmax'] + if 'W' in parameters: + weightSequence = parameters['W'] - # If the parameters are not given, use the default settings - if parameters == None: - Lmax = 5 - weightSequence = range(Lmax+1,0,-1) # i.e. [6,5,4,3,2,1] + if not are_parameters_valid(Lmax, weightSequence, subdivisionSequence): + print 'Error: the given parameters are not valid.' else: - if are_parameters_valid(parameters): - Lmax = parameters['Lmax'] - weightSequence = parameters['W'] - else: - pass - #raise InvalidParameterError + binarySequence = velocity_sequence_to_min_timespan(binarySequence) # converting to the minimum time-span format + L = find_L(binarySequence, Lmax, weightSequence, subdivisionSequence) + if L != None: + #? generate the metrical weights of the lowest level, + #? using the last matching_level number of elements in the weightSequence, to make sure the last element is 1 + H = get_H (weightSequence[-(L+1):], subdivisionSequence, L) + + metricity = get_metricity(binarySequence, H) # converting to binary sequence then calculate metricity + maxMetricity = get_max_metricity(binarySequence, H) - L = find_L(binarySequence, Lmax, weightSequence, subdivisionSequence) - if L != None: - #? generate the metrical weights of the lowest level, - #? using the last matching_level number of elements in the weightSequence, to make sure the last element is 1 - H = get_H (weightSequence[-(L+1):], subdivisionSequence, L) - - metricity = get_metricity(binarySequence, H) # converting to binary sequence then calculate metricity - maxMetricity = get_max_metricity(binarySequence, H) - - syncopation = maxMetricity - metricity + syncopation = maxMetricity - metricity return syncopation
--- a/Syncopation models/basic_functions.py Sun Apr 12 15:55:12 2015 +0100 +++ b/Syncopation models/basic_functions.py Sun Apr 12 22:34:35 2015 +0100 @@ -81,10 +81,11 @@ # upsample a velocity sequence to certain length, e.g. [1,1] to [1,0,0,0,1,0,0,0] def upsample_velocity_sequence(velocitySequence, length): - upsampledVelocitySequence = [0]*length + upsampledVelocitySequence = None if length%len(velocitySequence) != 0: print 'Error: the velocity sequence can only be upsampled to the interger times of its length.' else: + upsampledVelocitySequence = [0]*length scalingFactor = length/len(velocitySequence) for index in range(len(velocitySequence)): upsampledVelocitySequence[index*scalingFactor] = velocitySequence[index] @@ -214,35 +215,3 @@ # return subdivision_seq - - # The split_by_bar function seperates the score representation of rhythm by bar lines, - # resulting in a list representingbar-by-bar rhythm sequence, - # e.g. rhythm = ['|',[ts1,td1,v1], [ts2,td2,v2], '|',[ts3,td3,v3],'|'...] - # rhythm_bybar = [ [ [ts1,td1,v1], [ts2,td2,v2] ], [ [ts3,td3,v3] ], [...]] -# def split_by_bar(rhythm): -# rhythm_bybar = [] -# bar_index = [] -# for index in range(len(rhythm)): -# if rhythm[index] == '|': - -# return rhythm_bybar - -# def yseq_to_vseq(yseq): -# vseq = [] - -# return vseq - - -# # testing -# print find_prime_factors(10) -# print find_prime_factors(2) -# print find_prime_factors(12) - - -# print is_prime(1) # False -# print is_prime(2) # True -# print is_prime(3) # True -# print is_prime(29) # True -# print is_prime(345) # False -# print is_prime(999979) # True -# print is_prime(999981) # False \ No newline at end of file
--- a/Syncopation models/parameter_setter.py Sun Apr 12 15:55:12 2015 +0100 +++ b/Syncopation models/parameter_setter.py Sun Apr 12 22:34:35 2015 +0100 @@ -3,10 +3,12 @@ Institution: Centre for Digital Music, Queen Mary University of London ''' -# Set the parameters: subdivision_seq, weight_seq, L_max, strong_beat_level +# Set the parameters: time-signature, subdivision-sequence, strong-beat-level; Lmax; weight-sequence +# Important condition: Lmax needs to be no less than the length of subdivision-sequence and the length of weight-sequence + # {'key': time-signature} : -# {'value': [subdivision_seq, theoretical beat-level represented by index in the subdivision_seq list]} +# {'value': [subdivision-sequence, theoretical beat-level represented by index in the subdivision-sequence list]} timeSignatureBase = { '2/2': [[1,2,2,2,2,2],1], '3/2': [[1,3,2,2,2,2],1], @@ -23,6 +25,7 @@ '12/8':[[1,2,2,3,2,2],2], } + def add_time_signature(timeSignature, subdivisionSequence, beatLevel): if is_time_signature_valid(timeSignature,subdivisionSequence,beatLevel): if timeSignature in timesigBase: @@ -69,16 +72,44 @@ for timeSignature, settings in data.items(): print timeSignature, settings -# def get_subdivision_seq(timeSignature): -# if timeSignature in readTimesig(): -# return timesigBase[timesig][0] -# else: -# print 'Error: the subdivision sequence for this time-signature is not defined.' -# return None -# def get_beat_level(timesig): -# if timesig in readTimesig(): -# return timesigBase[timesig][1] -# else: -# print 'Error: the subdivision sequence for this time-signature is not defined.' -# return None +def are_parameters_valid(Lmax, weightSequence, subdivisionSequence): + + # is_Lmax_valid() checks: + # 1. if Lmax is a non-negative integer + # 2. if Lmax is higher than the length of weightSequence and subdivisionSequence + def is_Lmax_valid(): + isValid = False + if isinstance(Lmax,int) and Lmax > 0: + if Lmax <= len(subdivisionSequence): + if Lmax <= len(weightSequence): + isValid = True + else: + print """Error: Lmax exceeds the length of subdivision-sequence. + Either reduce Lmax, or extend subdivision-sequence through updating time-signature (refer to update_time_signature function). """ + else: + print """Error: Lmax exceeds the length of weight-sequence. Either reduce Lmax, or provide a new weight-sequence + whose length is greater or equal to Lmax.""" + else: + print 'Error: Lmax needs to be a positive integer.' + return isValid + + # is_weight_sequence_valid() checks: + # 1. weightSequence is a list of numbers + # 2. the length of weightSequence is no less than Lmax + def is_weight_sequence_valid(): + isValid = False + if isinstance(weightSequence,list) and weightSequence == [i for i in weightSequence if isinstance(i,int)]: + if len(weightSequence) >= Lmax: + isValid = True + else: + print 'Error: the length of weight-sequence needs to be greater or equal to Lmax.' + else: + print 'Error: the weight-sequence needs to be a list of integers.' + return isValid + + + if is_Lmax_valid() and is_weight_sequence_valid(): + return True + else: + return False
--- a/Syncopation models/syncopation.py Sun Apr 12 15:55:12 2015 +0100 +++ b/Syncopation models/syncopation.py Sun Apr 12 22:34:35 2015 +0100 @@ -7,9 +7,12 @@ def sync_perbar_permodel (model, bar, parameters): return model.get_syncopation(bar, parameters) - def syncopation_barlist_permodel(model, barlist, parameters): + def syncopation_barlist_permodel(model, source, parameters): total = 0 numberOfNotes = 0 + + + for bar in barlist: if sync_perbar_permodel(model, bar, parameters) != None: total += sync_perbar_permodel(model, bar, parameters)