X-Git-Url: https://scm.cri.minesparis.psl.eu/git/minwii.git/blobdiff_plain/e7854ed82aa375273bfbf772493739f875817061..46f3ffd7fdde386f41020171b5733e55a8e64a85:/src/minwii/musicxml.py diff --git a/src/minwii/musicxml.py b/src/minwii/musicxml.py new file mode 100755 index 0000000..0ed7085 --- /dev/null +++ b/src/minwii/musicxml.py @@ -0,0 +1,418 @@ +# -*- coding: utf-8 -*- +""" +conversion 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 itertools import cycle +#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} + +CHROM_SCALE = { 0 : ('C', 0), + 1 : ('C', 1), + 2 : ('D', 0), + 3 : ('E', -1), + 4 : ('E', 0), + 5 : ('F', 0), + 6 : ('F', 1), + 7 : ('G', 0), + 8 : ('G', 1), + 9 : ('A', 0), + 10 : ('B', -1), + 11 : ('B', 0)} + + +FR_NOTES = {'C' : u'Do', + 'D' : u'Ré', + 'E' : u'Mi', + 'F' : u'Fa', + 'G' : u'Sol', + 'A' : u'La', + 'B' : u'Si'} + +_marker = [] + +class Part(object) : + + def __init__(self, node, autoDetectChorus=True) : + self.node = node + self.notes = [] + self.repeats = [] + self.distinctNotes = [] + self.quarterNoteDuration = 500 + self._parseMusic() + self.verses = [[]] + self.chorus = [] + if autoDetectChorus : + self._findChorus() + self._findVersesLoops() + + def _parseMusic(self) : + divisions = 0 + previous = None + distinctNotesDict = {} + + for measureNode in self.node.getElementsByTagName('measure') : + measureNotes = [] + + # iteration sur les notes + # divisions de la noire + divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions)) + for noteNode in measureNode.getElementsByTagName('note') : + note = Note(noteNode, divisions, previous) + 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 : + barlineNode = measureNode.getElementsByTagName('barline')[0] + except IndexError : + continue + + 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 + il n'existe q'une seule syllable attachée. + """ + start = stop = None + for i, note in enumerate(self.notes) : + ll = len(note.lyrics) + if start is None and ll == 1 : + start = i + elif start is not None and ll > 1 : + stop = i + break + if not (start or stop) : + self.chorus = [] + else : + self.chorus = self.notes[start:stop] + + def _findVersesLoops(self) : + "recherche des couplets / boucles" + verse = self.verses[0] + for note in self.notes[:-1] : + verse.append(note) + ll = len(note.lyrics) + nll = len(note.next.lyrics) + if ll != nll : + verse = [] + self.verses.append(verse) + verse.append(self.notes[-1]) + + + def iterNotes(self, indefinitely=True) : + "exécution de la chanson avec l'alternance couplets / refrains" + if indefinitely == False : + iterable = self.verses + else : + iterable = cycle(self.verses) + for verse in iterable : + #print "---partie---" + repeats = len(verse[0].lyrics) + if repeats > 1 : + for i in range(repeats) : + # couplet + #print "---couplet%d---" % i + for note in verse : + yield note, i + # refrain + #print "---refrain---" + for note in self.chorus : + yield note, 0 + else : + for note in verse : + yield note, 0 + + def pprint(self) : + for note, verseIndex in self.iterNotes(indefinitely=False) : + print note, note.lyrics[verseIndex] + + + def assignNotesFromMidiNoteNumbers(self): + # TODO faire le mapping bande hauteur midi + for i in range(len(self.midiNoteNumbers)): + noteInExtendedScale = 0 + while self.midiNoteNumbers[i] > self.scale[noteInExtendedScale] and noteInExtendedScale < len(self.scale)-1: + noteInExtendedScale += 1 + if self.midiNoteNumbers[i] score-partwise + assert doc.nodeName == u'score-partwise' + + parts = doc.getElementsByTagName('part') + leadPart = parts[partIndex] + + part = Part(leadPart, autoDetectChorus=autoDetectChorus) + + if printNotes : + part.pprint() + + return part + + + +def main() : + usage = "%prog musicXmlFile.xml [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)") + + 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, + autoDetectChorus=options.autoDetectChorus, + printNotes=options.printNotes) + + +if __name__ == '__main__' : + sys.exit(main())