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: christopher@45: from basic_functions import get_H, velocity_sequence_to_min_timespan, get_rhythm_category, upsample_velocity_sequence christopher@45: from parameter_setter import are_parameters_valid christopher@45: christopher@45: def get_syncopation(bar, parameters = None): christopher@45: syncopation = None christopher@45: velocitySequence = bar.get_velocity_sequence() christopher@45: subdivisionSequence = bar.get_subdivision_sequence() christopher@45: christopher@45: if get_rhythm_category(velocitySequence, subdivisionSequence) == 'poly': christopher@45: print 'Warning: SG model detects polyrhythms so returning None.' christopher@45: else: christopher@45: #velocitySequence = velocity_sequence_to_min_timespan(velocitySequence) # converting to the minimum time-span format christopher@45: christopher@45: # set the defaults christopher@45: Lmax = 5 christopher@45: weightSequence = range(Lmax+1) # i.e. [0,1,2,3,4,5] 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@45: # generate the metrical weights of level Lmax, and upsample(stretch) the velocity sequence to match the length of H christopher@45: H = get_H(weightSequence,subdivisionSequence, Lmax) christopher@45: christopher@45: velocitySequence = upsample_velocity_sequence(velocitySequence, len(H)) christopher@45: christopher@45: # 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 christopher@45: def ave_dif_neighbours(index, level): christopher@45: christopher@45: averages = [] christopher@45: parameterGarma = 0.8 christopher@45: christopher@45: # The findPre function is to calculate the index of the previous neighbour at a certain metrical level. christopher@45: def find_pre(index, level): christopher@45: preIndex = (index - 1)%len(H) # using % is to restrict the index varies within range(0, len(H)) christopher@45: while(H[preIndex] > level): christopher@45: preIndex = (preIndex - 1)%len(H) christopher@45: #print 'preIndex', preIndex christopher@45: return preIndex christopher@45: christopher@45: # The findPost function is to calculate the index of the next neighbour at a certain metrical level. christopher@45: def find_post(index, level): christopher@45: postIndex = (index + 1)%len(H) christopher@45: while(H[postIndex] > level): christopher@45: postIndex = (postIndex + 1)%len(H) christopher@45: #print 'postIndex', postIndex christopher@45: return postIndex christopher@45: christopher@45: # The dif function is to calculate a difference level factor between two notes (at note position index1 and index 2) in velocity sequence christopher@45: def dif(index1,index2): christopher@45: parameterBeta = 0.5 christopher@45: dif_v = velocitySequence[index1]-velocitySequence[index2] christopher@45: dif_h = abs(H[index1]-H[index2]) christopher@45: dif = dif_v*(parameterBeta*dif_h/4+1-parameterBeta) christopher@45: #print 'dif', dif christopher@45: return dif christopher@45: christopher@45: # 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 christopher@45: for l in range(level, max(H)+1): christopher@45: ave = (parameterGarma*dif(index,find_pre(index,l))+dif(index,find_post(index,l)) )/(1+parameterGarma) christopher@45: averages.append(ave) christopher@45: #print 'averages', averages christopher@45: return averages christopher@45: christopher@45: # if the upsampling was successfully done christopher@45: if velocitySequence != None: christopher@45: syncopation = 0 christopher@45: # Calculate the syncopation value for each note christopher@45: for index in range(len(velocitySequence)): christopher@45: if velocitySequence[index] != 0: # Onset detected christopher@45: h = H[index] christopher@45: # Syncopation potential according to its metrical level, which is equal to the metrical weight christopher@45: potential = 1 - pow(0.5,h) christopher@45: level = h # Metrical weight is equal to its metrical level christopher@45: syncopation += min(ave_dif_neighbours(index, level))*potential christopher@45: else: christopher@45: print 'Try giving a bigger Lmax so that the rhythm sequence can be measured by the matching metrical weights sequence (H).' christopher@45: return syncopation