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