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