X-Git-Url: https://scm.cri.mines-paristech.fr/git/minwii.git/blobdiff_plain/9d4a748a0863cd7f9dd919362fb2c989e41f5f06..dc8216d3cd94fe2a5d89c6e8a4fae5de2f25bc9b:/src/app/musicxml.py diff --git a/src/app/musicxml.py b/src/app/musicxml.py index 7eedf53..0ed7085 100755 --- a/src/app/musicxml.py +++ b/src/app/musicxml.py @@ -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,22 +148,21 @@ 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 : iterable = cycle(self.verses) for verse in iterable : - print "---partie---" + #print "---partie---" repeats = len(verse[0].lyrics) if repeats > 1 : for i in range(repeats) : # couplet - print "---couplet%d---" % i + #print "---couplet%d---" % i for note in verse : yield note, i # refrain - print "---refrain---" + #print "---refrain---" for note in self.chorus : yield note, 0 else : @@ -219,7 +242,7 @@ class Tone(object) : @property def name(self) : - name = '%s%d' % (self.step, self.octave) + name = u'%s%d' % (self.step, self.octave) if self.alter < 0 : alterext = 'b' else : @@ -231,10 +254,10 @@ class Tone(object) : def nom(self) : name = FR_NOTES[self.step] if self.alter < 0 : - alterext = 'b' + alterext = u'♭' else : - alterext = '#' - name = '%s%s' % (name, abs(self.alter) * alterext) + alterext = u'#' + name = u'%s%s' % (name, abs(self.alter) * alterext) return name @@ -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__' :