Ajout d'une classe "Part" en vue de la gestion des répétitions.
[minwii.git] / src / songs / musicxmltosong.py
index de8e26a..25822b3 100755 (executable)
+# -*- coding: utf-8 -*-
 """
 converstion d'un fichier musicxml en objet song minwii.
 
 $Id$
 $URL$
 """
 """
 converstion d'un fichier musicxml en objet song minwii.
 
 $Id$
 $URL$
 """
+import sys
+from types import StringTypes
+from xml.dom.minidom import parse
+from optparse import OptionParser
+from Song import Song
 
 
+# Do4 <=> midi 60
+OCTAVE_REF = 4
+DIATO_SCALE = {'C' : 60,
+               'D' : 62,
+               'E' : 64,
+               'F' : 65,
+               'G' : 67,
+               'A' : 69,
+               'B' : 71}
+_marker = []
 
 
+class Part(object) :
+    
+    def __init__(self, node) :
+        self.node = node
+        self.notes = []
+        self._parseMusic()
+    
+    def _parseMusic(self) :
+        divisions = 0
+        
+        for measureNode in self.node.getElementsByTagName('measure') :
+            divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions))
+            next = previous = None
+            for i, noteNode in enumerate(measureNode.getElementsByTagName('note')) :
+                note = Note(noteNode, divisions, previous)
+                self.notes.append(note)
+                try :
+                    self.notes[i-1].next = note
+                except IndexError:
+                    pass
+                previous = note
+    
+    def pprint(self) :
+        for note in self.notes :
+            print note.name, note.midi, note.duration, note.lyrics
+                
+                
+        
+        
+
+class Note(object) :
+    def __init__(self, node, divisions, previous) :
+        self.node = node
+        self.step = _getNodeValue(node, 'pitch/step')
+        self.octave = int(_getNodeValue(node, 'pitch/octave'))
+        self.alter = int(_getNodeValue(node, 'pitch/alter', 0))
+        self._duration = float(_getNodeValue(node, 'duration'))
+        self.lyrics = []
+        for ly in node.getElementsByTagName('lyric') :
+            self.lyrics.append(Lyric(ly))
+
+        self.divisions = divisions
+        self.previous = previous
+        self.next = None
+    
+    @property
+    def midi(self) :
+        mid = DIATO_SCALE[self.step]
+        mid = mid + (self.octave - OCTAVE_REF) * 12
+        mid = mid + self.alter
+        return mid
+    
+    @property
+    def duration(self) :
+        return self._duration / self.divisions
+    
+    @property
+    def name(self) :
+        name = '%s%d' % (self.step, self.octave)
+        if self.alter < 0 :
+            alterext = 'b'
+        else :
+            alterext = '#'
+        name = '%s%s' % (name, abs(self.alter) * alterext)
+        return name
+    
+
+class Lyric(object) :
+    def __init__(self, node) :
+        self.node = node
+        self.syllabic = _getNodeValue(node, 'syllabic', 'single')
+        self.text = _getNodeValue(node, 'text')
+    
+    def __str__(self) :
+        return self.text.encode('utf-8')
+    __repr__  = __str__
+        
+        
+
+
+def _getNodeValue(node, path, default=_marker) :
+    try :
+        for name in path.split('/') :
+            node = node.getElementsByTagName(name)[0]
+        return node.firstChild.nodeValue
+    except :
+        if default is _marker :
+            raise
+        else :
+            return default
+
+def musicXml2Song(input, output, partIndex=0, printNotes=False) :
+    if isinstance(input, StringTypes) :
+        input = open(input, 'r')
+    
+    d = parse(input)
+    doc = d.documentElement
+    
+    # TODO conversion préalable score-timewise -> score-partwise
+    assert doc.nodeName == u'score-partwise'
+    
+    parts = doc.getElementsByTagName('part')
+    leadPart = parts[partIndex]
+    
+    part = Part(leadPart)
+    
+    if printNotes :
+        part.pprint()
+    
+    # divisions de la noire
+#    divisions = 0
+#    midiNotes, durations, lyrics = [], [], []
+#
+#    for measureNode in leadPart.getElementsByTagName('measure') :
+#        divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions))
+#        for noteNode in measureNode.getElementsByTagName('note') :
+#            note = Note(noteNode, divisions)
+#            if printNotes :
+#                print note.name, note.midi, note.duration, note.lyric
+#            midiNotes.append(note.midi)
+#            durations.append(note.duration)
+#            lyrics.append(note.lyric)
+#    
+#    song = Song(None,
+#                midiNoteNumbers = midiNotes,
+#                noteLengths = durations,
+#                lyrics = lyrics,
+#                notesInExtendedScale=None)
+#    song.save(output)
+    
+    
 def main() :
 def main() :
-    pass
+    usage = "%prog musicXmlFile.xml outputSongFile.smwi [options]"
+    op = OptionParser(usage)
+    op.add_option("-i", "--part-index", dest="partIndex"
+                 , default = 0
+                 , help = "Index de la partie qui contient le champ.")
+    op.add_option("-p", '--print', dest='printNotes'
+                  , action="store_true"
+                  , default = False
+                  , help = "Affiche les notes sur la sortie standard (debug)")
+    
+    options, args = op.parse_args()
+    
+    if len(args) != 2 :
+        raise SystemExit(op.format_help())
+    
+    musicXml2Song(args[0], args[1], partIndex=options.partIndex, printNotes=options.printNotes)
+    
 
 
 if __name__ == '__main__' :
 
 
 if __name__ == '__main__' :
-    main()
\ No newline at end of file
+    sys.exit(main())