Chaîne vide si pas de parole pour une note.
[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
26 class Note(object) :
27 def __init__(self, node, divisions) :
28 self.step = _getNodeValue(node, 'pitch/step')
29 self.octave = int(_getNodeValue(node, 'pitch/octave'))
30 self.alter = int(_getNodeValue(node, 'pitch/alter', 0))
31 self._duration = float(_getNodeValue(node, 'duration'))
32 self.lyric = _getNodeValue(node, 'lyric/text', '')
33
34 self.divisions = divisions
35
36 @property
37 def midi(self) :
38 mid = DIATO_SCALE[self.step]
39 mid = mid + (self.octave - OCTAVE_REF) * 12
40 mid = mid + self.alter
41 return mid
42
43 @property
44 def duration(self) :
45 return self._duration / self.divisions
46
47 @property
48 def name(self) :
49 name = '%s%d' % (self.step, self.octave)
50 if self.alter < 0 :
51 alterext = 'b'
52 else :
53 alterext = '#'
54 name = '%s%s' % (name, abs(self.alter) * alterext)
55 return name
56
57
58
59
60 def _getNodeValue(node, path, default=_marker) :
61 try :
62 for name in path.split('/') :
63 node = node.getElementsByTagName(name)[0]
64 return node.firstChild.nodeValue
65 except :
66 if default is _marker :
67 raise
68 else :
69 return default
70
71 def musicXml2Song(input, output, partIndex=0, printNotes=False) :
72 if isinstance(input, StringTypes) :
73 input = open(input, 'r')
74
75 d = parse(input)
76 doc = d.documentElement
77
78 # TODO conversion préalable score-timewise -> score-partwise
79 assert doc.nodeName == u'score-partwise'
80
81 parts = doc.getElementsByTagName('part')
82 leadPart = parts[partIndex]
83
84 # divisions de la noire
85 divisions = 0
86 midiNotes, durations, lyrics = [], [], []
87
88 for measureNode in leadPart.getElementsByTagName('measure') :
89 divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions))
90 for noteNode in measureNode.getElementsByTagName('note') :
91 note = Note(noteNode, divisions)
92 if printNotes :
93 print note.name, note.midi, note.duration, note.lyric
94 midiNotes.append(note.midi)
95 durations.append(note.duration)
96 lyrics.append(note.lyric)
97
98 song = Song(None,
99 midiNoteNumbers = midiNotes,
100 noteLengths = durations,
101 lyrics = lyrics,
102 notesInExtendedScale=None)
103 song.save(output)
104
105
106 def main() :
107 usage = "%prog musicXmlFile.xml outputSongFile.smwi [options]"
108 op = OptionParser(usage)
109 op.add_option("-i", "--part-index", dest="partIndex"
110 , default = 0
111 , help = "Index de la partie qui contient le champ.")
112 op.add_option("-p", '--print', dest='printNotes'
113 , action="store_true"
114 , default = False
115 , help = "Affiche les notes sur la sortie standard (debug)")
116
117 options, args = op.parse_args()
118
119 if len(args) != 2 :
120 raise SystemExit(op.format_help())
121
122 musicXml2Song(args[0], args[1], partIndex=options.partIndex, printNotes=options.printNotes)
123
124
125
126 if __name__ == '__main__' :
127 sys.exit(main())