1 # -*- coding: utf-8 -*-
3 conversion d'un fichier musicxml en objet song minwii.
9 from types
import StringTypes
10 from xml
.dom
.minidom
import parse
11 from optparse
import OptionParser
12 from itertools
import cycle
13 #from Song import Song
17 DIATO_SCALE
= {'C' : 60,
25 CHROM_SCALE
= { 0 : ('C', 0),
39 FR_NOTES
= {'C' : u
'Do',
51 def __init__(self
, node
, autoDetectChorus
=True) :
55 self
.distinctNotes
= []
56 self
.quarterNoteDuration
= 500
60 self
.songStartsWithChorus
= False
61 self
._findVersesLoops
(autoDetectChorus
)
63 def _parseMusic(self
) :
66 distinctNotesDict
= {}
68 for measureNode
in self
.node
.getElementsByTagName('measure') :
71 # iteration sur les notes
72 # divisions de la noire
73 divisions
= int(_getNodeValue(measureNode
, 'attributes/divisions', divisions
))
74 for noteNode
in measureNode
.getElementsByTagName('note') :
75 note
= Note(noteNode
, divisions
, previous
)
76 if (not note
.isRest
) and (not note
.tiedStop
) :
77 measureNotes
.append(note
)
81 assert previous
.tiedStart
82 previous
.addDuration(note
)
86 previous
.addDuration(note
)
87 except AttributeError :
88 # can occur if part starts with a rest.
89 if previous
is not None :
90 # something else is wrong.
95 self
.notes
.extend(measureNotes
)
97 for note
in measureNotes
:
98 if not distinctNotesDict
.has_key(note
.midi
) :
99 distinctNotesDict
[note
.midi
] = True
100 self
.distinctNotes
.append(note
)
104 barlineNode
= measureNode
.getElementsByTagName('barline')[0]
108 barline
= Barline(barlineNode
, measureNotes
)
110 self
.repeats
.append(barline
)
112 self
.distinctNotes
.sort(lambda a
, b
: cmp(a
.midi
, b
.midi
))
113 sounds
= self
.node
.getElementsByTagName('sound')
115 for sound
in sounds
:
116 if sound
.hasAttribute('tempo') :
117 tempo
= float(sound
.getAttribute('tempo'))
120 self
.quarterNoteDuration
= int(round(60000/tempo
))
123 def _findVersesLoops(self
, autoDetectChorus
) :
124 "recherche des couplets / boucles"
125 verse
= self
.verses
[0]
126 for note
in self
.notes
[:-1] :
128 ll
= len(note
.lyrics
)
129 nll
= len(note
.next
.lyrics
)
132 self
.verses
.append(verse
)
133 verse
.append(self
.notes
[-1])
135 if autoDetectChorus
and len(self
.verses
) > 1 :
136 for i
, verse
in enumerate(self
.verses
) :
137 if len(verse
[0].lyrics
) == 1 :
138 self
.chorus
= self
.verses
.pop(i
)
139 self
.songStartsWithChorus
= i
==0
143 def iterNotes(self
) :
144 "exécution de la chanson avec l'alternance couplets / refrains"
145 for verse
in self
.verses
:
146 if self
.songStartsWithChorus
:
147 for note
in self
.chorus
:
150 #print "---partie---"
151 repeats
= len(verse
[0].lyrics
)
153 for i
in range(repeats
) :
155 #print "---couplet%d---" % i
159 #print "---refrain---"
160 for note
in self
.chorus
:
167 def intervalsHistogram(self
) :
169 it
= self
.iterNotes()
170 previousNote
= it
.next()[0]
172 interval
= note
.midi
- previousNote
.midi
173 if histogram
.has_key(interval
) :
174 histogram
[interval
] += 1
176 histogram
[interval
] = 1
182 'Durée de référence du morceau en milisecondes'
183 it
= self
.iterNotes()
185 for note
, verseIndex
in it
:
186 duration
= duration
+ note
.duration
187 duration
= duration
* self
.quarterNoteDuration
# en milisecondes
192 for note
, verseIndex
in self
.iterNotes(indefinitely
=False) :
193 print note
, note
.lyrics
[verseIndex
]
196 def assignNotesFromMidiNoteNumbers(self
):
197 # TODO faire le mapping bande hauteur midi
198 for i
in range(len(self
.midiNoteNumbers
)):
199 noteInExtendedScale
= 0
200 while self
.midiNoteNumbers
[i
] > self
.scale
[noteInExtendedScale
] and noteInExtendedScale
< len(self
.scale
)-1:
201 noteInExtendedScale
+= 1
202 if self
.midiNoteNumbers
[i
]<self
.scale
[noteInExtendedScale
]:
203 noteInExtendedScale
-= 1
204 self
.notes
.append(noteInExtendedScale
)
207 class Barline(object) :
209 def __init__(self
, node
, measureNotes
) :
211 location
= self
.location
= node
.getAttribute('location') or 'right'
213 repeatN
= node
.getElementsByTagName('repeat')[0]
214 repeat
= {'direction' : repeatN
.getAttribute('direction'),
215 'times' : int(repeatN
.getAttribute('times') or 1)}
216 if location
== 'left' :
217 repeat
['note'] = measureNotes
[0]
218 elif location
== 'right' :
219 repeat
['note'] = measureNotes
[-1]
221 raise ValueError(location
)
228 if self
.location
== 'left' :
230 elif self
.location
== 'right' :
240 def midi_to_step_alter_octave(midi
):
241 stepIndex
= midi
% 12
242 step
, alter
= CHROM_SCALE
[stepIndex
]
243 octave
= midi
/ 12 - 1
244 return step
, alter
, octave
247 def __init__(self
, *args
) :
249 self
.step
, self
.alter
, self
.octave
= args
250 elif len(args
) == 1 :
252 self
.step
, self
.alter
, self
.octave
= Tone
.midi_to_step_alter_octave(midi
)
256 mid
= DIATO_SCALE
[self
.step
]
257 mid
= mid
+ (self
.octave
- OCTAVE_REF
) * 12
258 mid
= mid
+ self
.alter
264 name
= u
'%s%d' % (self
.step
, self
.octave
)
269 name
= '%s%s' % (name
, abs(self
.alter
) * alterext
)
274 name
= FR_NOTES
[self
.step
]
279 name
= u
'%s%s' % (name
, abs(self
.alter
) * alterext
)
285 scale
= [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
287 def __init__(self
, node
, divisions
, previous
) :
290 self
.tiedStart
= False
291 self
.tiedStop
= False
293 tieds
= _getElementsByPath(node
, 'notations/tied', [])
295 if tied
.getAttribute('type') == 'start' :
296 self
.tiedStart
= True
297 elif tied
.getAttribute('type') == 'stop' :
300 self
.step
= _getNodeValue(node
, 'pitch/step', None)
301 if self
.step
is not None :
302 self
.octave
= int(_getNodeValue(node
, 'pitch/octave'))
303 self
.alter
= int(_getNodeValue(node
, 'pitch/alter', 0))
304 elif self
.node
.getElementsByTagName('rest') :
307 NotImplementedError(self
.node
.toxml('utf-8'))
309 self
._duration
= float(_getNodeValue(node
, 'duration'))
311 for ly
in node
.getElementsByTagName('lyric') :
312 self
.lyrics
.append(Lyric(ly
))
314 self
.divisions
= divisions
315 self
.previous
= previous
319 return (u
'%5s %2s %2d %4s' % (self
.nom
, self
.name
, self
.midi
, round(self
.duration
, 2))).encode('utf-8')
322 return self
.name
.encode('utf-8')
324 def addDuration(self
, note
) :
325 self
._duration
= self
.duration
+ note
.duration
330 return self
._duration
/ self
.divisions
334 return self
.scale
.index(self
.midi
)
337 class Lyric(object) :
339 _syllabicModifiers
= {
342 'middle' : u
'- %s -',
346 def __init__(self
, node
) :
348 self
.syllabic
= _getNodeValue(node
, 'syllabic', 'single')
349 self
.text
= _getNodeValue(node
, 'text')
352 text
= self
._syllabicModifiers
[self
.syllabic
] % self
.text
356 return self
.syllabus().encode('utf-8')
362 def _getNodeValue(node
, path
, default
=_marker
) :
364 for name
in path
.split('/') :
365 node
= node
.getElementsByTagName(name
)[0]
366 return node
.firstChild
.nodeValue
368 if default
is _marker
:
373 def _getElementsByPath(node
, path
, default
=_marker
) :
375 parts
= path
.split('/')
376 for name
in parts
[:-1] :
377 node
= node
.getElementsByTagName(name
)[0]
378 return node
.getElementsByTagName(parts
[-1])
380 if default
is _marker
:
385 def musicXml2Song(input, partIndex
=0, autoDetectChorus
=True, printNotes
=False) :
386 if isinstance(input, StringTypes
) :
387 input = open(input, 'r')
390 doc
= d
.documentElement
392 # TODO conversion préalable score-timewise -> score-partwise
393 if doc
.nodeName
!= u
'score-partwise' :
394 raise ValueError('not a musicxml file')
396 parts
= doc
.getElementsByTagName('part')
397 leadPart
= parts
[partIndex
]
399 part
= Part(leadPart
, autoDetectChorus
=autoDetectChorus
)
409 usage
= "%prog musicXmlFile.xml [options]"
410 op
= OptionParser(usage
)
411 op
.add_option("-i", "--part-index", dest
="partIndex"
413 , help = "Index de la partie qui contient le champ.")
415 op
.add_option("-p", '--print', dest
='printNotes'
416 , action
="store_true"
418 , help = "Affiche les notes sur la sortie standard (debug)")
420 op
.add_option("-c", '--no-chorus', dest
='autoDetectChorus'
421 , action
="store_false"
423 , help = "désactive la détection du refrain")
426 options
, args
= op
.parse_args()
429 raise SystemExit(op
.format_help())
431 song
= musicXml2Song(args
[0],
432 partIndex
=options
.partIndex
,
433 autoDetectChorus
=options
.autoDetectChorus
,
434 printNotes
=options
.printNotes
)
435 from pprint
import pprint
436 pprint(song
.intervalsHistogram
)
440 if __name__
== '__main__' :