comparison Syncopation models/WNBD.py @ 1:b2da092dc2e0

The consolidated syncopation software. Have finished individual model and basic functions. Need to revise the coding in main.py, and add rhythm-input interface.
author Chunyang Song <csong@eecs.qmul.ac.uk>
date Sun, 05 Oct 2014 21:52:41 +0100
parents 76ce27beba95
children 031e2ccb1fb6
comparison
equal deleted inserted replaced
0:76ce27beba95 1:b2da092dc2e0
1 ''' 1 '''
2 Author: Chunyang Song 2 Author: Chunyang Song
3 Institution: Centre for Digital Music, Queen Mary University of London 3 Institution: Centre for Digital Music, Queen Mary University of London
4 4
5 ** Weighted Note-to-Beat Distance Model (WNBD) ** 5 '''
6 from basic_functions import repeat, get_note_indices
6 7
7 Algorithm: 8 def cumu_multiply(numbers):
9 product = 1
10 for n in numbers:
11 product = product*n
12 return product
8 13
9 Calculate the distance (d) of onset to the nearest strong beat; 14 def get_syncopation(seq, subdivision_seq, strong_beat_level, postbar_seq):
10 Calculate the WNBD measure (w) of each onset; 15 syncopation = None
11 Syncopation is the sum of WNBD measures divided by the number of onsets. 16
17 num_beats = cumu_multiply(subdivision_seq[0:strong_beat_level+1]) # num_beats is the number of strong beats
18 if len(seq)%num_beats != 0:
19 print 'Error: the length of sequence is not subdivable by the subdivision factor in subdivision_seq.'
20 else:
21 # Find the indices of all the strong-beats
22 beat_indices = []
23 beat_interval = len(seq)/num_beats
24 for i in range(num_beats+1):
25 beat_indices.append(i*beat_interval)
26 if postbar_seq != None: # if there is a postbar_seq, add another two beats index for later calculation
27 beat_indices += [len(seq)+beat_interval, len(seq)+ 2* beat_interval]
12 28
13 ''' 29 note_indices = get_note_indices(seq) # all the notes
14 30
15 from MeterStructure import MeterStructure 31 # Calculate the WNBD measure for each note
32 def measure_pernote(note_index, nextnote_index):
33 # Find the nearest beats where this note locates - in [beat_indices[j], beat_indices[j+1])
34 j = 0
35 while note_index < beat_indices[j] or note_index >= beat_indices[j+1]:
36 j = j + 1
37
38 # The distance of note to nearest beat normalised by the beat interval
39 distance_nearest_beat = min(abs(note_index - beat_indices[j]), abs(note_index - beat_indices[j+1]))/float(beat_interval)
16 40
17 def WNBD(rhythm, time_sig, category, bar): 41 # if this note is on-beat
42 if distance_nearest_beat == 0:
43 measure = 0
44 # or if this note is held on past the following beat, but ends on or before the later beat
45 elif beat_indices[j+1] < nextnote_index <= beat_indices[j+2]:
46 measure = float(2)/distance_nearest_beat
47 else:
48 measure = float(1)/distance_nearest_beat
18 49
19 ms = MeterStructure(time_sig) 50 return measure
20 beat = ms.getBeats(bar) + [len(rhythm)] # the "beat" array include all "strong-beat" positions across all bars, and the downbeat position of the following bar
21 if len(beat)!=0:
22 unit = beat[1]-beat[0] # how many digits to represent the length of one beat
23 51
24 onsetPos = [] # The onset positions 52 total = 0
25 d = [] # The distances of each onset to its nearest strong beat 53 for i in range(len(note_indices)):
26 beatToLeft = [] # The beat indexes of the beats that are to the left of or coincide with each onset 54 if i == len(note_indices)-1:# if this is the last note, end_time is the index of the following note in the next bar
27 wnbdMeasures = [] # the un-normalized measures of wnbd 55 if postbar_seq != None and postbar_seq != repeat([0],len(postbar_seq)):
28 56 nextnote_index = get_note_indices(postbar_seq)[0]+len(seq)
29 # Calculate the distance of each onset to the nearest strong beat 57 else: # or if the next bar is none or full rest, end_time is the end of this sequence.
30 l = len(rhythm) 58 nextnote_index = len(seq)
31 for i in range(l):
32 if rhythm[i] == 1: # onset detected
33 onsetPos.append(i)
34
35 # find its distance to the nearest strong beat
36 for j in range(len(beat)-1):
37 if beat[j]<= i < beat[j+1]:
38 d1 = abs(i-beat[j])
39 d2 = abs(i-beat[j+1])
40 d.append( min(d1,d2)/float(unit) ) # Normalize the distance to beat-level
41 beatToLeft.append(j)
42 else:
43 continue
44
45 #Calculate the WNBD measure of each onset
46 n = len(onsetPos)
47 for i in range(n):
48 if d[i] ==0: # If on-beat, measure is 0
49 wnbdMeasures.append(0)
50 else: 59 else:
51 if i == n-1: # The last note, then 60 nextnote_index = note_indices[i+1]
52 if beatToLeft[i] == beat[-3]/unit: # if the last note has more than 1- but less than 2-beat distance to the next bar 61 total += measure_pernote(note_indices[i],nextnote_index)
53 wnbdMeasures.append(2.0/d[i]) # measure is 2/d, else 1/d
54 else:
55 wnbdMeasures.append(1.0/d[i])
56 62
57 else: # if its not the last note 63 syncopation = float(total) / len(note_indices)
58 if beatToLeft[i+1] == beatToLeft[i] or (beatToLeft[i+1]-beatToLeft[i]==1 and d[i+1]==0): # if this note is no more than 1-beat distance away from the next note
59 wnbdMeasures.append(1.0/d[i])
60 elif beatToLeft[i+1] - beatToLeft[i] == 1 or (beatToLeft[i+1]-beatToLeft[i]==2 and d[i+1]==0): # if this note is no more than 2-beat distance away from the next
61 wnbdMeasures.append(2.0/d[i])
62 else: # if the note is more than 2-beat distance away from the next note
63 wnbdMeasures.append(1.0/d[i])
64 64
65 syncopation = sum(wnbdMeasures)/float(n) 65 return syncopation
66 return syncopation
67
68 else:
69 return -1
70
71
72 # Retrieve the stimuli
73 f = file('stimuli.txt')
74 #f = file('stimuli_34only.txt')
75
76 #Calculate syncopation for each rhythm pattern
77 while True:
78 line = f.readline().split(';')
79 if len(line) == 1:
80 break
81 else:
82 sti_name = line[0]
83 rhythmString = line[1].split()
84 time_sig = line[2]
85 category = line[3]
86 bar = int(line[4])
87
88 rhythm = map(int,rhythmString[0].split(','))
89
90 print sti_name, WNBD(rhythm, time_sig, category, bar)