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 repeat, subdivide, ceiling, velocity_sequence_to_min_timespan, get_rhythm_category christopher@45: christopher@45: def get_cost(sequence,nextSequence): christopher@45: sequence = velocity_sequence_to_min_timespan(sequence) # converting to the minimum time-span format christopher@45: christopher@45: if sequence[1:] == repeat([0],len(sequence)-1): # null prototype christopher@45: cost = 0 christopher@45: elif sequence == repeat([1],len(sequence)): # filled prototype christopher@45: cost = 1 christopher@45: elif sequence[0] == 1 and sequence[-1] == 0: # run1 prototype christopher@45: cost = 2 christopher@45: elif sequence[0] == 1 and (nextSequence == None or nextSequence[0] == 0): # run2 prototype christopher@45: cost = 2 christopher@45: elif sequence[-1] == 1 and nextSequence != None and nextSequence[0] == 1: # upbeat prototype christopher@45: cost = 3 christopher@45: elif sequence[0] == 0: # syncopated prototype christopher@45: cost = 5 christopher@45: christopher@45: return cost christopher@45: christopher@45: # This function calculates the syncopation value (cost) for the sequence with the postbar_seq for a certain level. christopher@45: def syncopation_perlevel(subSequences): christopher@45: #print 'subSequences', subSequences christopher@45: total = 0 christopher@45: for l in range(len(subSequences)-1): christopher@45: #print 'cost', get_cost(subSequences[l], subSequences[l+1]) christopher@45: total = total + get_cost(subSequences[l], subSequences[l+1]) christopher@45: #print 'total this level', total christopher@45: normalised = float(total)/(len(subSequences)-1) christopher@45: christopher@45: return normalised christopher@45: christopher@45: def get_syncopation(bar, parameters = None): christopher@45: syncopation = None christopher@45: christopher@50: binarySequence = velocity_sequence_to_min_timespan(bar.get_binary_sequence()) christopher@45: subdivisionSequence = bar.get_subdivision_sequence() christopher@45: christopher@45: # PRS does not handle polyrhythms christopher@45: if get_rhythm_category(binarySequence, subdivisionSequence) == 'poly': christopher@45: print 'Warning: PRS model detects polyrhythms so returning None.' christopher@50: elif bar.is_empty(): christopher@50: print 'Warning: PRS model detects empty bar so returning None.' christopher@45: else: christopher@45: syncopation = 0 christopher@45: christopher@45: # retrieve the binary sequence in the next bar christopher@45: if bar.get_next_bar() != None: christopher@45: nextbarBinarySequence = bar.get_next_bar().get_binary_sequence() christopher@45: else: christopher@45: nextbarBinarySequence = None christopher@45: christopher@45: # numberOfSubSeqs is the number of sub-sequences at a certain metrical level, initialised to be 1 (at the bar level) christopher@45: numberOfSubSeqs = 1 christopher@45: for subdivisor in subdivisionSequence: christopher@45: # numberOfSubSeqs is product of all the subdivisors up to the current level christopher@45: numberOfSubSeqs = numberOfSubSeqs * subdivisor christopher@45: christopher@45: # recursion stops when the length of sub-sequence is less than 2 christopher@45: if len(binarySequence)/numberOfSubSeqs >= 2: christopher@45: # generate sub-sequences and append the next bar sequence christopher@45: subSequences = subdivide(ceiling(binarySequence), numberOfSubSeqs) christopher@45: subSequences.append(nextbarBinarySequence) christopher@45: # adding syncopation at each metrical level to the total syncopation christopher@45: #print 'per level', syncopation_perlevel(subSequences) christopher@45: syncopation += syncopation_perlevel(subSequences) christopher@45: else: christopher@45: break christopher@45: christopher@45: return syncopation