Mercurial > hg > multiomr
comparison Functions/MeasureFunctions.py @ 1:0f7f611deca4
Functions
author | Victor Padilla <victor.padilla.mc@gmail.com> |
---|---|
date | Mon, 04 May 2015 22:53:31 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
0:1eb6054a1f84 | 1:0f7f611deca4 |
---|---|
1 ''' | |
2 @organization: Lancaster University & University of Leeds | |
3 @version: 1.0 | |
4 Created on 11/12/2014 | |
5 | |
6 @author: Victor Padilla | |
7 @contact: v.padilla@lancaster.ac.uk | |
8 | |
9 Functions to manipulate measures | |
10 | |
11 | |
12 ''' | |
13 | |
14 from music21 import stream | |
15 from music21 import note | |
16 from music21 import meter | |
17 from music21 import omr | |
18 from music21 import spanner | |
19 from music21 import chord | |
20 import copy | |
21 | |
22 | |
23 class MeasureFunctions: | |
24 | |
25 def getDuration(self,bar): | |
26 ''' | |
27 Returns the duration of a single measure | |
28 ''' | |
29 duration=0 | |
30 try: | |
31 for event in bar: | |
32 try: | |
33 if(event.isNote): | |
34 duration+=event.duration.quarterLength | |
35 if(event.isRest): | |
36 duration+=event.duration.quarterLength | |
37 except: | |
38 pass | |
39 except: | |
40 pass | |
41 return str(duration) | |
42 | |
43 def correctIncorrectMeasuresArray(self,omr,incorrectMeasures): | |
44 ''' | |
45 Iteractive method that detects if some measures have been wrong flagged as an error. | |
46 It is under development | |
47 ''' | |
48 measures=omr.parts[0].getElementsByClass(stream.Measure) | |
49 if 0 in incorrectMeasures: | |
50 incorrectMeasures.remove(0)#Anacrusis | |
51 | |
52 for barNumber in incorrectMeasures: | |
53 if barNumber<len(measures)-1: | |
54 measure=measures[barNumber] | |
55 measureNext=measures[barNumber+1] | |
56 duration=self.getDuration(measure) | |
57 | |
58 if(float(duration)<=0): | |
59 incorrectMeasures.remove(barNumber) | |
60 self.correctIncorrectMeasuresArray(omr,incorrectMeasures) | |
61 | |
62 if(measureNext!=None): | |
63 durationNext=self.getDuration(measureNext) | |
64 if(float(duration)+float(durationNext)==4): | |
65 try: | |
66 incorrectMeasures.remove(barNumber) | |
67 incorrectMeasures.remove(barNumber+1) | |
68 except: | |
69 pass | |
70 self.correctIncorrectMeasuresArray(omr,incorrectMeasures) | |
71 return incorrectMeasures | |
72 | |
73 | |
74 def filterExtraMeasures(self,omr): | |
75 ''' | |
76 Function that removes empty measures | |
77 ''' | |
78 sco=stream.Score() | |
79 s=stream.Part() | |
80 for measure in omr.parts[0].getElementsByClass(stream.Measure): | |
81 if measure.flat.duration.quarterLength>0: | |
82 s.append(measure) | |
83 sco.append(s) | |
84 return sco | |
85 | |
86 | |
87 def getIncorrectMeasureIndices(self,omr): | |
88 ''' | |
89 Method that detect wrong measures using transitions | |
90 ''' | |
91 arrChunks= self.getTransitions(omr) | |
92 measures=omr.parts[0].getElementsByClass(stream.Measure) | |
93 indexBar=0 | |
94 FlagErrors=[] | |
95 barFrom=0 | |
96 barTo=0 | |
97 for chunk in arrChunks: | |
98 indexC=arrChunks.index(chunk) | |
99 chunkBefore=arrChunks[indexC-1] | |
100 if(indexC==0): | |
101 barFrom=0 | |
102 else: | |
103 barFrom+=chunkBefore[1] | |
104 barTo=chunk[1]+barFrom | |
105 chunkMeasures=measures[barFrom:barTo] | |
106 quarterChunk=round(chunk[0]/2) | |
107 for measure in chunkMeasures: | |
108 if measure.duration.quarterLength!=quarterChunk: | |
109 FlagErrors.append(indexBar) | |
110 indexBar+=1 | |
111 return FlagErrors | |
112 | |
113 def _filterTransitions(self,arrMeasureIndex): | |
114 ''' | |
115 Inner method that removes false transitions in measures for detecting missing time signatures | |
116 ''' | |
117 arrMeasureIndex2=[] | |
118 arrMeasureIndex.insert(0,0) | |
119 arrMeasureIndex.append(-1) | |
120 for mes in arrMeasureIndex: | |
121 indexM=arrMeasureIndex.index(mes) | |
122 if indexM>0: | |
123 bars=arrMeasureIndex[indexM]-arrMeasureIndex[indexM-1] | |
124 if (bars>9): | |
125 arrMeasureIndex2.append(mes) | |
126 arrMeasureIndex2.insert(0,0) | |
127 arrMeasureIndex2.append(-1) | |
128 return arrMeasureIndex2 | |
129 | |
130 def getTransitions(self,omr): | |
131 ''' | |
132 Returns transitions in a omr | |
133 ''' | |
134 MeasuresLength=[] | |
135 arrMeasureIndex_before=self._getTransitionBar(omr) | |
136 | |
137 arrMeasureIndex=self._filterTransitions(arrMeasureIndex_before) | |
138 for i in range(len(arrMeasureIndex)-1): | |
139 arrMeasureslength= self._getAverageQuavers(omr,arrMeasureIndex[i],arrMeasureIndex[i+1],False) | |
140 MeasuresLength.append(arrMeasureslength) | |
141 | |
142 return MeasuresLength | |
143 | |
144 def _getTransitionBar(self,omr): | |
145 ''' | |
146 Inner function for getting transitions | |
147 ''' | |
148 arrOut=[] | |
149 measures=omr.parts[0].getElementsByClass(stream.Measure) | |
150 barsNumber=len(measures) | |
151 for barIndex in range(5,barsNumber): | |
152 averageBefore=self._getAverageQuavers(omr,barIndex-4,barIndex,isUntilTS=False) | |
153 averageAfter=self._getAverageQuavers(omr,barIndex,barIndex+2,isUntilTS=False) | |
154 if(abs(averageBefore[0]-averageAfter[0])>1.5): | |
155 if(measures[barIndex].duration.quarterLength<averageBefore[0]*2):#rest bars or missing bars | |
156 arrOut.append(barIndex) | |
157 return arrOut | |
158 | |
159 def _getAverageQuavers(self,myStream,measureIndex,measureIndexEnd,isUntilTS): | |
160 ''' | |
161 Inner function for getting the average quavers per measure in a group of measures | |
162 ''' | |
163 quavers=0 | |
164 barNumbers=0 | |
165 averageQuavers=0 | |
166 measures=myStream.parts[0].getElementsByClass(stream.Measure) | |
167 for bar in measures[measureIndex:measureIndexEnd]: | |
168 duration=bar.duration.quarterLength*2 | |
169 barNumbers+=1 | |
170 if(duration>0 and duration<10): | |
171 quavers+=duration | |
172 if isUntilTS: | |
173 if (len(bar.getElementsByClass(meter.TimeSignature))>0): | |
174 break | |
175 if barNumbers>0: | |
176 averageQuavers=quavers/barNumbers | |
177 return averageQuavers,barNumbers | |
178 | |
179 #************************************************** | |
180 def getPossibleBeamsErrors(self,omr): | |
181 ''' | |
182 Function that returns measure errors based on non conventional beaming | |
183 ''' | |
184 arrErrors=[] | |
185 measures=omr.parts[0].getElementsByClass(stream.Measure) | |
186 barsNumber=len(measures) | |
187 for i in range(barsNumber): | |
188 notes=measures[i].getElementsByClass(note.Note) | |
189 count=0 | |
190 state=0 | |
191 for n in notes: | |
192 if n.duration.quarterLength==0.25: | |
193 bs=n.beams.getNumbers() | |
194 if(len(bs)>0): | |
195 b=n.beams.getByNumber(bs[0]) | |
196 if b.type=='start': | |
197 count=1 | |
198 state=1 | |
199 if b.type=='continue': | |
200 if(state==1 or state==2): | |
201 count+=1 | |
202 state=2 | |
203 if b.type=='stop': | |
204 if(state==1 or state==2): | |
205 count+=1 | |
206 if count==3: | |
207 arrErrors.append(i) | |
208 arrErrors=list(set(arrErrors)) | |
209 return arrErrors | |
210 | |
211 def getPossibleLastNoteErrors(self,omr): | |
212 ''' | |
213 Function that returns measure errors based on the last notes rhythm | |
214 ''' | |
215 arrErrors=[] | |
216 measures=omr.parts[0].getElementsByClass(stream.Measure) | |
217 barsNumber=len(measures) | |
218 for i in range(barsNumber): | |
219 notes=measures[i].getElementsByClass(note.GeneralNote) | |
220 if(len(notes)>0): | |
221 lastNote=notes[len(notes)-1] | |
222 if lastNote.duration.quarterLength<=0.25: | |
223 if(lastNote.isRest): | |
224 arrErrors.append(i) | |
225 else: | |
226 bs=lastNote.beams.getNumbers() | |
227 | |
228 if(len(bs)==0): | |
229 if(len(notes)>2): | |
230 noteBefore=notes[len(notes)-2] | |
231 if noteBefore.duration.quarterLength+lastNote.duration.quarterLength!=1: | |
232 arrErrors.append(i) | |
233 | |
234 | |
235 return arrErrors | |
236 | |
237 def flagIncorrectMeasures(self,omr2): | |
238 ''' | |
239 flag the incorrect measures from a omr using different methods | |
240 ''' | |
241 mf=MeasureFunctions() | |
242 sc=omr.correctors.ScoreCorrector(omr2) | |
243 part=sc.getSinglePart(0) | |
244 | |
245 arrErrors=[] | |
246 im=part.getIncorrectMeasureIndices(runFast=False) | |
247 | |
248 im1= mf.getIncorrectMeasureIndices(omr2) | |
249 | |
250 im2= mf.getPossibleBeamsErrors(omr2) | |
251 im3= mf.getPossibleLastNoteErrors(omr2) | |
252 | |
253 | |
254 arrErrors.append(im) | |
255 arrErrors.append(im1) | |
256 arrErrors.append(im2) | |
257 arrErrors.append(im3) | |
258 | |
259 if(len(im)>15): | |
260 if(len(im1)<len(im)): | |
261 im=im1 | |
262 imSum=list(set(im)|set(im2)|set(im3)) | |
263 imSum=sorted(imSum) | |
264 imOK=mf.correctIncorrectMeasuresArray(omr2,imSum) | |
265 print im | |
266 return imOK,arrErrors | |
267 | |
268 def getSlurs(self,part): | |
269 ''' | |
270 Returns the slurs from one part | |
271 ''' | |
272 slurs=part.flat.getElementsByClass(spanner.Spanner) | |
273 return slurs | |
274 | |
275 def reconstructScore(self,part,hashPart): | |
276 ''' | |
277 This function include rest measures in one part based on the hash part aligned | |
278 ''' | |
279 partReconstructed=stream.Part() | |
280 barNumber=1 | |
281 gaps=[] | |
282 for i in range(len(hashPart)): | |
283 if hashPart[i]!="*": | |
284 m=part.getElementsByClass(stream.Measure)[barNumber-1] | |
285 partReconstructed.append(m) | |
286 barNumber+=1 | |
287 else: | |
288 m=stream.Measure() | |
289 partReconstructed.append(m) | |
290 gaps.append(i) | |
291 slurs=self.getSlurs(part) | |
292 partReconstructed.append(slurs) | |
293 myStream=self.reorderMeasures(partReconstructed) | |
294 return myStream,gaps | |
295 | |
296 def reorderMeasures(self,omr): | |
297 ''' | |
298 Returns the correct measures number | |
299 ''' | |
300 slurs=self.getSlurs(omr) | |
301 s=stream.Part() | |
302 barNumber=1 | |
303 for measure in omr.getElementsByClass(stream.Measure): | |
304 measure.number=barNumber | |
305 s.append(measure) | |
306 barNumber+=1 | |
307 s.append(slurs) | |
308 return s | |
309 | |
310 def getNoteByOffset(self,voice,offset): | |
311 ''' | |
312 Returns one note in one voice according to the offset | |
313 ''' | |
314 for n in voice.getElementsByClass(note.Note): | |
315 if n.offset==offset: | |
316 return n | |
317 for n in voice.getElementsByClass(chord.Chord): | |
318 if n.offset==offset: | |
319 return n | |
320 | |
321 def convertVoicesToChord(self,omr): | |
322 ''' | |
323 Function that converts voices with the same rhythm to chords | |
324 ''' | |
325 for part in omr.getElementsByClass(stream.Part): | |
326 for measure in part.getElementsByClass(stream.Measure): | |
327 measure.show('text') | |
328 voices=measure.getElementsByClass(stream.Voice) | |
329 if len(voices)>=2: | |
330 for element1 in voices[0].getElementsByClass(note.GeneralNote): | |
331 offset1= element1.offset | |
332 for voice in voices: | |
333 if voice!=voices[0]: | |
334 element2=self.getNoteByOffset( voice, offset1) | |
335 if element2 is not None and element1.duration.quarterLength==element2.duration.quarterLength and not isinstance(element1,note.Rest): | |
336 mychord=self.mergeChords(element1,element2) | |
337 mychord.duration.quarterLength=element1.quarterLength | |
338 voices[0].replace(element1,mychord) | |
339 myrest=note.Rest() | |
340 myrest.duration.quarterLength=element1.quarterLength | |
341 voice.replace(element2,myrest) | |
342 | |
343 omr=self.removeRestVoice(omr) | |
344 return omr | |
345 def removeRestVoice(self,omr): | |
346 ''' | |
347 This option removes the rest voices (without notes) | |
348 ''' | |
349 for part in omr.getElementsByClass(stream.Part): | |
350 for measure in part.getElementsByClass(stream.Measure): | |
351 voices=measure.getElementsByClass(stream.Voice) | |
352 for voice in voices: | |
353 myvoice = copy.deepcopy(voice) | |
354 myvoice.removeByClass('Rest') | |
355 if len(myvoice)==0: | |
356 measure.remove(voice) | |
357 | |
358 return omr | |
359 | |
360 def mergeChords(self,element1,element2): | |
361 ''' | |
362 Returns one chord made by two elements, notes or chords | |
363 ''' | |
364 notes1=[] | |
365 notes2=[] | |
366 notes=[] | |
367 if isinstance(element1,chord.Chord): | |
368 notes1=element1.pitches | |
369 else: | |
370 notes1.append(element1.pitch) | |
371 | |
372 if isinstance(element2,chord.Chord): | |
373 notes2=element2.pitches | |
374 else: | |
375 notes2.append(element2.pitch) | |
376 | |
377 for n in notes1: | |
378 notes.append(n) | |
379 for n in notes2: | |
380 notes.append(n) | |
381 | |
382 mychord = chord.Chord(notes) | |
383 return mychord | |
384 | |
385 | |
386 def convertBeamsToTriplets(self,omr): | |
387 ''' | |
388 This option search for measures where the rhythm is bigger than the time signature | |
389 and tries to convert 3 note beams in triplets | |
390 ''' | |
391 tsDefault=omr.flat.getElementsByClass('TimeSignature')[0] | |
392 idPart=0 | |
393 for part in omr.getElementsByClass(stream.Part): | |
394 idPart+=1 | |
395 idMeasure=0 | |
396 for measure in part.getElementsByClass(stream.Measure): | |
397 idMeasure+=1 | |
398 if self._IsMeasureHigherTS(idMeasure, part,tsDefault): | |
399 print idMeasure | |
400 measure=self.beamsToTriplets(measure) | |
401 return omr | |
402 | |
403 | |
404 def _IsMeasureHigherTS(self,idMeasure,part,ts): | |
405 ''' | |
406 Inner function that detects if one measure is bigger than the time signature | |
407 ''' | |
408 idCountMeasure=0 | |
409 for measure in part.getElementsByClass(stream.Measure): | |
410 idCountMeasure+=1 | |
411 newts=measure.flat.getElementsByClass('TimeSignature') | |
412 if len(newts)>0: | |
413 ts=newts[0] | |
414 if idMeasure==idCountMeasure: | |
415 voices=measure.getElementsByClass(stream.Voice) | |
416 totalDuration=0 | |
417 if len(voices)>=1: | |
418 for voice in voices: | |
419 totalDuration=0 | |
420 for element in voice.getElementsByClass(note.GeneralNote): | |
421 totalDuration+=element.duration.quarterLength; | |
422 else: | |
423 for element in measure.getElementsByClass(note.GeneralNote): | |
424 totalDuration+=element.duration.quarterLength; | |
425 quartersBymeasure=ts.numerator*(4.0/ts.denominator) | |
426 if totalDuration>quartersBymeasure: | |
427 return True | |
428 else: | |
429 return False | |
430 def beamsToTriplets(self,measure): | |
431 ''' | |
432 This function returns one measure changing 3 notes beamed by triplets | |
433 ''' | |
434 voices=measure.getElementsByClass(stream.Voice) | |
435 if len(voices)>=1: | |
436 for voice in voices: | |
437 notes=voice.getElementsByClass(note.NotRest) | |
438 notes=self._notesToTriplets(notes) | |
439 | |
440 else: | |
441 notes=measure.getElementsByClass(note.NotRest) | |
442 notes=self._notesToTriplets(notes) | |
443 | |
444 return measure | |
445 | |
446 | |
447 def _notesToTriplets(self,notes): | |
448 ''' | |
449 Gets one note arrays and tries to convert to triplets | |
450 ''' | |
451 | |
452 for i in range(len(notes)): | |
453 self._removeSlurs(notes[i]) | |
454 beams1=notes[i].beams.beamsList | |
455 | |
456 try: | |
457 beams2=notes[i+1].beams.beamsList | |
458 beams3=notes[i+2].beams.beamsList | |
459 if len(beams1)>0 and len(beams2)>0 and len(beams3)>0: | |
460 if beams1[0].type=='start' and beams2[0].type=='continue' and beams3[0].type=='stop': | |
461 if notes[i].duration.quarterLength==notes[i+1].duration.quarterLength==notes[i+2].duration.quarterLength: | |
462 mytype=notes[i].duration.type | |
463 duration=note.duration.convertTypeToQuarterLength(mytype) | |
464 realDuration=duration*2.0/3.0 | |
465 notes[i].duration.quarterLength=realDuration | |
466 notes[i+1].duration.quarterLength=realDuration | |
467 notes[i+2].duration.quarterLength=realDuration | |
468 | |
469 except: | |
470 pass | |
471 | |
472 return notes | |
473 | |
474 def _removeSlurs(self,n): | |
475 ''' | |
476 Removes the slur in one note | |
477 ''' | |
478 for element in n.getSites(): | |
479 if isinstance(element,stream.SpannerStorage): | |
480 for e in element.elements: | |
481 element.pop(0) | |
482 | |
483 def removesEmptyVoices(self,omr): | |
484 ''' | |
485 Removes empty voices (just rests) | |
486 ''' | |
487 for part in omr.getElementsByClass(stream.Part): | |
488 for measure in part.getElementsByClass(stream.Measure): | |
489 print measure | |
490 voices=measure.getElementsByClass(stream.Voice) | |
491 if len(voices)>=1: | |
492 print len(voices) | |
493 for voice in voices: | |
494 notes=voice.getElementsByClass(note.NotRest) | |
495 if len(notes)==0: | |
496 index=measure.index(voice) | |
497 measure.pop(index) | |
498 voices=measure.getElementsByClass(stream.Voice) | |
499 if len(voices)==1: | |
500 measure.flattenUnnecessaryVoices() | |
501 return omr | |
502 | |
503 def removesGaps(self,omr): | |
504 ''' | |
505 This option changes gaps by rests in voices | |
506 ''' | |
507 for part in omr.getElementsByClass(stream.Part): | |
508 for measure in part.getElementsByClass(stream.Measure): | |
509 voices=measure.getElementsByClass(stream.Voice) | |
510 if len(voices)>=1: | |
511 for voice in voices: | |
512 v=voice.findGaps() | |
513 if v!=None: | |
514 nextOffset=1000 | |
515 for element in voice.getElementsByClass(note.GeneralNote): | |
516 offset= element.offset | |
517 if offset>nextOffset: | |
518 rest=note.Rest() | |
519 rest.duration.quarterLength=offset-nextOffset | |
520 voice.insert(nextOffset,rest) | |
521 nextOffset=1000 | |
522 else: | |
523 duration=element.duration.quarterLength | |
524 nextOffset=offset+duration | |
525 return omr | |
526 | |
527 |