christopher@45
|
1 # This python file is a collection of basic functions that are used in the syncopation models.
|
christopher@45
|
2
|
christopher@45
|
3 import math
|
christopher@45
|
4
|
christopher@45
|
5 # The concatenation function is used to concatenate two sequences.
|
christopher@45
|
6 def concatenate(seq1,seq2):
|
christopher@45
|
7 return seq1+seq2
|
christopher@45
|
8
|
christopher@45
|
9 # The repetition function is to concatenate a sequence to itself for 'times' number of times.
|
christopher@45
|
10 def repeat(seq,times):
|
christopher@45
|
11 new_seq = list(seq)
|
christopher@45
|
12 if times >= 1:
|
christopher@45
|
13 for i in range(times-1):
|
christopher@45
|
14 new_seq = concatenate(new_seq,seq)
|
christopher@45
|
15 else:
|
christopher@45
|
16 #print 'Error: repetition times needs to be no less than 1.'
|
christopher@45
|
17 new_seq = []
|
christopher@45
|
18 return new_seq
|
christopher@45
|
19
|
christopher@45
|
20 # The subdivision function is to equally subdivide a sequence into 'divisor' number of segments.
|
christopher@45
|
21 def subdivide(seq,divisor):
|
christopher@45
|
22 subSeq = []
|
christopher@45
|
23 if len(seq) % divisor != 0:
|
christopher@45
|
24 print 'Error: rhythmic sequence cannot be equally subdivided.'
|
christopher@45
|
25 else:
|
christopher@45
|
26 n = len(seq) / divisor
|
christopher@45
|
27 start , end = 0, n
|
christopher@45
|
28 for i in range(divisor):
|
christopher@45
|
29 subSeq.append(seq[start : end])
|
christopher@45
|
30 start = end
|
christopher@45
|
31 end = end + n
|
christopher@45
|
32 return subSeq
|
christopher@45
|
33
|
christopher@45
|
34
|
christopher@45
|
35 # The ceiling function is to round each number inside a sequence up to its nearest integer.
|
christopher@45
|
36 def ceiling(seq):
|
christopher@45
|
37 seq_ceil = []
|
christopher@45
|
38 for s in seq:
|
christopher@45
|
39 seq_ceil.append(int(math.ceil(s)))
|
christopher@45
|
40 return seq_ceil
|
christopher@45
|
41
|
christopher@45
|
42 # The find_divisor function returns a list of all possible divisors for a length of sequence.
|
christopher@45
|
43 def find_divisor(number):
|
christopher@45
|
44 divisors = [1]
|
christopher@45
|
45 for i in range(2,number+1):
|
christopher@45
|
46 if number%i ==0:
|
christopher@45
|
47 divisors.append(i)
|
christopher@45
|
48 return divisors
|
christopher@45
|
49
|
christopher@45
|
50 # The find_divisor function returns a list of all possible divisors for a length of sequence.
|
christopher@45
|
51 def find_prime_factors(number):
|
christopher@45
|
52 primeFactors = find_divisor(number)
|
christopher@45
|
53
|
christopher@45
|
54 # remove 1 because 1 is not prime number
|
christopher@45
|
55 del primeFactors[0]
|
christopher@45
|
56
|
christopher@45
|
57 # reversely traverse all the divisors list and once find a non-prime then delete
|
christopher@45
|
58 for i in range(len(primeFactors)-1,0,-1):
|
christopher@45
|
59 # print primeFactors[i], is_prime(primeFactors[i])
|
christopher@45
|
60 if not is_prime(primeFactors[i]):
|
christopher@45
|
61 del primeFactors[i]
|
christopher@45
|
62
|
christopher@45
|
63 return primeFactors
|
christopher@45
|
64
|
christopher@45
|
65 def is_prime(number):
|
christopher@45
|
66 isPrime = True
|
christopher@45
|
67 # 0 or 1 is not prime numbers
|
christopher@45
|
68 if number < 2:
|
christopher@45
|
69 isPrime = False
|
christopher@45
|
70 # 2 is the only even prime number
|
christopher@45
|
71 elif number == 2:
|
christopher@45
|
72 pass
|
christopher@45
|
73 # all the other even numbers are non-prime
|
christopher@45
|
74 elif number % 2 == 0:
|
christopher@45
|
75 isPrime = False
|
christopher@45
|
76 else:
|
christopher@45
|
77 for odd in range(3, int(math.sqrt(number) + 1), 2):
|
christopher@45
|
78 if number % odd == 0:
|
christopher@45
|
79 isPrime = False
|
christopher@45
|
80 return isPrime
|
christopher@45
|
81
|
christopher@45
|
82 # upsample a velocity sequence to certain length, e.g. [1,1] to [1,0,0,0,1,0,0,0]
|
christopher@45
|
83 def upsample_velocity_sequence(velocitySequence, length):
|
christopher@45
|
84 upsampledVelocitySequence = None
|
christopher@45
|
85 if length < len(velocitySequence):
|
christopher@45
|
86 print 'Error: the requested upsampling length needs to be longer than velocity sequence.'
|
christopher@45
|
87 elif length % len(velocitySequence) != 0:
|
christopher@45
|
88 print 'Error: velocity sequence can only be upsampled to a interger times of its own length.'
|
christopher@45
|
89 else:
|
christopher@45
|
90 upsampledVelocitySequence = [0]*length
|
christopher@45
|
91 scalingFactor = length/len(velocitySequence)
|
christopher@45
|
92 for index in range(len(velocitySequence)):
|
christopher@45
|
93 upsampledVelocitySequence[index*scalingFactor] = velocitySequence[index]
|
christopher@45
|
94 return upsampledVelocitySequence
|
christopher@45
|
95
|
christopher@45
|
96
|
christopher@45
|
97 # convert a velocity sequence to its minimum time-span representation
|
christopher@45
|
98 def velocity_sequence_to_min_timespan(velocitySequence):
|
christopher@45
|
99 from music_objects import VelocitySequence
|
christopher@45
|
100 minTimeSpanVelocitySeq = [1]
|
christopher@45
|
101 for divisors in find_divisor(len(velocitySequence)):
|
christopher@45
|
102 segments = subdivide(velocitySequence,divisors)
|
christopher@45
|
103 if len(segments)!=0:
|
christopher@45
|
104 del minTimeSpanVelocitySeq[:]
|
christopher@45
|
105 for s in segments:
|
christopher@45
|
106 minTimeSpanVelocitySeq.append(s[0])
|
christopher@45
|
107 if sum(minTimeSpanVelocitySeq) == sum(velocitySequence):
|
christopher@45
|
108 break
|
christopher@45
|
109 return VelocitySequence(minTimeSpanVelocitySeq)
|
christopher@45
|
110
|
christopher@45
|
111 """
|
christopher@45
|
112 # convert a note sequence to its minimum time-span representation
|
christopher@45
|
113 def note_sequence_to_min_timespan(noteSequence):
|
christopher@45
|
114 from music_objects import note_sequence_to_velocity_sequence
|
christopher@45
|
115 timeSpanTicks = len(note_sequence_to_velocity_sequence(noteSequence))
|
christopher@45
|
116 # print timeSpanTicks
|
christopher@45
|
117
|
christopher@45
|
118 barBinaryArray = [0]*(timeSpanTicks+1)
|
christopher@45
|
119 for note in noteSequence:
|
christopher@45
|
120 # mark note_on event (i.e. startTime) and note_off event (i.e. endTime = startTime + duration) as 1 in the barBinaryArray
|
christopher@45
|
121 barBinaryArray[note.startTime] = 1
|
christopher@45
|
122 barBinaryArray[note.startTime + note.duration] = 1
|
christopher@45
|
123
|
christopher@45
|
124 # convert the barBinaryArray to its minimum time-span representation
|
christopher@45
|
125 minBarBinaryArray = velocity_sequence_to_min_timetpan(barBinaryArray[:-1])
|
christopher@45
|
126 print barBinaryArray
|
christopher@45
|
127 print minBarBinaryArray
|
christopher@45
|
128 delta_t = len(barBinaryArray)/len(minBarBinaryArray)
|
christopher@45
|
129
|
christopher@45
|
130 # scale the startTime and duration of each note by delta_t
|
christopher@45
|
131 for note in noteSequence:
|
christopher@45
|
132 note.startTime = note.startTime/delta_t
|
christopher@45
|
133 note.duration = note.duration/delta_t
|
christopher@45
|
134
|
christopher@45
|
135 return noteSequence
|
christopher@45
|
136 """
|
christopher@45
|
137
|
christopher@45
|
138 # get_note_indices returns all the indices of all the notes in this velocity_sequence
|
christopher@45
|
139 def get_note_indices(velocitySequence):
|
christopher@45
|
140 noteIndices = []
|
christopher@45
|
141
|
christopher@45
|
142 for index in range(len(velocitySequence)):
|
christopher@45
|
143 if velocitySequence[index] != 0:
|
christopher@45
|
144 noteIndices.append(index)
|
christopher@45
|
145
|
christopher@45
|
146 return noteIndices
|
christopher@45
|
147
|
christopher@45
|
148
|
christopher@45
|
149 # The get_H returns a sequence of metrical weight for a certain metrical level (horizontal),
|
christopher@45
|
150 # given the sequence of metrical weights in a hierarchy (vertical) and a sequence of subdivisions.
|
christopher@45
|
151 def get_H(weightSequence,subdivisionSequence, level):
|
christopher@45
|
152 H = []
|
christopher@45
|
153 #print len(weight_seq), len(subdivision_seq), level
|
christopher@45
|
154 if (level <= len(subdivisionSequence)-1) and (level <= len(weightSequence)-1):
|
christopher@45
|
155 if level == 0:
|
christopher@45
|
156 H = repeat([weightSequence[0]],subdivisionSequence[0])
|
christopher@45
|
157 else:
|
christopher@45
|
158 H_pre = get_H(weightSequence,subdivisionSequence,level-1)
|
christopher@45
|
159 for h in H_pre:
|
christopher@45
|
160 H = concatenate(H, concatenate([h], repeat([weightSequence[level]],subdivisionSequence[level]-1)))
|
christopher@45
|
161 else:
|
christopher@45
|
162 print 'Error: a subdivision factor or metrical weight is not defined for the request metrical level.'
|
christopher@45
|
163 return H
|
christopher@45
|
164
|
christopher@45
|
165
|
christopher@45
|
166 def calculate_bar_ticks(numerator, denominator, ticksPerQuarter):
|
christopher@45
|
167 return (numerator * ticksPerQuarter *4) / denominator
|
christopher@45
|
168
|
christopher@45
|
169
|
christopher@45
|
170 def get_rhythm_category(velocitySequence, subdivisionSequence):
|
christopher@45
|
171 '''
|
christopher@45
|
172 The get_rhythm_category function is used to detect rhythm category: monorhythm or polyrhythm.
|
christopher@45
|
173 For monorhythms, all prime factors of the length of minimum time-span representation of this sequence are
|
christopher@45
|
174 elements of its subdivision_seq, otherwise it is polyrhythm;
|
christopher@45
|
175 e.g. prime_factors of polyrhythm 100100101010 in 4/4 is [2,3] but subdivision_seq = [1,2,2] for 4/4
|
christopher@45
|
176 '''
|
christopher@45
|
177 rhythmCategory = 'mono'
|
christopher@45
|
178 for f in find_prime_factors(len(velocity_sequence_to_min_timespan(velocitySequence))):
|
christopher@45
|
179 if not (f in subdivisionSequence):
|
christopher@45
|
180 rhythmCategory = 'poly'
|
christopher@45
|
181 break
|
christopher@45
|
182 return rhythmCategory
|
christopher@45
|
183
|
christopher@45
|
184
|
christopher@45
|
185 def string_to_sequence(inputString,typeFunction=float):
|
christopher@45
|
186 return map(typeFunction, inputString.split(','))
|
christopher@45
|
187
|
christopher@71
|
188 # find the metrical level L that contains the same number of metrical positions as the length of the binary sequence
|
christopher@71
|
189 # if the given Lmax is not big enough to analyse the given sequence, request a bigger Lmax
|
christopher@71
|
190 def find_rhythm_Lmax(rhythmSequence, Lmax, weightSequence, subdivisionSequence):
|
christopher@71
|
191 L = Lmax
|
christopher@71
|
192
|
christopher@71
|
193 # initially assuming the Lmax is not big enough
|
christopher@71
|
194 needBiggerLmax = True
|
christopher@71
|
195
|
christopher@71
|
196 # from the lowest metrical level (Lmax) to the highest, find the matching metrical level that
|
christopher@71
|
197 # has the same length as the length of binary sequence
|
christopher@71
|
198 while L >= 0:
|
christopher@71
|
199 if len(get_H(weightSequence,subdivisionSequence, L)) == len(rhythmSequence):
|
christopher@71
|
200 needBiggerLmax = False
|
christopher@71
|
201 break
|
christopher@71
|
202 else:
|
christopher@71
|
203 L = L - 1
|
christopher@71
|
204
|
christopher@71
|
205 # if need a bigger Lmax, print error message and return None; otherwise return the matching metrical level L
|
christopher@71
|
206 if needBiggerLmax:
|
christopher@71
|
207 print 'Error: needs a bigger L_max (i.e. the lowest metrical level) to match the given rhythm sequence.'
|
christopher@71
|
208 L = None
|
christopher@71
|
209
|
christopher@71
|
210 return L
|
christopher@71
|
211
|
christopher@71
|
212
|
christopher@45
|
213 # # The get_subdivision_seq function returns the subdivision sequence of several common time-signatures defined by GTTM,
|
christopher@45
|
214 # # or ask for the top three level of subdivision_seq manually set by the user.
|
christopher@45
|
215 # def get_subdivision_seq(timesig, L_max):
|
christopher@45
|
216 # subdivision_seq = []
|
christopher@45
|
217
|
christopher@45
|
218 # if timesig == '2/4' or timesig == '4/4':
|
christopher@45
|
219 # subdivision_seq = [1,2,2]
|
christopher@45
|
220 # elif timesig == '3/4' or timesig == '3/8':
|
christopher@45
|
221 # subdivision_seq = [1,3,2]
|
christopher@45
|
222 # elif timesig == '6/8':
|
christopher@45
|
223 # subdivision_seq = [1,2,3]
|
christopher@45
|
224 # elif timesig == '9/8':
|
christopher@45
|
225 # subdivision_seq = [1,3,3]
|
christopher@45
|
226 # elif timesig == '12/8':
|
christopher@45
|
227 # subdivision_seq = [1,4,3]
|
christopher@45
|
228 # elif timesig == '5/4' or timesig == '5/8':
|
christopher@45
|
229 # subdivision_seq = [1,5,2]
|
christopher@45
|
230 # elif timesig == '7/4' or timesig == '7/8':
|
christopher@45
|
231 # subdivision_seq = [1,7,2]
|
christopher@45
|
232 # elif timesig == '11/4' or timesig == '11/8':
|
christopher@45
|
233 # subdivision_seq = [1,11,2]
|
christopher@45
|
234 # else:
|
christopher@45
|
235 # 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
|
236 # for i in range(3):
|
christopher@45
|
237 # s = int(input('Enter the subdivision factor at metrical level '+str(i)+':'))
|
christopher@45
|
238 # subdivision_seq.append(s)
|
christopher@45
|
239
|
christopher@45
|
240 # if L_max > 2:
|
christopher@45
|
241 # subdivision_seq = subdivision_seq + [2]*(L_max-2)
|
christopher@45
|
242 # else:
|
christopher@45
|
243 # subdivision_seq = subdivision_seq[0:L_max+1]
|
christopher@45
|
244
|
christopher@45
|
245 # return subdivision_seq
|
christopher@45
|
246
|