christopher@45: # This python file is a collection of basic functions that are used in the syncopation models. christopher@45: christopher@45: import math christopher@45: christopher@45: # The concatenation function is used to concatenate two sequences. christopher@45: def concatenate(seq1,seq2): christopher@45: return seq1+seq2 christopher@45: christopher@45: # The repetition function is to concatenate a sequence to itself for 'times' number of times. christopher@45: def repeat(seq,times): christopher@45: new_seq = list(seq) christopher@45: if times >= 1: christopher@45: for i in range(times-1): christopher@45: new_seq = concatenate(new_seq,seq) christopher@45: else: christopher@45: #print 'Error: repetition times needs to be no less than 1.' christopher@45: new_seq = [] christopher@45: return new_seq christopher@45: christopher@45: # The subdivision function is to equally subdivide a sequence into 'divisor' number of segments. christopher@45: def subdivide(seq,divisor): christopher@45: subSeq = [] christopher@45: if len(seq) % divisor != 0: christopher@45: print 'Error: rhythmic sequence cannot be equally subdivided.' christopher@45: else: christopher@45: n = len(seq) / divisor christopher@45: start , end = 0, n christopher@45: for i in range(divisor): christopher@45: subSeq.append(seq[start : end]) christopher@45: start = end christopher@45: end = end + n christopher@45: return subSeq christopher@45: christopher@45: christopher@45: # The ceiling function is to round each number inside a sequence up to its nearest integer. christopher@45: def ceiling(seq): christopher@45: seq_ceil = [] christopher@45: for s in seq: christopher@45: seq_ceil.append(int(math.ceil(s))) christopher@45: return seq_ceil christopher@45: christopher@45: # The find_divisor function returns a list of all possible divisors for a length of sequence. christopher@45: def find_divisor(number): christopher@45: divisors = [1] christopher@45: for i in range(2,number+1): christopher@45: if number%i ==0: christopher@45: divisors.append(i) christopher@45: return divisors christopher@45: christopher@45: # The find_divisor function returns a list of all possible divisors for a length of sequence. christopher@45: def find_prime_factors(number): christopher@45: primeFactors = find_divisor(number) christopher@45: christopher@45: # remove 1 because 1 is not prime number christopher@45: del primeFactors[0] christopher@45: christopher@45: # reversely traverse all the divisors list and once find a non-prime then delete christopher@45: for i in range(len(primeFactors)-1,0,-1): christopher@45: # print primeFactors[i], is_prime(primeFactors[i]) christopher@45: if not is_prime(primeFactors[i]): christopher@45: del primeFactors[i] christopher@45: christopher@45: return primeFactors christopher@45: christopher@45: def is_prime(number): christopher@45: isPrime = True christopher@45: # 0 or 1 is not prime numbers christopher@45: if number < 2: christopher@45: isPrime = False christopher@45: # 2 is the only even prime number christopher@45: elif number == 2: christopher@45: pass christopher@45: # all the other even numbers are non-prime christopher@45: elif number % 2 == 0: christopher@45: isPrime = False christopher@45: else: christopher@45: for odd in range(3, int(math.sqrt(number) + 1), 2): christopher@45: if number % odd == 0: christopher@45: isPrime = False christopher@45: return isPrime christopher@45: christopher@45: # upsample a velocity sequence to certain length, e.g. [1,1] to [1,0,0,0,1,0,0,0] christopher@45: def upsample_velocity_sequence(velocitySequence, length): christopher@45: upsampledVelocitySequence = None christopher@45: if length < len(velocitySequence): christopher@45: print 'Error: the requested upsampling length needs to be longer than velocity sequence.' christopher@45: elif length % len(velocitySequence) != 0: christopher@45: print 'Error: velocity sequence can only be upsampled to a interger times of its own length.' christopher@45: else: christopher@45: upsampledVelocitySequence = [0]*length christopher@45: scalingFactor = length/len(velocitySequence) christopher@45: for index in range(len(velocitySequence)): christopher@45: upsampledVelocitySequence[index*scalingFactor] = velocitySequence[index] christopher@45: return upsampledVelocitySequence christopher@45: christopher@45: christopher@45: # convert a velocity sequence to its minimum time-span representation christopher@45: def velocity_sequence_to_min_timespan(velocitySequence): christopher@45: from music_objects import VelocitySequence christopher@45: minTimeSpanVelocitySeq = [1] christopher@45: for divisors in find_divisor(len(velocitySequence)): christopher@45: segments = subdivide(velocitySequence,divisors) christopher@45: if len(segments)!=0: christopher@45: del minTimeSpanVelocitySeq[:] christopher@45: for s in segments: christopher@45: minTimeSpanVelocitySeq.append(s[0]) christopher@45: if sum(minTimeSpanVelocitySeq) == sum(velocitySequence): christopher@45: break christopher@45: return VelocitySequence(minTimeSpanVelocitySeq) christopher@45: christopher@45: """ christopher@45: # convert a note sequence to its minimum time-span representation christopher@45: def note_sequence_to_min_timespan(noteSequence): christopher@45: from music_objects import note_sequence_to_velocity_sequence christopher@45: timeSpanTicks = len(note_sequence_to_velocity_sequence(noteSequence)) christopher@45: # print timeSpanTicks christopher@45: christopher@45: barBinaryArray = [0]*(timeSpanTicks+1) christopher@45: for note in noteSequence: christopher@45: # mark note_on event (i.e. startTime) and note_off event (i.e. endTime = startTime + duration) as 1 in the barBinaryArray christopher@45: barBinaryArray[note.startTime] = 1 christopher@45: barBinaryArray[note.startTime + note.duration] = 1 christopher@45: christopher@45: # convert the barBinaryArray to its minimum time-span representation christopher@45: minBarBinaryArray = velocity_sequence_to_min_timetpan(barBinaryArray[:-1]) christopher@45: print barBinaryArray christopher@45: print minBarBinaryArray christopher@45: delta_t = len(barBinaryArray)/len(minBarBinaryArray) christopher@45: christopher@45: # scale the startTime and duration of each note by delta_t christopher@45: for note in noteSequence: christopher@45: note.startTime = note.startTime/delta_t christopher@45: note.duration = note.duration/delta_t christopher@45: christopher@45: return noteSequence christopher@45: """ christopher@45: christopher@45: # get_note_indices returns all the indices of all the notes in this velocity_sequence christopher@45: def get_note_indices(velocitySequence): christopher@45: noteIndices = [] christopher@45: christopher@45: for index in range(len(velocitySequence)): christopher@45: if velocitySequence[index] != 0: christopher@45: noteIndices.append(index) christopher@45: christopher@45: return noteIndices christopher@45: christopher@45: christopher@45: # The get_H returns a sequence of metrical weight for a certain metrical level (horizontal), christopher@45: # given the sequence of metrical weights in a hierarchy (vertical) and a sequence of subdivisions. christopher@45: def get_H(weightSequence,subdivisionSequence, level): christopher@45: H = [] christopher@45: #print len(weight_seq), len(subdivision_seq), level christopher@45: if (level <= len(subdivisionSequence)-1) and (level <= len(weightSequence)-1): christopher@45: if level == 0: christopher@45: H = repeat([weightSequence[0]],subdivisionSequence[0]) christopher@45: else: christopher@45: H_pre = get_H(weightSequence,subdivisionSequence,level-1) christopher@45: for h in H_pre: christopher@45: H = concatenate(H, concatenate([h], repeat([weightSequence[level]],subdivisionSequence[level]-1))) christopher@45: else: christopher@45: print 'Error: a subdivision factor or metrical weight is not defined for the request metrical level.' christopher@45: return H christopher@45: christopher@45: christopher@45: def calculate_bar_ticks(numerator, denominator, ticksPerQuarter): christopher@45: return (numerator * ticksPerQuarter *4) / denominator christopher@45: christopher@45: christopher@45: def get_rhythm_category(velocitySequence, subdivisionSequence): christopher@45: ''' christopher@45: The get_rhythm_category function is used to detect rhythm category: monorhythm or polyrhythm. christopher@45: For monorhythms, all prime factors of the length of minimum time-span representation of this sequence are christopher@45: elements of its subdivision_seq, otherwise it is polyrhythm; christopher@45: e.g. prime_factors of polyrhythm 100100101010 in 4/4 is [2,3] but subdivision_seq = [1,2,2] for 4/4 christopher@45: ''' christopher@45: rhythmCategory = 'mono' christopher@45: for f in find_prime_factors(len(velocity_sequence_to_min_timespan(velocitySequence))): christopher@45: if not (f in subdivisionSequence): christopher@45: rhythmCategory = 'poly' christopher@45: break christopher@45: return rhythmCategory christopher@45: christopher@45: christopher@45: def string_to_sequence(inputString,typeFunction=float): christopher@45: return map(typeFunction, inputString.split(',')) christopher@45: christopher@71: # find the metrical level L that contains the same number of metrical positions as the length of the binary sequence christopher@71: # if the given Lmax is not big enough to analyse the given sequence, request a bigger Lmax christopher@71: def find_rhythm_Lmax(rhythmSequence, Lmax, weightSequence, subdivisionSequence): christopher@71: L = Lmax christopher@71: christopher@71: # initially assuming the Lmax is not big enough christopher@71: needBiggerLmax = True christopher@71: christopher@71: # from the lowest metrical level (Lmax) to the highest, find the matching metrical level that christopher@71: # has the same length as the length of binary sequence christopher@71: while L >= 0: christopher@71: if len(get_H(weightSequence,subdivisionSequence, L)) == len(rhythmSequence): christopher@71: needBiggerLmax = False christopher@71: break christopher@71: else: christopher@71: L = L - 1 christopher@71: christopher@71: # if need a bigger Lmax, print error message and return None; otherwise return the matching metrical level L christopher@71: if needBiggerLmax: christopher@71: print 'Error: needs a bigger L_max (i.e. the lowest metrical level) to match the given rhythm sequence.' christopher@71: L = None christopher@71: christopher@71: return L christopher@71: christopher@71: christopher@45: # # The get_subdivision_seq function returns the subdivision sequence of several common time-signatures defined by GTTM, christopher@45: # # or ask for the top three level of subdivision_seq manually set by the user. christopher@45: # def get_subdivision_seq(timesig, L_max): christopher@45: # subdivision_seq = [] christopher@45: christopher@45: # if timesig == '2/4' or timesig == '4/4': christopher@45: # subdivision_seq = [1,2,2] christopher@45: # elif timesig == '3/4' or timesig == '3/8': christopher@45: # subdivision_seq = [1,3,2] christopher@45: # elif timesig == '6/8': christopher@45: # subdivision_seq = [1,2,3] christopher@45: # elif timesig == '9/8': christopher@45: # subdivision_seq = [1,3,3] christopher@45: # elif timesig == '12/8': christopher@45: # subdivision_seq = [1,4,3] christopher@45: # elif timesig == '5/4' or timesig == '5/8': christopher@45: # subdivision_seq = [1,5,2] christopher@45: # elif timesig == '7/4' or timesig == '7/8': christopher@45: # subdivision_seq = [1,7,2] christopher@45: # elif timesig == '11/4' or timesig == '11/8': christopher@45: # subdivision_seq = [1,11,2] christopher@45: # else: christopher@45: # print 'Time-signature',timesig,'is undefined. Please indicate subdivision sequence for this requested time-signature, e.g. [1,2,2] for 4/4 meter.' christopher@45: # for i in range(3): christopher@45: # s = int(input('Enter the subdivision factor at metrical level '+str(i)+':')) christopher@45: # subdivision_seq.append(s) christopher@45: christopher@45: # if L_max > 2: christopher@45: # subdivision_seq = subdivision_seq + [2]*(L_max-2) christopher@45: # else: christopher@45: # subdivision_seq = subdivision_seq[0:L_max+1] christopher@45: christopher@45: # return subdivision_seq christopher@45: