christopher@45
|
1 '''
|
christopher@45
|
2 Author: Chunyang Song
|
christopher@45
|
3 Institution: Centre for Digital Music, Queen Mary University of London
|
christopher@45
|
4
|
christopher@45
|
5 '''
|
christopher@45
|
6
|
christopher@71
|
7 from basic_functions import get_H, velocity_sequence_to_min_timespan, get_rhythm_category, upsample_velocity_sequence, find_rhythm_Lmax
|
christopher@45
|
8 from parameter_setter import are_parameters_valid
|
christopher@45
|
9
|
christopher@45
|
10 def get_syncopation(bar, parameters = None):
|
christopher@45
|
11 syncopation = None
|
christopher@45
|
12 velocitySequence = bar.get_velocity_sequence()
|
christopher@45
|
13 subdivisionSequence = bar.get_subdivision_sequence()
|
christopher@45
|
14
|
christopher@45
|
15 if get_rhythm_category(velocitySequence, subdivisionSequence) == 'poly':
|
christopher@45
|
16 print 'Warning: SG model detects polyrhythms so returning None.'
|
christopher@71
|
17 elif bar.is_empty():
|
christopher@71
|
18 print 'Warning: SG model detects empty bar so returning None.'
|
christopher@45
|
19 else:
|
christopher@71
|
20 velocitySequence = velocity_sequence_to_min_timespan(velocitySequence) # converting to the minimum time-span format
|
christopher@45
|
21
|
christopher@45
|
22 # set the defaults
|
christopher@71
|
23 Lmax = 10
|
christopher@45
|
24 weightSequence = range(Lmax+1) # i.e. [0,1,2,3,4,5]
|
christopher@45
|
25 if parameters!= None:
|
christopher@45
|
26 if 'Lmax' in parameters:
|
christopher@45
|
27 Lmax = parameters['Lmax']
|
christopher@45
|
28 if 'W' in parameters:
|
christopher@45
|
29 weightSequence = parameters['W']
|
christopher@45
|
30
|
christopher@45
|
31 if not are_parameters_valid(Lmax, weightSequence, subdivisionSequence):
|
christopher@45
|
32 print 'Error: the given parameters are not valid.'
|
christopher@45
|
33 else:
|
christopher@71
|
34 Lmax = find_rhythm_Lmax(velocitySequence, Lmax, weightSequence, subdivisionSequence)
|
christopher@71
|
35 if Lmax != None:
|
christopher@71
|
36 # generate the metrical weights of level Lmax, and upsample(stretch) the velocity sequence to match the length of H
|
christopher@71
|
37 H = get_H(weightSequence,subdivisionSequence, Lmax)
|
christopher@71
|
38 #print len(velocitySequence)
|
christopher@71
|
39 #velocitySequence = upsample_velocity_sequence(velocitySequence, len(H))
|
christopher@71
|
40 #print len(velocitySequence)
|
christopher@71
|
41
|
christopher@71
|
42 # 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
|
43 def ave_dif_neighbours(index, level):
|
christopher@45
|
44
|
christopher@71
|
45 averages = []
|
christopher@71
|
46 parameterGarma = 0.8
|
christopher@71
|
47
|
christopher@71
|
48 # The findPre function is to calculate the index of the previous neighbour at a certain metrical level.
|
christopher@71
|
49 def find_pre(index, level):
|
christopher@71
|
50 preIndex = (index - 1)%len(H) # using % is to restrict the index varies within range(0, len(H))
|
christopher@71
|
51 while(H[preIndex] > level):
|
christopher@71
|
52 preIndex = (preIndex - 1)%len(H)
|
christopher@71
|
53 #print 'preIndex', preIndex
|
christopher@71
|
54 return preIndex
|
christopher@45
|
55
|
christopher@71
|
56 # The findPost function is to calculate the index of the next neighbour at a certain metrical level.
|
christopher@71
|
57 def find_post(index, level):
|
christopher@71
|
58 postIndex = (index + 1)%len(H)
|
christopher@71
|
59 while(H[postIndex] > level):
|
christopher@71
|
60 postIndex = (postIndex + 1)%len(H)
|
christopher@71
|
61 #print 'postIndex', postIndex
|
christopher@71
|
62 return postIndex
|
christopher@71
|
63
|
christopher@71
|
64 # 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
|
65 def dif(index1,index2):
|
christopher@71
|
66 parameterBeta = 0.5
|
christopher@71
|
67 dif_v = velocitySequence[index1]-velocitySequence[index2]
|
christopher@71
|
68 dif_h = abs(H[index1]-H[index2])
|
christopher@71
|
69 diffactor = (parameterBeta*dif_h/4+1-parameterBeta)
|
christopher@71
|
70 if diffactor>1:
|
christopher@71
|
71 return dif_v
|
christopher@71
|
72 else:
|
christopher@71
|
73 return dif_v*diffactor
|
christopher@45
|
74
|
christopher@45
|
75
|
christopher@71
|
76 # 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
|
77 for l in range(level, max(H)+1):
|
christopher@71
|
78 ave = (parameterGarma*dif(index,find_pre(index,l))+dif(index,find_post(index,l)) )/(1+parameterGarma)
|
christopher@71
|
79 averages.append(ave)
|
christopher@71
|
80 return averages
|
christopher@45
|
81
|
christopher@71
|
82 # if the upsampling was successfully done
|
christopher@71
|
83 if velocitySequence != None:
|
christopher@71
|
84 syncopation = 0
|
christopher@71
|
85 # Calculate the syncopation value for each note
|
christopher@71
|
86 for index in range(len(velocitySequence)):
|
christopher@71
|
87 if velocitySequence[index] != 0: # Onset detected
|
christopher@71
|
88 h = H[index]
|
christopher@71
|
89 # Syncopation potential according to its metrical level, which is equal to the metrical weight
|
christopher@71
|
90 potential = 1 - pow(0.5,h)
|
christopher@71
|
91 level = h # Metrical weight is equal to its metrical level
|
christopher@71
|
92 syncopation += min(ave_dif_neighbours(index, level))*potential
|
christopher@71
|
93 else:
|
christopher@71
|
94 print 'Try giving a bigger Lmax so that the rhythm sequence can be measured by the matching metrical weights sequence (H).'
|
christopher@45
|
95 return syncopation
|