1 # -*- coding: utf-8 -*-
3 converstion 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 FR_NOTES
= {'C' : u
'Do',
37 requiresExtendedScale
= False
38 scale
= [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
39 quarterNoteLength
= 400
41 def __init__(self
, node
, autoDetectChorus
=True) :
49 self
._findVersesLoops
()
51 def _parseMusic(self
) :
54 next
= previous
= None
55 for measureNode
in self
.node
.getElementsByTagName('measure') :
56 # divisions de la noire
57 divisions
= int(_getNodeValue(measureNode
, 'attributes/divisions', divisions
))
58 for noteNode
in measureNode
.getElementsByTagName('note') :
59 note
= Note(noteNode
, divisions
, previous
)
61 self
.notes
.append(note
)
63 self
.notes
[noteIndex
-1].next
= note
67 previous
.addDuration(note
)
73 def _findChorus(self
):
74 """ le refrain correspond aux notes pour lesquelles
75 il n'existe q'une seule syllable attachée.
78 for i
, note
in enumerate(self
.notes
) :
80 if start
is None and ll
== 1 :
82 elif start
is not None and ll
> 1 :
85 self
.chorus
= self
.notes
[start
:stop
]
87 def _findVersesLoops(self
) :
88 "recherche des couplets / boucles"
89 verse
= self
.verses
[0]
90 for note
in self
.notes
[:-1] :
93 nll
= len(note
.next
.lyrics
)
96 self
.verses
.append(verse
)
97 verse
.append(self
.notes
[-1])
100 def iterNotes(self
) :
101 "exécution de la chanson avec l'alternance couplets / refrains"
102 for verse
in self
.verses
:
104 repeats
= len(verse
[0].lyrics
)
106 for i
in range(repeats
) :
108 print "---couplet%d---" % i
112 print "---refrain---"
113 for note
in self
.chorus
:
120 for note
, verseIndex
in self
.iterNotes() :
121 print note
.nom
, note
.name
, note
.midi
, note
.duration
, note
.lyrics
[verseIndex
]
124 def assignNotesFromMidiNoteNumbers(self
):
125 # TODO faire le mapping bande hauteur midi
126 for i
in range(len(self
.midiNoteNumbers
)):
127 noteInExtendedScale
= 0
128 while self
.midiNoteNumbers
[i
] > self
.scale
[noteInExtendedScale
] and noteInExtendedScale
< len(self
.scale
)-1:
129 noteInExtendedScale
+= 1
130 if self
.midiNoteNumbers
[i
]<self
.scale
[noteInExtendedScale
]:
131 noteInExtendedScale
-= 1
132 self
.notes
.append(noteInExtendedScale
)
138 scale
= [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
140 def __init__(self
, node
, divisions
, previous
) :
143 self
.step
= _getNodeValue(node
, 'pitch/step', None)
144 if self
.step
is not None :
145 self
.octave
= int(_getNodeValue(node
, 'pitch/octave'))
146 self
.alter
= int(_getNodeValue(node
, 'pitch/alter', 0))
147 elif self
.node
.getElementsByTagName('rest') :
150 NotImplementedError(self
.node
.toxml('utf-8'))
152 self
._duration
= float(_getNodeValue(node
, 'duration'))
154 for ly
in node
.getElementsByTagName('lyric') :
155 self
.lyrics
.append(Lyric(ly
))
157 self
.divisions
= divisions
158 self
.previous
= previous
161 def addDuration(self
, note
) :
162 self
._duration
= self
.duration
+ note
.duration
167 mid
= DIATO_SCALE
[self
.step
]
168 mid
= mid
+ (self
.octave
- OCTAVE_REF
) * 12
169 mid
= mid
+ self
.alter
174 return self
._duration
/ self
.divisions
178 name
= '%s%d' % (self
.step
, self
.octave
)
183 name
= '%s%s' % (name
, abs(self
.alter
) * alterext
)
188 name
= FR_NOTES
[self
.step
]
193 name
= '%s%s' % (name
, abs(self
.alter
) * alterext
)
198 return self
.scale
.index(self
.midi
)
201 class Lyric(object) :
203 _syllabicModifiers
= {
210 def __init__(self
, node
) :
212 self
.syllabic
= _getNodeValue(node
, 'syllabic', 'single')
213 self
.text
= _getNodeValue(node
, 'text')
216 text
= self
._syllabicModifiers
[self
.syllabic
] % self
.text
217 return text
.encode('utf-8')
223 def _getNodeValue(node
, path
, default
=_marker
) :
225 for name
in path
.split('/') :
226 node
= node
.getElementsByTagName(name
)[0]
227 return node
.firstChild
.nodeValue
229 if default
is _marker
:
234 def musicXml2Song(input, partIndex
=0, printNotes
=False) :
235 if isinstance(input, StringTypes
) :
236 input = open(input, 'r')
239 doc
= d
.documentElement
241 # TODO conversion préalable score-timewise -> score-partwise
242 assert doc
.nodeName
== u
'score-partwise'
244 parts
= doc
.getElementsByTagName('part')
245 leadPart
= parts
[partIndex
]
247 part
= Part(leadPart
)
255 # divisions de la noire
257 # midiNotes, durations, lyrics = [], [], []
259 # for measureNode in leadPart.getElementsByTagName('measure') :
260 # divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions))
261 # for noteNode in measureNode.getElementsByTagName('note') :
262 # note = Note(noteNode, divisions)
264 # print note.name, note.midi, note.duration, note.lyric
265 # midiNotes.append(note.midi)
266 # durations.append(note.duration)
267 # lyrics.append(note.lyric)
270 # midiNoteNumbers = midiNotes,
271 # noteLengths = durations,
273 # notesInExtendedScale=None)
278 usage
= "%prog musicXmlFile.xml outputSongFile.smwi [options]"
279 op
= OptionParser(usage
)
280 op
.add_option("-i", "--part-index", dest
="partIndex"
282 , help = "Index de la partie qui contient le champ.")
283 op
.add_option("-p", '--print', dest
='printNotes'
284 , action
="store_true"
286 , help = "Affiche les notes sur la sortie standard (debug)")
288 options
, args
= op
.parse_args()
291 raise SystemExit(op
.format_help())
293 musicXml2Song(args
[0], partIndex
=options
.partIndex
, printNotes
=options
.printNotes
)
297 if __name__
== '__main__' :