25822b3931360605cce556d75cd1def88334610b
[minwii.git] / src / songs / musicxmltosong.py
1 # -*- coding: utf-8 -*-
2 """
3 converstion d'un fichier musicxml en objet song minwii.
4
5 $Id$
6 $URL$
7 """
8 import sys
9 from types import StringTypes
10 from xml.dom.minidom import parse
11 from optparse import OptionParser
12 from Song import Song
13
14 # Do4 <=> midi 60
15 OCTAVE_REF = 4
16 DIATO_SCALE = {'C' : 60,
17 'D' : 62,
18 'E' : 64,
19 'F' : 65,
20 'G' : 67,
21 'A' : 69,
22 'B' : 71}
23 _marker = []
24
25 class Part(object) :
26
27 def __init__(self, node) :
28 self.node = node
29 self.notes = []
30 self._parseMusic()
31
32 def _parseMusic(self) :
33 divisions = 0
34
35 for measureNode in self.node.getElementsByTagName('measure') :
36 divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions))
37 next = previous = None
38 for i, noteNode in enumerate(measureNode.getElementsByTagName('note')) :
39 note = Note(noteNode, divisions, previous)
40 self.notes.append(note)
41 try :
42 self.notes[i-1].next = note
43 except IndexError:
44 pass
45 previous = note
46
47 def pprint(self) :
48 for note in self.notes :
49 print note.name, note.midi, note.duration, note.lyrics
50
51
52
53
54
55 class Note(object) :
56 def __init__(self, node, divisions, previous) :
57 self.node = node
58 self.step = _getNodeValue(node, 'pitch/step')
59 self.octave = int(_getNodeValue(node, 'pitch/octave'))
60 self.alter = int(_getNodeValue(node, 'pitch/alter', 0))
61 self._duration = float(_getNodeValue(node, 'duration'))
62 self.lyrics = []
63 for ly in node.getElementsByTagName('lyric') :
64 self.lyrics.append(Lyric(ly))
65
66 self.divisions = divisions
67 self.previous = previous
68 self.next = None
69
70 @property
71 def midi(self) :
72 mid = DIATO_SCALE[self.step]
73 mid = mid + (self.octave - OCTAVE_REF) * 12
74 mid = mid + self.alter
75 return mid
76
77 @property
78 def duration(self) :
79 return self._duration / self.divisions
80
81 @property
82 def name(self) :
83 name = '%s%d' % (self.step, self.octave)
84 if self.alter < 0 :
85 alterext = 'b'
86 else :
87 alterext = '#'
88 name = '%s%s' % (name, abs(self.alter) * alterext)
89 return name
90
91
92 class Lyric(object) :
93 def __init__(self, node) :
94 self.node = node
95 self.syllabic = _getNodeValue(node, 'syllabic', 'single')
96 self.text = _getNodeValue(node, 'text')
97
98 def __str__(self) :
99 return self.text.encode('utf-8')
100 __repr__ = __str__
101
102
103
104
105 def _getNodeValue(node, path, default=_marker) :
106 try :
107 for name in path.split('/') :
108 node = node.getElementsByTagName(name)[0]
109 return node.firstChild.nodeValue
110 except :
111 if default is _marker :
112 raise
113 else :
114 return default
115
116 def musicXml2Song(input, output, partIndex=0, printNotes=False) :
117 if isinstance(input, StringTypes) :
118 input = open(input, 'r')
119
120 d = parse(input)
121 doc = d.documentElement
122
123 # TODO conversion préalable score-timewise -> score-partwise
124 assert doc.nodeName == u'score-partwise'
125
126 parts = doc.getElementsByTagName('part')
127 leadPart = parts[partIndex]
128
129 part = Part(leadPart)
130
131 if printNotes :
132 part.pprint()
133
134 # divisions de la noire
135 # divisions = 0
136 # midiNotes, durations, lyrics = [], [], []
137 #
138 # for measureNode in leadPart.getElementsByTagName('measure') :
139 # divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions))
140 # for noteNode in measureNode.getElementsByTagName('note') :
141 # note = Note(noteNode, divisions)
142 # if printNotes :
143 # print note.name, note.midi, note.duration, note.lyric
144 # midiNotes.append(note.midi)
145 # durations.append(note.duration)
146 # lyrics.append(note.lyric)
147 #
148 # song = Song(None,
149 # midiNoteNumbers = midiNotes,
150 # noteLengths = durations,
151 # lyrics = lyrics,
152 # notesInExtendedScale=None)
153 # song.save(output)
154
155
156 def main() :
157 usage = "%prog musicXmlFile.xml outputSongFile.smwi [options]"
158 op = OptionParser(usage)
159 op.add_option("-i", "--part-index", dest="partIndex"
160 , default = 0
161 , help = "Index de la partie qui contient le champ.")
162 op.add_option("-p", '--print', dest='printNotes'
163 , action="store_true"
164 , default = False
165 , help = "Affiche les notes sur la sortie standard (debug)")
166
167 options, args = op.parse_args()
168
169 if len(args) != 2 :
170 raise SystemExit(op.format_help())
171
172 musicXml2Song(args[0], args[1], partIndex=options.partIndex, printNotes=options.printNotes)
173
174
175
176 if __name__ == '__main__' :
177 sys.exit(main())