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 requiresExtendedScale
= False
52 scale
= [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
53 quarterNoteLength
= 400
55 def __init__(self
, node
, autoDetectChorus
=True) :
64 self
._findVersesLoops
()
66 def _parseMusic(self
) :
70 for measureNode
in self
.node
.getElementsByTagName('measure') :
73 # iteration sur les notes
74 # divisions de la noire
75 divisions
= int(_getNodeValue(measureNode
, 'attributes/divisions', divisions
))
76 for noteNode
in measureNode
.getElementsByTagName('note') :
77 note
= Note(noteNode
, divisions
, previous
)
79 measureNotes
.append(note
)
83 previous
.addDuration(note
)
86 self
.notes
.extend(measureNotes
)
90 barlineNode
= measureNode
.getElementsByTagName('barline')[0]
94 barline
= Barline(barlineNode
, measureNotes
)
96 self
.repeats
.append(barline
)
98 def _findChorus(self
):
99 """ le refrain correspond aux notes pour lesquelles
100 il n'existe q'une seule syllable attachée.
103 for i
, note
in enumerate(self
.notes
) :
104 ll
= len(note
.lyrics
)
105 if start
is None and ll
== 1 :
107 elif start
is not None and ll
> 1 :
110 self
.chorus
= self
.notes
[start
:stop
]
112 def _findVersesLoops(self
) :
113 "recherche des couplets / boucles"
114 verse
= self
.verses
[0]
115 for note
in self
.notes
[:-1] :
117 ll
= len(note
.lyrics
)
118 nll
= len(note
.next
.lyrics
)
121 self
.verses
.append(verse
)
122 verse
.append(self
.notes
[-1])
125 def iterNotes(self
, indefinitely
=True) :
126 "exécution de la chanson avec l'alternance couplets / refrains"
127 print 'indefinitely', indefinitely
128 if indefinitely
== False :
129 iterable
= self
.verses
131 iterable
= cycle(self
.verses
)
132 for verse
in iterable
:
134 repeats
= len(verse
[0].lyrics
)
136 for i
in range(repeats
) :
138 print "---couplet%d---" % i
142 print "---refrain---"
143 for note
in self
.chorus
:
150 for note
, verseIndex
in self
.iterNotes(indefinitely
=False) :
151 print note
, note
.lyrics
[verseIndex
]
154 def assignNotesFromMidiNoteNumbers(self
):
155 # TODO faire le mapping bande hauteur midi
156 for i
in range(len(self
.midiNoteNumbers
)):
157 noteInExtendedScale
= 0
158 while self
.midiNoteNumbers
[i
] > self
.scale
[noteInExtendedScale
] and noteInExtendedScale
< len(self
.scale
)-1:
159 noteInExtendedScale
+= 1
160 if self
.midiNoteNumbers
[i
]<self
.scale
[noteInExtendedScale
]:
161 noteInExtendedScale
-= 1
162 self
.notes
.append(noteInExtendedScale
)
165 class Barline(object) :
167 def __init__(self
, node
, measureNotes
) :
169 location
= self
.location
= node
.getAttribute('location') or 'right'
171 repeatN
= node
.getElementsByTagName('repeat')[0]
172 repeat
= {'direction' : repeatN
.getAttribute('direction'),
173 'times' : int(repeatN
.getAttribute('times') or 1)}
174 if location
== 'left' :
175 repeat
['note'] = measureNotes
[0]
176 elif location
== 'right' :
177 repeat
['note'] = measureNotes
[-1]
179 raise ValueError(location
)
186 if self
.location
== 'left' :
188 elif self
.location
== 'right' :
198 def midi_to_step_alter_octave(midi
):
199 stepIndex
= midi
% 12
200 step
, alter
= CHROM_SCALE
[stepIndex
]
201 octave
= midi
/ 12 - 1
202 return step
, alter
, octave
205 def __init__(self
, *args
) :
207 self
.step
, self
.alter
, self
.octave
= args
208 elif len(args
) == 1 :
210 self
.step
, self
.alter
, self
.octave
= Tone
.midi_to_step_alter_octave(midi
)
214 mid
= DIATO_SCALE
[self
.step
]
215 mid
= mid
+ (self
.octave
- OCTAVE_REF
) * 12
216 mid
= mid
+ self
.alter
222 name
= '%s%d' % (self
.step
, self
.octave
)
227 name
= '%s%s' % (name
, abs(self
.alter
) * alterext
)
232 name
= FR_NOTES
[self
.step
]
237 name
= '%s%s' % (name
, abs(self
.alter
) * alterext
)
243 scale
= [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
245 def __init__(self
, node
, divisions
, previous
) :
248 self
.step
= _getNodeValue(node
, 'pitch/step', None)
249 if self
.step
is not None :
250 self
.octave
= int(_getNodeValue(node
, 'pitch/octave'))
251 self
.alter
= int(_getNodeValue(node
, 'pitch/alter', 0))
252 elif self
.node
.getElementsByTagName('rest') :
255 NotImplementedError(self
.node
.toxml('utf-8'))
257 self
._duration
= float(_getNodeValue(node
, 'duration'))
259 for ly
in node
.getElementsByTagName('lyric') :
260 self
.lyrics
.append(Lyric(ly
))
262 self
.divisions
= divisions
263 self
.previous
= previous
267 return (u
'%5s %2s %2d %4s' % (self
.nom
, self
.name
, self
.midi
, round(self
.duration
, 2))).encode('utf-8')
270 return self
.name
.encode('utf-8')
272 def addDuration(self
, note
) :
273 self
._duration
= self
.duration
+ note
.duration
278 # mid = DIATO_SCALE[self.step]
279 # mid = mid + (self.octave - OCTAVE_REF) * 12
280 # mid = mid + self.alter
285 return self
._duration
/ self
.divisions
289 # name = '%s%d' % (self.step, self.octave)
290 # if self.alter < 0 :
294 # name = '%s%s' % (name, abs(self.alter) * alterext)
299 # name = FR_NOTES[self.step]
300 # if self.alter < 0 :
304 # name = '%s%s' % (name, abs(self.alter) * alterext)
309 return self
.scale
.index(self
.midi
)
312 class Lyric(object) :
314 _syllabicModifiers
= {
321 def __init__(self
, node
) :
323 self
.syllabic
= _getNodeValue(node
, 'syllabic', 'single')
324 self
.text
= _getNodeValue(node
, 'text')
326 def syllabus(self
, encoding
='utf-8'):
327 text
= self
._syllabicModifiers
[self
.syllabic
] % self
.text
328 return text
.encode(encoding
)
331 return self
.syllabus()
337 def _getNodeValue(node
, path
, default
=_marker
) :
339 for name
in path
.split('/') :
340 node
= node
.getElementsByTagName(name
)[0]
341 return node
.firstChild
.nodeValue
343 if default
is _marker
:
348 def musicXml2Song(input, partIndex
=0, printNotes
=False) :
349 if isinstance(input, StringTypes
) :
350 input = open(input, 'r')
353 doc
= d
.documentElement
355 # TODO conversion préalable score-timewise -> score-partwise
356 assert doc
.nodeName
== u
'score-partwise'
358 parts
= doc
.getElementsByTagName('part')
359 leadPart
= parts
[partIndex
]
361 part
= Part(leadPart
)
371 usage
= "%prog musicXmlFile.xml [options]"
372 op
= OptionParser(usage
)
373 op
.add_option("-i", "--part-index", dest
="partIndex"
375 , help = "Index de la partie qui contient le champ.")
376 op
.add_option("-p", '--print', dest
='printNotes'
377 , action
="store_true"
379 , help = "Affiche les notes sur la sortie standard (debug)")
381 options
, args
= op
.parse_args()
384 raise SystemExit(op
.format_help())
386 musicXml2Song(args
[0], partIndex
=options
.partIndex
, printNotes
=options
.printNotes
)
390 if __name__
== '__main__' :