X-Git-Url: https://svn.cri.ensmp.fr/git/minwii.git/blobdiff_plain/1711aaae1c2b3397d739a0669df124e0534711b0..d8d786898468fe6b6065a626070593cc162f5171:/src/songs/musicxmltosong.py diff --git a/src/songs/musicxmltosong.py b/src/songs/musicxmltosong.py index 1724fc4..7f5c8bb 100755 --- a/src/songs/musicxmltosong.py +++ b/src/songs/musicxmltosong.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -converstion d'un fichier musicxml en objet song minwii. +conversion d'un fichier musicxml en objet song minwii. $Id$ $URL$ @@ -9,7 +9,8 @@ import sys from types import StringTypes from xml.dom.minidom import parse from optparse import OptionParser -from Song import Song +from itertools import cycle +#from Song import Song # Do4 <=> midi 60 OCTAVE_REF = 4 @@ -20,13 +21,27 @@ DIATO_SCALE = {'C' : 60, 'G' : 67, 'A' : 69, 'B' : 71} + +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) : + 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._parseMusic() self.verses = [[]] self.chorus = [] @@ -36,20 +51,35 @@ class Part(object) : def _parseMusic(self) : divisions = 0 - noteIndex = 0 - next = previous = None + previous = None + 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) - self.notes.append(note) - try : - self.notes[noteIndex-1].next = note - except IndexError: - pass + if not note.isRest : + measureNotes.append(note) + if previous : + previous.next = note + else : + previous.addDuration(note) + continue previous = note - noteIndex += 1 + self.notes.extend(measureNotes) + + # barres de reprises + try : + barlineNode = measureNode.getElementsByTagName('barline')[0] + except IndexError : + continue + + barline = Barline(barlineNode, measureNotes) + if barline.repeat : + self.repeats.append(barline) def _findChorus(self): """ le refrain correspond aux notes pour lesquelles @@ -78,16 +108,24 @@ class Part(object) : verse.append(self.notes[-1]) - def iterNotes(self) : + def iterNotes(self, indefinitely=True) : "exécution de la chanson avec l'alternance couplets / refrains" - for verse in self.verses : + print 'indefinitely', indefinitely + 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 : @@ -95,17 +133,66 @@ class Part(object) : yield note, 0 def pprint(self) : - for note, verseIndex in self.iterNotes() : - print note.name, note.midi, note.duration, note.lyrics[verseIndex] - + 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]<self.scale[noteInExtendedScale]: + noteInExtendedScale -= 1 + self.notes.append(noteInExtendedScale) + + +class Barline(object) : + + def __init__(self, node, measureNotes) : + self.node = node + location = self.location = node.getAttribute('location') or 'right' + try : + repeatN = node.getElementsByTagName('repeat')[0] + repeat = {'direction' : repeatN.getAttribute('direction'), + 'times' : int(repeatN.getAttribute('times') or 1)} + if location == 'left' : + repeat['note'] = measureNotes[0] + elif location == 'right' : + repeat['note'] = measureNotes[-1] + else : + raise ValueError(location) + self.repeat = repeat + except IndexError : + self.repeat = None + + def __str__(self) : + if self.repeat : + if self.location == 'left' : + return '|:' + elif self.location == 'right' : + return ':|' + return '|' + + __repr__ = __str__ class Note(object) : + scale = [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72] + 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.isRest = False + self.step = _getNodeValue(node, 'pitch/step', None) + if self.step is not None : + self.octave = int(_getNodeValue(node, 'pitch/octave')) + self.alter = int(_getNodeValue(node, 'pitch/alter', 0)) + elif self.node.getElementsByTagName('rest') : + self.isRest = True + else : + NotImplementedError(self.node.toxml('utf-8')) + self._duration = float(_getNodeValue(node, 'duration')) self.lyrics = [] for ly in node.getElementsByTagName('lyric') : @@ -115,6 +202,16 @@ class Note(object) : self.previous = previous self.next = None + def __str__(self) : + return (u'%5s %2s %2d %4s' % (self.nom, self.name, self.midi, round(self.duration, 2))).encode('utf-8') + + def __repr__(self) : + return self.name.encode('utf-8') + + def addDuration(self, note) : + self._duration = self.duration + note.duration + self.divisions = 1 + @property def midi(self) : mid = DIATO_SCALE[self.step] @@ -136,6 +233,20 @@ class Note(object) : 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) + class Lyric(object) : @@ -151,9 +262,12 @@ class Lyric(object) : self.syllabic = _getNodeValue(node, 'syllabic', 'single') self.text = _getNodeValue(node, 'text') - def __str__(self) : + def syllabus(self, encoding='utf-8'): text = self._syllabicModifiers[self.syllabic] % self.text - return text.encode('utf-8') + return text.encode(encoding) + + def __str__(self) : + return self.syllabus() __repr__ = __str__ @@ -170,7 +284,7 @@ def _getNodeValue(node, path, default=_marker) : else : return default -def musicXml2Song(input, output, partIndex=0, printNotes=False) : +def musicXml2Song(input, partIndex=0, printNotes=False) : if isinstance(input, StringTypes) : input = open(input, 'r') @@ -187,31 +301,12 @@ def musicXml2Song(input, output, partIndex=0, printNotes=False) : 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) + + return part def main() : - usage = "%prog musicXmlFile.xml outputSongFile.smwi [options]" + usage = "%prog musicXmlFile.xml [options]" op = OptionParser(usage) op.add_option("-i", "--part-index", dest="partIndex" , default = 0 @@ -223,10 +318,10 @@ def main() : options, args = op.parse_args() - if len(args) != 2 : + if len(args) != 1 : raise SystemExit(op.format_help()) - musicXml2Song(args[0], args[1], partIndex=options.partIndex, printNotes=options.printNotes) + musicXml2Song(args[0], partIndex=options.partIndex, printNotes=options.printNotes)