+++ /dev/null
-# -*- 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) :
-
- 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 = []
- if autoDetectChorus :
- self._findChorus()
- self._findVersesLoops()
-
- def _parseMusic(self) :
- divisions = 0
- 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)
- if not note.isRest :
- measureNotes.append(note)
- if previous :
- previous.next = note
- else :
- previous.addDuration(note)
- continue
- previous = note
- 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
- 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
- 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"
- 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 :
- 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]<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 Tone(object) :
-
- @staticmethod
- def midi_to_step_alter_octave(midi):
- stepIndex = midi % 12
- step, alter = CHROM_SCALE[stepIndex]
- octave = midi / 12 - 1
- return step, alter, octave
-
-
- def __init__(self, *args) :
- if len(args) == 3 :
- self.step, self.alter, self.octave = args
- elif len(args) == 1 :
- midi = args[0]
- self.step, self.alter, self.octave = Tone.midi_to_step_alter_octave(midi)
-
- @property
- def midi(self) :
- mid = DIATO_SCALE[self.step]
- mid = mid + (self.octave - OCTAVE_REF) * 12
- mid = mid + self.alter
- return mid
-
-
- @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
-
-
-
-class Note(Tone) :
- scale = [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]
-
- def __init__(self, node, divisions, previous) :
- self.node = node
- 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') :
- self.lyrics.append(Lyric(ly))
-
- self.divisions = divisions
- 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]
-# 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)
-
-
-class Lyric(object) :
-
- _syllabicModifiers = {
- 'single' : '%s',
- 'begin' : '%s -',
- 'middle' : '- %s -',
- 'end' : '- %s'
- }
-
- def __init__(self, node) :
- self.node = node
- self.syllabic = _getNodeValue(node, 'syllabic', 'single')
- self.text = _getNodeValue(node, 'text')
-
- def syllabus(self, encoding='utf-8'):
- text = self._syllabicModifiers[self.syllabic] % self.text
- return text.encode(encoding)
-
- def __str__(self) :
- return self.syllabus()
- __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, 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()
-
- 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)")
-
- options, args = op.parse_args()
-
- if len(args) != 1 :
- raise SystemExit(op.format_help())
-
- musicXml2Song(args[0], partIndex=options.partIndex, printNotes=options.printNotes)
-
-
-
-if __name__ == '__main__' :
- sys.exit(main())