Bah, c'n'était pas si compliqué tout compte fait :-).
[minwii.git] / src / app / musicxml.py
index 7eedf53..9302118 100755 (executable)
@@ -47,15 +47,13 @@ FR_NOTES = {'C' : u'Do',
 _marker = []
 
 class Part(object) :
-    
-    requiresExtendedScale = False
-    scale = [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
-    quarterNoteLength = 400
-    
+        
     def __init__(self, node, autoDetectChorus=True) :
         self.node = node
         self.notes = []
         self.repeats = []
+        self.distinctNotes = []
+        self.quarterNoteDuration = 500
         self._parseMusic()
         self.verses = [[]]
         self.chorus = []
@@ -66,6 +64,7 @@ class Part(object) :
     def _parseMusic(self) :
         divisions = 0
         previous = None
+        distinctNotesDict = {}
 
         for measureNode in self.node.getElementsByTagName('measure') :
             measureNotes = []
@@ -75,15 +74,25 @@ class Part(object) :
             divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions))
             for noteNode in measureNode.getElementsByTagName('note') :
                 note = Note(noteNode, divisions, previous)
-                if not note.isRest :
+                if (not note.isRest) and (not note.tiedStop) :
                     measureNotes.append(note)
                     if previous :
                         previous.next = note
+                elif note.tiedStop :
+                    assert previous.tiedStart
+                    previous.addDuration(note)
+                    continue
                 else :
                     previous.addDuration(note)
                     continue
                 previous = note
+
             self.notes.extend(measureNotes)
+
+            for note in measureNotes :
+                if not distinctNotesDict.has_key(note.midi) :
+                    distinctNotesDict[note.midi] = True
+                    self.distinctNotes.append(note)
             
             # barres de reprises
             try :
@@ -94,6 +103,18 @@ class Part(object) :
             barline = Barline(barlineNode, measureNotes)
             if barline.repeat :
                 self.repeats.append(barline)
+        
+        self.distinctNotes.sort(lambda a, b : cmp(a.midi, b.midi))
+        sounds = self.node.getElementsByTagName('sound')
+        tempo = 120
+        for sound in sounds :
+            if sound.hasAttribute('tempo') :
+                tempo = float(sound.getAttribute('tempo'))
+                break
+        
+        self.quarterNoteDuration = int(round(60000/tempo))
+        
+        
 
     def _findChorus(self):
         """ le refrain correspond aux notes pour lesquelles
@@ -107,7 +128,10 @@ class Part(object) :
             elif start is not None and ll > 1 :
                 stop = i
                 break
-        self.chorus = self.notes[start:stop]
+        if not (start or stop) :
+            self.chorus = []
+        else :
+            self.chorus = self.notes[start:stop]
     
     def _findVersesLoops(self) :
         "recherche des couplets / boucles"
@@ -124,7 +148,6 @@ class Part(object) :
     
     def iterNotes(self, indefinitely=True) :
         "exécution de la chanson avec l'alternance couplets / refrains"
-        print 'indefinitely', indefinitely
         if indefinitely == False :
             iterable = self.verses
         else :
@@ -245,6 +268,16 @@ class Note(Tone) :
     def __init__(self, node, divisions, previous) :
         self.node = node
         self.isRest = False
+        self.tiedStart = False
+        self.tiedStop = False
+        
+        tieds = _getElementsByPath(node, 'notations/tied', [])
+        for tied in tieds :
+            if tied.getAttribute('type') == 'start' :
+                self.tiedStart = True
+            elif tied.getAttribute('type') == 'stop' :
+                self.tiedStop = True
+        
         self.step = _getNodeValue(node, 'pitch/step', None)
         if self.step is not None :
             self.octave = int(_getNodeValue(node, 'pitch/octave'))
@@ -273,37 +306,10 @@ class Note(Tone) :
         self._duration = self.duration + note.duration
         self.divisions = 1
     
-#    @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
-#    
-#    @property
-#    def nom(self) :
-#        name = FR_NOTES[self.step]
-#        if self.alter < 0 :
-#            alterext = 'b'
-#        else :
-#            alterext = '#'
-#        name = '%s%s' % (name, abs(self.alter) * alterext)
-#        return name
-    
     @property
     def column(self):
         return self.scale.index(self.midi)
@@ -312,10 +318,10 @@ class Note(Tone) :
 class Lyric(object) :
     
     _syllabicModifiers = {
-        'single' : '%s',
-        'begin'  : '%s -',
-        'middle' : '- %s -',
-        'end'    : '- %s'
+        'single' : u'%s',
+        'begin'  : u'%s -',
+        'middle' : u'- %s -',
+        'end'    : u'- %s'
         }
     
     def __init__(self, node) :
@@ -323,12 +329,12 @@ class Lyric(object) :
         self.syllabic = _getNodeValue(node, 'syllabic', 'single')
         self.text = _getNodeValue(node, 'text')
     
-    def syllabus(self, encoding='utf-8'):
+    def syllabus(self):
         text = self._syllabicModifiers[self.syllabic] % self.text
-        return text.encode(encoding)
+        return text
     
     def __str__(self) :
-        return self.syllabus()
+        return self.syllabus().encode('utf-8')
     __repr__  = __str__
         
         
@@ -345,7 +351,19 @@ def _getNodeValue(node, path, default=_marker) :
         else :
             return default
 
-def musicXml2Song(input, partIndex=0, printNotes=False) :
+def _getElementsByPath(node, path, default=_marker) :
+    try :
+        parts = path.split('/')
+        for name in parts[:-1] :
+            node = node.getElementsByTagName(name)[0]
+        return node.getElementsByTagName(parts[-1])
+    except IndexError :
+        if default is _marker :
+            raise
+        else :
+            return default
+
+def musicXml2Song(input, partIndex=0, autoDetectChorus=True, printNotes=False) :
     if isinstance(input, StringTypes) :
         input = open(input, 'r')
     
@@ -358,7 +376,7 @@ def musicXml2Song(input, partIndex=0, printNotes=False) :
     parts = doc.getElementsByTagName('part')
     leadPart = parts[partIndex]
     
-    part = Part(leadPart)
+    part = Part(leadPart, autoDetectChorus=autoDetectChorus)
     
     if printNotes :
         part.pprint()
@@ -373,18 +391,27 @@ def main() :
     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)")
+
+    op.add_option("-c", '--no-chorus', dest='autoDetectChorus'
+                , action="store_false"
+                , default = True
+                , help = "désactive la détection du refrain")
+
     
     options, args = op.parse_args()
     
     if len(args) != 1 :
         raise SystemExit(op.format_help())
     
-    musicXml2Song(args[0], partIndex=options.partIndex, printNotes=options.printNotes)
-    
+    musicXml2Song(args[0],
+                  partIndex=options.partIndex,
+                  autoDetectChorus=options.autoDetectChorus,
+                  printNotes=options.printNotes)
 
 
 if __name__ == '__main__' :