From 3d4f4e5642eda2b5e22e868cba106db4c4df0dbe Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Tue, 9 May 2006 14:11:11 +0000 Subject: [PATCH] *** empty log message *** --- ChangeLog | 34 +++++ python/musicexp.py | 106 ++++++++++--- python/musicxml.py | 275 ++++++++++++++++++++++++---------- scripts/musicxml2ly.py | 333 ++++++++++++++++++++++++++++------------- 4 files changed, 552 insertions(+), 196 deletions(-) diff --git a/ChangeLog b/ChangeLog index a652a22477..1897fbd9d1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,40 @@ 2006-05-09 Han-Wen Nienhuys + * scripts/musicxml2ly.py (musicxml_clef_to_lily): use new + Attributes methods + (musicxml_time_to_lily): idem + (musicxml_key_to_lily): idem + (instrument_drumtype_dict): dict for supported drumtypes. + (LilyPondVoiceBuilder.__init__): new class: sanely keep track of + moments and pending mm rests + (musicxml_voice_to_lily_voice): rewrite to use LilyPondVoiceBuilder + (musicxml_voice_to_lily_voice): strip KeyChangeEvents for drums. + (musicxml_voice_to_lily_voice): add mode change. + (option_parser): lxml.etree (http://codespeak.net/lxml/) for more + speed and less memory usage (factor 5 to 10). + (convert): write -defs.ly and driver file separately so people + can script their own part extraction. + + * python/musicxml.py (minidom_demarshal_node): new function: + separate minidom handling. + (lxml_demarshal_node): new function: support lxml.etree too. + (Xml_node.message): new function: verbose error message, with XML + path to offending node. + (Attributes.get_measure_length): sane interface to MusicXML attributes. + (Part_list.generate_id_instrument_dict): new method: collect + instrument names, to be able to set drum_type. + (Part.interpret): handle underfull measures + (Part.interpret): assign instrument names. + + * python/musicexp.py (Output_printer.close): new method + (MusicWrapper.print_ly): new class: support other modes, + eg. \drummode + (BarCheck.print_ly): new class. Support bar checks, with comments + and fancy barchecks. + (NoteEvent.__init__): also set drum_type for drum notes. + (MultiMeasureRest.lisp_expression): dump mm rests. + * lily/paper-column-engraver.cc (stop_translation_timestep): set line-break-permission if forbidBreak is not set. diff --git a/python/musicexp.py b/python/musicexp.py index 2f54f36b7b..e7b3870425 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -94,7 +94,6 @@ class Output_printer: self.dump (arg) def dump (self, str): - if self._skipspace: self._skipspace = False self.unformatted_output (str) @@ -103,6 +102,13 @@ class Output_printer: for w in words: self.add_word (w) + + def close (self): + self.newline () + self._file.close () + self._file = None + + class Duration: def __init__ (self): self.duration_log = 0 @@ -143,7 +149,7 @@ class Duration: def get_length (self): dot_fact = Rational( (1 << (1 + self.dots))-1, - 1 << self.dots) + 1 << self.dots) log = abs (self.duration_log) dur = 1 << log @@ -182,11 +188,10 @@ class Pitch: c.octave += c.step / 7 c.step = c.step % 7 - def lisp_expression (self): return '(ly:make-pitch %d %d %d)' % (self.octave, - self.step, - self.alteration) + self.step, + self.alteration) def copy (self): p = Pitch () @@ -218,6 +223,7 @@ class Pitch: str += "," * (-self.octave - 1) return str + def print_ly (self, outputter): outputter (self.ly_expression()) @@ -249,7 +255,6 @@ class Music: name = self.name() props = self.get_properties () -# props += 'start %f ' % self.start return "(make-music '%s %s)" % (name, props) @@ -267,15 +272,15 @@ class Music: if not text: return - if text == '\n': printer.newline () return + lines = string.split (text, '\n') for l in lines: if l: - printer.dump ('% ' + l) + printer.unformatted_output ('% ' + l) printer.newline () @@ -295,6 +300,15 @@ class MusicWrapper (Music): def print_ly (self, func): self.element.print_ly (func) +class ModeChangingMusicWrapper (MusicWrapper): + def __init__ (self): + MusicWrapper.__init__ (self) + self.mode = 'notemode' + + def print_ly (self, func): + func ('\\%s' % self.mode) + MusicWrapper.print_ly (self, func) + class TimeScaledMusic (MusicWrapper): def print_ly (self, func): func ('\\times %d/%d ' % @@ -429,12 +443,30 @@ class EventChord(NestedMusic): else: pass - # print 'huh', rest_events, note_events, other_events for e in other_events: e.print_ly (printer) self.print_comment (printer) + +class BarCheck (Music): + def __init__ (self): + Music.__init__ (self) + self.bar_number = 0 + + def print_ly (self, printer): + if self.bar_number > 0 and (self.bar_number % 10) == 0: + printer.dump ("| \\barNumberCheck #%d " % self.bar_number) + printer.newline () + else: + printer.dump ("| ") + printer.print_verbatim (' %% %d' % self.bar_number) + printer.newline () + + + def ly_expression (self): + return " | " + class Event(Music): pass @@ -477,7 +509,7 @@ class RhythmicEvent(Event): def get_properties (self): return ("'duration %s" - % self.duration.lisp_expression ()) + % self.duration.lisp_expression ()) class RestEvent (RhythmicEvent): def ly_expression (self): @@ -494,15 +526,21 @@ class SkipEvent (RhythmicEvent): class NoteEvent(RhythmicEvent): def __init__ (self): RhythmicEvent.__init__ (self) - self.pitch = Pitch() + self.pitch = None + self.drum_type = None self.cautionary = False self.forced_accidental = False def get_properties (self): - return ("'pitch %s\n 'duration %s" - % (self.pitch.lisp_expression (), - self.duration.lisp_expression ())) + str = RhythmicEvent.get_properties () + + if self.pitch: + str += self.pitch.lisp_expression () + elif self.drum_type: + str += "'drum-type '%s" % self.drum_type + return str + def pitch_mods (self): excl_question = '' if self.cautionary: @@ -513,13 +551,21 @@ class NoteEvent(RhythmicEvent): return excl_question def ly_expression (self): - return '%s%s%s' % (self.pitch.ly_expression (), - self.pitch_mods(), - self.duration.ly_expression ()) + if self.pitch: + return '%s%s%s' % (self.pitch.ly_expression (), + self.pitch_mods(), + self.duration.ly_expression ()) + elif self.drum_type: + return '%s%s' (self.drum_type, + self.duration.ly_expression ()) def print_ly (self, printer): - self.pitch.print_ly (printer) - printer (self.pitch_mods ()) + if self.pitch: + self.pitch.print_ly (printer) + printer (self.pitch_mods ()) + else: + printer (self.drum_type) + self.duration.print_ly (printer) class KeySignatureChange (Music): @@ -576,6 +622,28 @@ class ClefChange (Music): return clefsetting +class MultiMeasureRest(Music): + + def lisp_expression (self): + return """ +(make-music + 'MultiMeasureRestMusicGroup + 'elements + (list (make-music (quote BarCheck)) + (make-music + 'EventChord + 'elements + (list (make-music + 'MultiMeasureRestEvent + 'duration + %s))) + (make-music (quote BarCheck)))) +""" % self.duration.lisp_expression () + + def ly_expression (self): + return 'R%s' % self.duration.ly_expression () + + def test_pitch (): bflat = Pitch() bflat.alteration = -1 diff --git a/python/musicxml.py b/python/musicxml.py index e324f702a6..45b3523f6e 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -1,10 +1,5 @@ -import sys import new -import re -import string -from rational import Rational - -from xml.dom import minidom, Node +from rational import * class Xml_node: def __init__ (self): @@ -14,6 +9,9 @@ class Xml_node: self._name = 'xml_node' self._parent = None + def get_parent (self): + return self._parent + def is_first (self): return self._parent.get_typed_children (self.__class__)[0] == self @@ -31,6 +29,14 @@ class Xml_node: return ''.join ([c.get_text () for c in self._children]) + def message (self, msg): + print msg + + p = self + while p: + print ' In: <%s %s>' % (p._name, ' '.join (['%s=%s' % item for item in p._original.attrib.items()])) + p = p._parent + def get_typed_children (self, klass): return [c for c in self._children if isinstance(c, klass)] @@ -75,7 +81,7 @@ class Music_xml_node (Xml_node): class Duration (Music_xml_node): def get_length (self): - dur = string.atoi (self.get_text ()) * Rational (1,4) + dur = int (self.get_text ()) * Rational (1,4) return dur class Hash_comment (Music_xml_node): @@ -90,13 +96,13 @@ class Pitch (Music_xml_node): ch = self.get_unique_typed_child (class_dict[u'octave']) step = ch.get_text ().strip () - return string.atoi (step) + return int (step) def get_alteration (self): ch = self.get_maybe_exist_typed_child (class_dict[u'alter']) alter = 0 if ch: - alter = string.atoi (ch.get_text ().strip ()) + alter = int (ch.get_text ().strip ()) return alter class Measure_element (Music_xml_node): @@ -119,6 +125,7 @@ class Attributes (Measure_element): def set_attributes_from_previous (self, dict): self._dict.update (dict) + def read_self (self): for c in self.get_all_children (): self._dict[c.get_name()] = c @@ -126,20 +133,64 @@ class Attributes (Measure_element): def get_named_attribute (self, name): return self._dict[name] + def get_measure_length (self): + (n,d) = self.get_time_signature () + return Rational (n,d) + + def get_time_signature (self): + "return time sig as a (beat, beat-type) tuple" + + try: + mxl = self.get_named_attribute ('time') + + beats = mxl.get_maybe_exist_named_child ('beats') + type = mxl.get_maybe_exist_named_child ('beat-type') + return (int (beats.get_text ()), + int (type.get_text ())) + except KeyError: + print 'error: requested time signature, but time sig unknown' + return (4, 4) + + def get_clef_sign (self): + mxl = self.get_named_attribute ('clef') + sign = mxl.get_maybe_exist_named_child ('sign') + if sign: + return sign.get_text () + else: + print 'clef requested, but unknow' + return 'G' + + def get_key_signature (self): + "return (fifths, mode) tuple" + + key = self.get_named_attribute ('key') + mode_node = self.get_maybe_exist_named_child ('mode') + mode = 'major' + if mode_node: + mode = mode_node.get_text () + + fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ()) + return (fifths, mode) + + class Note (Measure_element): + def __init__ (self): + Measure_element.__init__ (self) + self.instrument_name = '' + def get_duration_log (self): ch = self.get_maybe_exist_typed_child (class_dict[u'type']) if ch: log = ch.get_text ().strip() - return {'eighth': 3, - 'quarter': 2, - 'half': 1, - '16th': 4, - '32nd': 5, + return {'eighth': 3, + 'quarter': 2, + 'half': 1, + '16th': 4, + '32nd': 5, 'breve': -1, - 'long': -2, - 'whole': 0} [log] + 'long': -2, + 'whole': 0} [log] else: return 0 @@ -150,13 +201,37 @@ class Note (Measure_element): return self.get_typed_children (class_dict[u'pitch']) class Part_list (Music_xml_node): - pass - + def __init__ (self): + Music_xml_node.__init__ (self) + self._id_instrument_name_dict = {} + + def generate_id_instrument_dict (self): + + ## not empty to make sure this happens only once. + mapping = {1: 1} + for score_part in self.get_named_children ('score-part'): + for instr in score_part.get_named_children ('score-instrument'): + id = instr.id + name = instr.get_named_child ("instrument-name") + mapping[id] = name.get_text () + + self._id_instrument_name_dict = mapping + + def get_instrument (self, id): + if not self._id_instrument_name_dict: + self.generate_id_instrument_dict() + + try: + return self._id_instrument_name_dict[id] + except KeyError: + print "Opps, couldn't find instrument for ID=", id + return "Grand Piano" + class Measure(Music_xml_node): def get_notes (self): return self.get_typed_children (class_dict[u'note']) - + class Musicxml_voice: def __init__ (self): self._elements = [] @@ -182,38 +257,79 @@ class Part (Music_xml_node): def __init__ (self): self._voices = [] + def get_part_list (self): + n = self + while n and n.get_name() != 'score-partwise': + n = n._parent + + return n.get_named_child ('part-list') + def interpret (self): """Set durations and starting points.""" - + + part_list = self.get_part_list () + now = Rational (0) factor = Rational (1) - attr_dict = {} + attributes_dict = {} + attributes_object = None measures = self.get_typed_children (Measure) - + for m in measures: + measure_start_moment = now + measure_position = Rational (0) for n in m.get_all_children (): dur = Rational (0) - if n.__class__ == Attributes: - n.set_attributes_from_previous (attr_dict) + if n.__class__ == Attributes: + n.set_attributes_from_previous (attributes_dict) n.read_self () - attr_dict = n._dict.copy () - + attributes_dict = n._dict.copy () + attributes_object = n + factor = Rational (1, - string.atoi (attr_dict['divisions'] - .get_text ())) + int (attributes_dict['divisions'].get_text ())) elif (n.get_maybe_exist_typed_child (Duration) and not n.get_maybe_exist_typed_child (Chord)): + mxl_dur = n.get_maybe_exist_typed_child (Duration) dur = mxl_dur.get_length () * factor + if n.get_name() == 'backup': dur = - dur if n.get_maybe_exist_typed_child (Grace): dur = Rational (0) + rest = n.get_maybe_exist_typed_child (Rest) + if (rest + and attributes_object + and attributes_object.get_measure_length () == dur): + + rest._is_whole_measure = True + + n._when = now + n._measure_position = measure_position n._duration = dur now += dur + measure_position += dur + if n._name == 'note': + instrument = n.get_maybe_exist_named_child ('instrument') + if instrument: + n.instrument_name = part_list.get_instrument (instrument.id) + + if attributes_object: + length = attributes_object.get_measure_length () + new_now = measure_start_moment + length + + if now <> new_now: + problem = 'incomplete' + if now > new_now: + problem = 'overfull' + + m.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now)) + + now = new_now def extract_voices (part): voices = {} @@ -269,7 +385,7 @@ class Time_modification(Music_xml_node): def get_fraction (self): b = self.get_maybe_exist_typed_child (class_dict['actual-notes']) a = self.get_maybe_exist_typed_child (class_dict['normal-notes']) - return (string.atoi(a.get_text ()), string.atoi (b.get_text ())) + return (int(a.get_text ()), int (b.get_text ())) class Accidental (Music_xml_node): def __init__ (self): @@ -288,7 +404,9 @@ class Slur (Music_xml_node): class Beam (Music_xml_node): def get_type (self): return self.get_text () - + def is_primary (self): + return self.number == "1" + class Chord (Music_xml_node): pass @@ -299,7 +417,12 @@ class Alter (Music_xml_node): pass class Rest (Music_xml_node): - pass + def __init__ (self): + Music_xml_node.__init__ (self) + self._is_whole_measure = False + def is_whole_measure (self): + return self._is_whole_measure + class Mode (Music_xml_node): pass class Tied (Music_xml_node): @@ -312,6 +435,11 @@ class Grace (Music_xml_node): class Staff (Music_xml_node): pass +class Instrument (Music_xml_node): + pass + +## need this, not all classes are instantiated +## for every input file. class_dict = { '#comment': Hash_comment, 'accidental': Accidental, @@ -322,6 +450,7 @@ class_dict = { 'dot': Dot, 'duration': Duration, 'grace': Grace, + 'instrument': Instrument, 'mode' : Mode, 'measure': Measure, 'notations': Notations, @@ -345,32 +474,48 @@ def name2class_name (name): return str (name) -def create_classes (names, dict): - for n in names: - if dict.has_key (n): - continue - - class_name = name2class_name (n) +def get_class (name): + try: + return class_dict[name] + except KeyError: + class_name = name2class_name (name) klass = new.classobj (class_name, (Music_xml_node,) , {}) - dict[n] = klass + class_dict[name] = klass + return klass + +def lxml_demarshal_node (node): + name = node.tag + + if name is None: + return None + klass = get_class (name) + py_node = klass() + + py_node._original = node + py_node._name = name + py_node._data = node.text + py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()] + py_node._children = filter (lambda x: x, py_node._children) + + for c in py_node._children: + c._parent = py_node -def element_names (node, dict): - dict[node.nodeName] = 1 - for cn in node.childNodes: - element_names (cn, dict) - return dict + for (k,v) in node.items (): + py_node.__dict__[k] = v + + return py_node -def demarshal_node (node): +def minidom_demarshal_node (node): name = node.nodeName - klass = class_dict[name] + + klass = get_class (name) py_node = klass() py_node._name = name - py_node._children = [demarshal_node (cn) for cn in node.childNodes] + py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes] for c in py_node._children: c._parent = py_node if node.attributes: - for (nm, value) in node.attributes.items(): py_node.__dict__[nm] = value @@ -381,34 +526,12 @@ def demarshal_node (node): py_node._original = node return py_node -def strip_white_space (node): - node._children = \ - [c for c in node._children - if not (c._original.nodeType == Node.TEXT_NODE and - re.match (r'^\s*$', c._data))] - - for c in node._children: - strip_white_space (c) - -def create_tree (name): - doc = minidom.parse(name) - node = doc.documentElement - names = element_names (node, {}).keys() - create_classes (names, class_dict) - - return demarshal_node (node) - -def test_musicxml (tree): - m = tree._children[-2] - print m - -def read_musicxml (name): - tree = create_tree (name) - strip_white_space (tree) - return tree if __name__ == '__main__': - tree = read_musicxml ('BeetAnGeSample.xml') - test_musicxml (tree) - - + import lxml.etree + + tree = lxml.etree.parse ('beethoven.xml') + mxl_tree = lxml_demarshal_node (tree.getroot ()) + ks = class_dict.keys() + ks.sort() + print '\n'.join (ks) diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 71b41d9dc3..f1125b15d6 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -6,6 +6,9 @@ import re import os import string from gettext import gettext as _ +import musicxml + + datadir = '@local_lilypond_datadir@' @@ -31,10 +34,10 @@ for prefix_component in ['share', 'lib']: sys.path.insert (0, datadir) +import lilylib as ly import musicxml import musicexp -import lilylib as ly from rational import Rational @@ -100,41 +103,31 @@ def group_tuplets (music_list, events): return new_list -def musicxml_clef_to_lily (mxl): - sign = mxl.get_maybe_exist_named_child ('sign') +def musicxml_clef_to_lily (attributes): change = musicexp.ClefChange () - if sign: - change.type = sign.get_text () + change.type = attributes.get_clef_sign () return change - -def musicxml_time_to_lily (mxl): - beats = mxl.get_maybe_exist_named_child ('beats') - type = mxl.get_maybe_exist_named_child ('beat-type') +def musicxml_time_to_lily (attributes): + (beats, type) = attributes.get_time_signature () + change = musicexp.TimeSignatureChange() - change.fraction = (string.atoi(beats.get_text ()), - string.atoi(type.get_text ())) + change.fraction = (beats, type) return change -def musicxml_key_to_lily (mxl): +def musicxml_key_to_lily (attributes): start_pitch = musicexp.Pitch () + (fifths, mode) = attributes.get_key_signature () try: - mode = mxl.get_maybe_exist_named_child ('mode') - if mode: - mode = mode.get_text () - else: - mode = 'major' - - (n,a) = { 'major' : (0,0), - 'minor' : (6,0), + (n,a) = { + 'major' : (0,0), + 'minor' : (6,0), }[mode] start_pitch.step = n start_pitch.alteration = a except KeyError: print 'unknown mode', mode - - fifths = string.atoi (mxl.get_maybe_exist_named_child ('fifths').get_text ()) fifth = musicexp.Pitch() fifth.step = 4 @@ -166,19 +159,10 @@ def musicxml_attributes_to_lily (attrs): ## ugh: you get clefs spread over staves for piano if childs: - elts.append (func (childs[0])) + elts.append (func (attrs)) return elts -def create_skip_music (duration): - skip = musicexp.SkipEvent() - skip.duration.duration_log = 0 - skip.duration.factor = duration - - evc = musicexp.EventChord () - evc.append (skip) - return evc - spanner_event_dict = { 'slur' : musicexp.SlurEvent, 'beam' : musicexp.BeamEvent, @@ -208,6 +192,14 @@ def musicxml_spanner_to_lily_event (mxl_event): return ev +instrument_drumtype_dict = { + 'Acoustic Snare Drum': 'acousticsnare', + 'Side Stick': 'sidestick', + 'Open Triangle': 'opentriangle', + 'Tambourine': 'tambourine', + +} + def musicxml_note_to_lily_main_event (n): pitch = None duration = None @@ -226,59 +218,142 @@ def musicxml_note_to_lily_main_event (n): elif n.get_maybe_exist_typed_child (musicxml.Rest): event = musicexp.RestEvent() + elif n.instrument_name: + event = musicexp.NoteEvent () + event.drum_type = instrument_drumtype_dict[n.instrument_name] + + + if not event: + n.message ("could not find suitable event") event.duration = musicxml_duration_to_lily (n) return event -def musicxml_voice_to_lily_voice (voice): - ly_voice = [] - ly_now = Rational (0) - pending_skip = Rational (0) +## todo +class NegativeSkip: + def __init__ (self, here, dest): + self.here = here + self.dest = dest + +class LilyPondVoiceBuilder: + def __init__ (self): + self.elements = [] + self.end_moment = Rational (0) + self.begin_moment = Rational (0) + self.pending_multibar = Rational (0) + + def _insert_multibar (self): + r = musicexp.MultiMeasureRest () + r.duration = musicexp.Duration() + r.duration.duration_log = 0 + r.duration.factor = self.pending_multibar + self.elements.append (r) + self.begin_moment = self.end_moment + self.end_moment = self.begin_moment + self.pending_multibar + self.pending_multibar = Rational (0) + + def add_multibar_rest (self, duration): + self.pending_multibar += duration + + + def add_music (self, music, duration): + assert isinstance (music, musicexp.Music) + if self.pending_multibar > Rational (0): + self._insert_multibar () + + self.elements.append (music) + self.begin_moment = self.end_moment + self.end_moment = self.begin_moment + duration + + def add_bar_check (self, number): + b = musicexp.BarCheck () + b.bar_number = number + self.add_music (b, Rational (0)) + + def jumpto (self, moment): + current_end = self.end_moment + self.pending_multibar + diff = moment - current_end + + if diff < Rational (0): + raise NegativeSkip(current_end, moment) + + if diff > Rational (0): + skip = musicexp.SkipEvent() + skip.duration.duration_log = 0 + skip.duration.factor = diff + + evc = musicexp.EventChord () + evc.elements.append (skip) + self.add_music (evc, diff) + + def last_event_chord (self, starting_at): + if (self.elements + and isinstance (self.elements[-1], musicexp.EventChord) + and self.begin_moment == starting_at): + return self.elements[-1] + else: + self.jumpto (starting_at) + return None + def correct_negative_skip (self, goto): + self.end_moment = goto + self.begin_moment = goto + evc = musicexp.EventChord () + self.elements.append (evc) + +def musicxml_voice_to_lily_voice (voice): tuplet_events = [] + modes_found = {} + voice_builder = LilyPondVoiceBuilder() for n in voice._elements: if n.get_name () == 'forward': continue - - if isinstance (n, musicxml.Attributes): - ly_now += pending_skip - pending_skip = Rational (0) + + try: + voice_builder.jumpto (n._when) + except NegativeSkip, neg: + voice_builder.correct_negative_skip (n._when) + n.message ("Negative skip? from %s to %s, diff %s" % (neg.here, neg.dest, neg.dest - neg.here)) - ly_voice.extend (musicxml_attributes_to_lily (n)) + if isinstance (n, musicxml.Attributes): + if n.is_first () and n._measure_position == Rational (0): + voice_builder.add_bar_check (int (n.get_parent ().number)) + for a in musicxml_attributes_to_lily (n): + voice_builder.add_music (a, Rational (0)) continue - + if not n.__class__.__name__ == 'Note': print 'not a Note or Attributes?', n continue - if n.is_first () and ly_voice: - ly_voice[-1].comment += '\n' - + rest = n.get_maybe_exist_typed_child (musicxml.Rest) + if (rest + and rest.is_whole_measure ()): + voice_builder.add_multibar_rest (n._duration) + continue + + if n.is_first () and n._measure_position == Rational (0): + num = int (n.get_parent ().number) + voice_builder.add_bar_check (num) + main_event = musicxml_note_to_lily_main_event (n) - ev_chord = None - if None == n.get_maybe_exist_typed_child (musicxml.Chord): - ly_now += pending_skip - pending_skip = main_event.get_length () + try: + if main_event.drum_type: + modes_found['drummode'] = True + except AttributeError: + pass - if ly_now <> n._when: - diff = n._when - ly_now - if diff < Rational (0): - print 'huh: negative skip', n._when, ly_now, n._duration - diff = Rational (1,314159265) + ev_chord = voice_builder.last_event_chord (n._when) + if not ev_chord: + ev_chord = musicexp.EventChord() - ly_voice.append (create_skip_music (diff)) - ly_now = n._when - - ly_voice.append (musicexp.EventChord()) - else: - pass - ev_chord = ly_voice[-1] ev_chord.append (main_event) + voice_builder.add_music (ev_chord, n._duration) notations = n.get_maybe_exist_typed_child (musicxml.Notations) tuplet_event = None @@ -307,7 +382,8 @@ def musicxml_voice_to_lily_voice (voice): ev_chord.append (musicexp.TieEvent ()) mxl_beams = [b for b in n.get_named_children ('beam') - if b.get_type () in ('begin', 'end')] + if (b.get_type () in ('begin', 'end') + and b.is_primary ())] if mxl_beams: beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0]) if beam_ev: @@ -321,17 +397,38 @@ def musicxml_voice_to_lily_voice (voice): tuplet_events.append ((ev_chord, tuplet_event, frac)) - ly_voice = group_tuplets (ly_voice, tuplet_events) + ## force trailing mm rests to be written out. + voice_builder.add_music (musicexp.EventChord ()) + + ly_voice = group_tuplets (voice_builder.elements, tuplet_events) seq_music = musicexp.SequentialMusic() + + if 'drummode' in modes_found.keys (): + ## \key barfs in drummode. + ly_voice = [e for e in ly_voice + if not isinstance(e, musicexp.KeySignatureChange)] seq_music.elements = ly_voice - return seq_music + + + + if len (modes_found) > 1: + print 'Too many modes found', modes_found.keys () + + return_value = seq_music + for mode in modes_found.keys (): + v = musicexp.ModeChangingMusicWrapper() + v.element = return_value + v.mode = mode + return_value = v + + return return_value def musicxml_id_to_lily (id): digits = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', - 'nine', 'ten'] + 'nine', 'ten'] for dig in digits: d = digits.index (dig) + 1 @@ -349,12 +446,10 @@ def musicxml_pitch_to_lily (mxl_pitch): p.octave = mxl_pitch.get_octave () - 4 return p - - def voices_in_part (part): """Return a Name -> Voice dictionary for PART""" part.interpret () - part.extract_voices () + part.extract_voices () voice_dict = part.get_voices () return voice_dict @@ -390,8 +485,8 @@ under certain conditions. Invoke as `lilypond --warranty' for more information. Copyright (c) 2005--2006 by - Han-Wen Nienhuys and - Jan Nieuwenhuizen + Han-Wen Nienhuys and + Jan Nieuwenhuizen """, description = @@ -399,16 +494,23 @@ Copyright (c) 2005--2006 by """ ) p.add_option ('-v', '--verbose', - action = "store_true", - dest = 'verbose', - help = 'be verbose') + action = "store_true", + dest = 'verbose', + help = 'be verbose') + + p.add_option ('', '--lxml', + action="store_true", + default=False, + dest="use_lxml", + help="Use lxml.etree; uses less memory and cpu time.") + p.add_option ('-o', '--output', - metavar = 'FILE', - action = "store", - default = None, - type = 'string', - dest = 'output', - help = 'set output file') + metavar = 'FILE', + action="store", + default=None, + type='string', + dest='output_name', + help='set output file') p.add_option_group ('', description = '''Report bugs via http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs ''') @@ -420,11 +522,14 @@ def music_xml_voice_name_to_lily_name (part, name): def print_voice_definitions (printer, voices): for (part, nv_dict) in voices.items(): + for (name, (voice, mxlvoice)) in nv_dict.items (): k = music_xml_voice_name_to_lily_name (part, name) printer.dump ('%s = ' % k) voice.print_ly (printer) printer.newline() + + def uniq_list (l): return dict ([(elt,1) for elt in l]).keys () @@ -479,44 +584,70 @@ def print_score_setup (printer, part_list, voices): printer ('>>') printer.newline () - printer ('>>') printer.newline () - - def print_ly_preamble (printer, filename): printer.dump_version () printer.print_verbatim ('%% converted from %s\n' % filename) -def convert (filename, output_name): - printer = musicexp.Output_printer() - progress ("Reading MusicXML...") +def read_musicxml (filename, use_lxml): + if use_lxml: + import lxml.etree + + tree = lxml.etree.parse (filename) + mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ()) + return mxl_tree + else: + from xml.dom import minidom, Node + + doc = minidom.parse(filename) + node = doc.documentElement + return musicxml.minidom_demarshal_node (node) + + return None + + +def convert (filename, options): + progress ("Reading MusicXML from %s ..." % filename) - tree = musicxml.read_musicxml (filename) - parts = tree.get_typed_children (musicxml.Part) - voices = get_all_voices (parts) + tree = read_musicxml (filename, options.use_lxml) part_list = [] + id_instrument_map = {} if tree.get_maybe_exist_typed_child (musicxml.Part_list): - pl = tree.get_maybe_exist_typed_child (musicxml.Part_list) - part_list = pl.get_named_children ("score-part") + mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list) + part_list = mxl_pl.get_named_children ("score-part") - if not output_name: - output_name = os.path.basename (filename) - output_name = os.path.splitext (output_name)[0] + '.ly' + parts = tree.get_typed_children (musicxml.Part) + voices = get_all_voices (parts) - - if output_name: - progress ("Output to `%s'" % output_name) - printer.set_file (open (output_name, 'w')) - - progress ("Printing as .ly...") + if not options.output_name: + options.output_name = os.path.basename (filename) + options.output_name = os.path.splitext (options.output_name)[0] + + defs_ly_name = options.output_name + '-defs.ly' + driver_ly_name = options.output_name + '.ly' + + printer = musicexp.Output_printer() + progress ("Output to `%s'" % defs_ly_name) + printer.set_file (open (defs_ly_name, 'w')) + + print_ly_preamble (printer, filename) + print_voice_definitions (printer, voices) + + printer.close () + + + progress ("Output to `%s'" % driver_ly_name) + printer = musicexp.Output_printer() + printer.set_file (open (driver_ly_name, 'w')) print_ly_preamble (printer, filename) - print_voice_definitions (printer, voices) + printer.dump (r'\include "%s"' % defs_ly_name) print_score_setup (printer, part_list, voices) printer.newline () + return voices @@ -528,7 +659,7 @@ def main (): opt_parser.print_usage() sys.exit (2) - voices = convert (args[0], options.output) + voices = convert (args[0], options) if __name__ == '__main__': main() -- 2.39.5