]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/musicxml2ly.py
Merge master into nested-bookparts
[lilypond.git] / scripts / musicxml2ly.py
index 3c62f4600d24d5afa6616f47e05fc3d637bc08c6..6cdd72e430996ba47c1f58fd9ac3db68d941e283 100644 (file)
@@ -52,14 +52,27 @@ additional_definitions = {
           (make-circle-stencil 0.7 0.1 #f)
           (ly:make-stencil
             (list 'draw-line 0.1 0 0.1 0 1)
-            '(-0.1 . 0.1) '(0.1 . 1)
-          )
-        )
-        0.7 X
-      )
-    )
-  )
-)"""
+            '(-0.1 . 0.1) '(0.1 . 1)))
+        0.7 X))))""",
+  "eyeglasses": """eyeglassesps = #"0.15 setlinewidth
+      -0.9 0 translate
+      1.1 1.1 scale
+      1.2 0.7 moveto
+      0.7 0.7 0.5 0 361 arc
+      stroke
+      2.20 0.70 0.50 0 361 arc
+      stroke
+      1.45 0.85 0.30 0 180 arc
+      stroke
+      0.20 0.70 moveto
+      0.80 2.00 lineto
+      0.92 2.26 1.30 2.40 1.15 1.70 curveto
+      stroke
+      2.70 0.70 moveto
+      3.30 2.00 lineto
+      3.42 2.26 3.80 2.40 3.65 1.70 curveto
+      stroke"
+eyeglasses =  \markup { \with-dimensions #'(0 . 4.4) #'(0 . 2.5) \postscript #eyeglassesps }"""
 }
 
 def round_to_two_digits (val):
@@ -426,7 +439,6 @@ def extract_score_structure (part_list, staffinfo):
     return structure
 
 
-
 def musicxml_duration_to_lily (mxl_note):
     d = musicexp.Duration ()
     # if the note has no Type child, then that method spits out a warning and 
@@ -443,16 +455,26 @@ def musicxml_duration_to_lily (mxl_note):
 
 def rational_to_lily_duration (rational_len):
     d = musicexp.Duration ()
-    d.duration_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (rational_len.denominator (), -1)
-    d.factor = Rational (rational_len.numerator ())
-    if d.duration_log < 0:
+
+    rational_len.normalize_self ()
+    d_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (rational_len.denominator (), -1)
+
+    # Duration of the form 1/2^n or 3/2^n can be converted to a simple lilypond duration
+    if (d_log >= 0 and rational_len.numerator() in (1,3,5,7) ):
+        # account for the dots!
+        d.dots = (rational_len.numerator()-1)/2
+        d.duration_log = d_log - d.dots
+    elif (d_log >= 0):
+        d.duration_log = d_log
+        d.factor = Rational (rational_len.numerator ())
+    else:
         error_message (_ ("Encountered rational duration with denominator %s, "
                        "unable to convert to lilypond duration") %
                        rational_len.denominator ())
         # TODO: Test the above error message
         return None
-    else:
-        return d
+
+    return d
 
 def musicxml_partial_to_lily (partial_len):
     if partial_len > 0:
@@ -727,15 +749,10 @@ def musicxml_barline_to_lily (barline):
 
     return retval.values ()
 
-# Brackets need a special engraver added the Staff context!
-def musicxml_bracket_to_ly ():
-    layout_information.set_context_item ('Staff', '\consists "Horizontal_bracket_engraver"  % for \\startGroup and \\stopGroup brackets')
-    return musicexp.BracketSpannerEvent ()
-
 spanner_event_dict = {
     'beam' : musicexp.BeamEvent,
     'dashes' : musicexp.TextSpannerEvent,
-    'bracket' : musicxml_bracket_to_ly,
+    'bracket' : musicexp.BracketSpannerEvent,
     'glissando' : musicexp.GlissandoEvent,
     'octave-shift' : musicexp.OctaveShiftEvent,
     'pedal' : musicexp.PedalEvent,
@@ -895,42 +912,42 @@ articulations_dict = {
     #"delayed-turn": "?",
     "detached-legato": (musicexp.ShortArticulationEvent, "_"), # or "portato"
     "doit": musicxml_doit_to_lily_event,
-    #"double-tongue": "",
+    #"double-tongue": "?",
     "down-bow": "downbow",
     "falloff": musicxml_falloff_to_lily_event,
     "fingering": musicxml_fingering_event,
-    #"fingernails": "",
-    #"fret": "",
-    #"hammer-on": "",
+    #"fingernails": "?",
+    #"fret": "?",
+    #"hammer-on": "?",
     "harmonic": "flageolet",
-    #"heel": "",
+    #"heel": "?",
     "inverted-mordent": "prall",
     "inverted-turn": "reverseturn",
     "mordent": "mordent",
     "open-string": "open",
-    #"plop": "",
-    #"pluck": "",
-    #"pull-off": "",
+    #"plop": "?",
+    #"pluck": "?",
+    #"pull-off": "?",
     #"schleifer": "?",
-    #"scoop": "",
+    #"scoop": "?",
     #"shake": "?",
     "snap-pizzicato": musicxml_snappizzicato_event,
-    #"spiccato": "",
+    #"spiccato": "?",
     "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo"
     "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato"
     "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped"
-    #"stress": "",
+    #"stress": "?",
     "string": musicxml_string_event,
     "strong-accent": (musicexp.ShortArticulationEvent, "^"), # or "marcato"
-    #"tap": "",
+    #"tap": "?",
     "tenuto": (musicexp.ShortArticulationEvent, "-"), # or "tenuto"
     "thumb-position": "thumb",
-    #"toe": "",
+    #"toe": "?",
     "turn": "turn",
     "tremolo": musicxml_tremolo_to_lily_event,
     "trill-mark": "trill",
-    #"triple-tongue": "",
-    #"unstress": ""
+    #"triple-tongue": "?",
+    #"unstress": "?"
     "up-bow": "upbow",
     #"wavy-line": "?",
 }
@@ -1036,7 +1053,7 @@ def musicxml_words_to_lily_event (words):
             "medium": '',
             "large": '\\large',
             "x-large": '\\huge',
-            "xx-large": '\\bigger\\huge'
+            "xx-large": '\\larger\\huge'
         }.get (size, '')
         if font_size:
             event.markup += font_size
@@ -1140,6 +1157,76 @@ def musicxml_rehearsal_to_ly_mark (mxl_event):
     ev = musicexp.MarkEvent ("\\markup { %s }" % text)
     return ev
 
+def musicxml_harp_pedals_to_ly (mxl_event):
+    count = 0
+    result = "\\harp-pedal #\""
+    for t in mxl_event.get_named_children ('pedal-tuning'):
+      alter = t.get_named_child ('pedal-alter')
+      if alter:
+        val = int (alter.get_text ().strip ())
+        result += {1: "v", 0: "-", -1: "^"}.get (val, "")
+      count += 1
+      if count == 3:
+        result += "|"
+    ev = musicexp.MarkupEvent ()
+    ev.contents = result + "\""
+    return ev
+
+def musicxml_eyeglasses_to_ly (mxl_event):
+    needed_additional_definitions.append ("eyeglasses")
+    return musicexp.MarkEvent ("\\eyeglasses")
+
+def next_non_hash_index (lst, pos):
+    pos += 1
+    while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text):
+        pos += 1
+    return pos
+
+def musicxml_metronome_to_ly (mxl_event):
+    children = mxl_event.get_all_children ()
+    if not children:
+        return
+
+    index = -1
+    index = next_non_hash_index (children, index)
+    if isinstance (children[index], musicxml.BeatUnit): 
+        # first form of metronome-mark, using unit and beats/min or other unit
+        ev = musicexp.TempoMark ()
+        if hasattr (mxl_event, 'parentheses'):
+            ev.set_parentheses (mxl_event.parentheses == "yes")
+
+        d = musicexp.Duration ()
+        d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
+        index = next_non_hash_index (children, index)
+        if isinstance (children[index], musicxml.BeatUnitDot):
+            d.dots = 1
+            index = next_non_hash_index (children, index)
+        ev.set_base_duration (d)
+        if isinstance (children[index], musicxml.BeatUnit):
+            # Form "note = newnote"
+            newd = musicexp.Duration ()
+            newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
+            index = next_non_hash_index (children, index)
+            if isinstance (children[index], musicxml.BeatUnitDot):
+                newd.dots = 1
+                index = next_non_hash_index (children, index)
+            ev.set_new_duration (newd)
+        elif isinstance (children[index], musicxml.PerMinute):
+            # Form "note = bpm"
+            try:
+                beats = int (children[index].get_text ())
+                ev.set_beats_per_minute (beats)
+            except ValueError:
+                pass
+        else:
+            error_message (_ ("Unknown metronome mark, ignoring"))
+            return
+        return ev
+    else:
+        #TODO: Implement the other (more complex) way for tempo marks!
+        error_message (_ ("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
+        return
+
 # translate directions into Events, possible values:
 #   -) string  (MarkEvent with that command)
 #   -) function (function(mxl_event) needs to return a full Event-derived object
@@ -1149,12 +1236,12 @@ directions_dict = {
     'coda' : (musicexp.MusicGlyphMarkEvent, "coda"),
 #     'damp' : ???
 #     'damp-all' : ???
-#     'eyeglasses': ??????
-#     'harp-pedals' : 
-#     'image' : 
-#     'metronome' : 
+    'eyeglasses': musicxml_eyeglasses_to_ly,
+    'harp-pedals' : musicxml_harp_pedals_to_ly,
+#     'image' : ???
+    'metronome' : musicxml_metronome_to_ly,
     'rehearsal' : musicxml_rehearsal_to_ly_mark,
-#     'scordatura' : 
+#     'scordatura' : ???
     'segno' : (musicexp.MusicGlyphMarkEvent, "segno"),
     'words' : musicxml_words_to_lily_event,
 }
@@ -1235,7 +1322,162 @@ def musicxml_harmony_to_lily (n):
         ev = musicxml_frame_to_lily_event (f)
         if ev:
             res.append (ev)
+    return res
+
+
+def musicxml_chordpitch_to_lily (mxl_cpitch):
+    r = musicexp.ChordPitch ()
+    r.alteration = mxl_cpitch.get_alteration ()
+    r.step = musicxml_step_to_lily (mxl_cpitch.get_step ())
+    return r
+
+chordkind_dict = {
+    'major': '5',
+    'minor': 'm5',
+    'augmented': 'aug5',
+    'diminished': 'dim5',
+        # Sevenths:
+    'dominant': '7',
+    'major-seventh': 'maj7',
+    'minor-seventh': 'm7',
+    'diminished-seventh': 'dim7',
+    'augmented-seventh': 'aug7',
+    'half-diminished': 'dim5m7',
+    'major-minor': 'maj7m5',
+        # Sixths:
+    'major-sixth': '6',
+    'minor-sixth': 'm6',
+        # Ninths:
+    'dominant-ninth': '9',
+    'major-ninth': 'maj9',
+    'minor-ninth': 'm9',
+        # 11ths (usually as the basis for alteration):
+    'dominant-11th': '11',
+    'major-11th': 'maj11',
+    'minor-11th': 'm11',
+        # 13ths (usually as the basis for alteration):
+    'dominant-13th': '13.11',
+    'major-13th': 'maj13.11',
+    'minor-13th': 'm13',
+        # Suspended:
+    'suspended-second': 'sus2',
+    'suspended-fourth': 'sus4',
+        # Functional sixths:
+    # TODO
+    #'Neapolitan': '???',
+    #'Italian': '???',
+    #'French': '???',
+    #'German': '???',
+        # Other:
+    #'pedal': '???',(pedal-point bass)
+    'power': '5^3',
+    #'Tristan': '???',
+    'other': '1',
+    'none': None,
+}
+
+def musicxml_chordkind_to_lily (kind):
+    res = chordkind_dict.get (kind, None)
+    # Check for None, since a major chord is converted to ''
+    if res == None:
+        error_message (_ ("Unable to convert chord type %s to lilypond.") % kind)
+    return res
+
+def musicxml_harmony_to_lily_chordname (n):
+    res = []
+    root = n.get_maybe_exist_named_child ('root')
+    if root:
+        ev = musicexp.ChordNameEvent ()
+        ev.root = musicxml_chordpitch_to_lily (root)
+        kind = n.get_maybe_exist_named_child ('kind')
+        if kind:
+            ev.kind = musicxml_chordkind_to_lily (kind.get_text ())
+            if not ev.kind:
+                return res
+        bass = n.get_maybe_exist_named_child ('bass')
+        if bass:
+            ev.bass = musicxml_chordpitch_to_lily (bass)
+        inversion = n.get_maybe_exist_named_child ('inversion')
+        if inversion:
+            # TODO: Lilypond does not support inversions, does it?
+
+            # Mail from Carl Sorensen on lilypond-devel, June 11, 2008:
+            # 4. LilyPond supports the first inversion in the form of added 
+            # bass notes.  So the first inversion of C major would be c:/g.   
+            # To get the second inversion of C major, you would need to do 
+            # e:6-3-^5 or e:m6-^5.  However, both of these techniques 
+            # require you to know the chord and calculate either the fifth 
+            # pitch (for the first inversion) or the third pitch (for the 
+            # second inversion) so they may not be helpful for musicxml2ly.
+            inversion_count = string.atoi (inversion.get_text ())
+            if inversion_count == 1:
+              # TODO: Calculate the bass note for the inversion...
+              pass
+            pass
+        for deg in n.get_named_children ('degree'):
+            d = musicexp.ChordModification ()
+            d.type = deg.get_type ()
+            d.step = deg.get_value ()
+            d.alteration = deg.get_alter ()
+            ev.add_modification (d)
+        #TODO: convert the user-symbols attribute: 
+            #major: a triangle, like Unicode 25B3
+            #minor: -, like Unicode 002D
+            #augmented: +, like Unicode 002B
+            #diminished: (degree), like Unicode 00B0
+            #half-diminished: (o with slash), like Unicode 00F8
+        if ev and ev.root:
+            res.append (ev)
+
+    return res
 
+def musicxml_figured_bass_note_to_lily (n):
+    res = musicexp.FiguredBassNote ()
+    suffix_dict = { 'sharp' : "+", 
+                    'flat' : "-", 
+                    'natural' : "!", 
+                    'double-sharp' : "++", 
+                    'flat-flat' : "--", 
+                    'sharp-sharp' : "++", 
+                    'slash' : "/" }
+    prefix = n.get_maybe_exist_named_child ('prefix')
+    if prefix:
+        res.set_prefix (suffix_dict.get (prefix.get_text (), ""))
+    fnumber = n.get_maybe_exist_named_child ('figure-number')
+    if fnumber:
+        res.set_number (fnumber.get_text ())
+    suffix = n.get_maybe_exist_named_child ('suffix')
+    if suffix:
+        res.set_suffix (suffix_dict.get (suffix.get_text (), ""))
+    if n.get_maybe_exist_named_child ('extend'):
+        # TODO: Implement extender lines (unfortunately, in lilypond you have 
+        #       to use \set useBassFigureExtenders = ##t, which turns them on
+        #       globally, while MusicXML has a property for each note...
+        #       I'm not sure there is a proper way to implement this cleanly
+        #n.extend
+        pass
+    return res
+
+
+
+def musicxml_figured_bass_to_lily (n):
+    if not isinstance (n, musicxml.FiguredBass):
+        return
+    res = musicexp.FiguredBassEvent ()
+    for i in n.get_named_children ('figure'):
+        note = musicxml_figured_bass_note_to_lily (i)
+        if note:
+            res.append (note)
+    dur = n.get_maybe_exist_named_child ('duration')
+    if dur:
+        # apply the duration to res
+        length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4)
+        res.set_real_duration (length)
+        duration = rational_to_lily_duration (length)
+        if duration:
+            res.set_duration (duration)
+    if hasattr (n, 'parentheses') and n.parentheses == "yes":
+        res.set_parentheses (True)
     return res
 
 instrument_drumtype_dict = {
@@ -1310,6 +1552,7 @@ class LilyPondVoiceBuilder:
         self.begin_moment = Rational (0)
         self.pending_multibar = Rational (0)
         self.ignore_skips = False
+        self.has_relevant_elements = False
 
     def _insert_multibar (self):
         r = musicexp.MultiMeasureRest ()
@@ -1334,6 +1577,7 @@ class LilyPondVoiceBuilder:
         if self.pending_multibar > Rational (0):
             self._insert_multibar ()
 
+        self.has_relevant_elements = True
         self.elements.append (music)
         self.begin_moment = self.end_moment
         self.set_duration (duration)
@@ -1349,6 +1593,7 @@ class LilyPondVoiceBuilder:
         assert isinstance (command, musicexp.Music)
         if self.pending_multibar > Rational (0):
             self._insert_multibar ()
+        self.has_relevant_elements = True
         self.elements.append (command)
     def add_barline (self, barline):
         # TODO: Implement merging of default barline and custom bar line
@@ -1362,9 +1607,13 @@ class LilyPondVoiceBuilder:
         self.pending_dynamics.append (dynamic)
 
     def add_bar_check (self, number):
+        # re/store has_relevant_elements, so that a barline alone does not
+        # trigger output for figured bass, chord names
+        has_relevant = self.has_relevant_elements
         b = musicexp.BarLine ()
         b.bar_number = number
         self.add_barline (b)
+        self.has_relevant_elements = has_relevant
 
     def jumpto (self, moment):
         current_end = self.end_moment + self.pending_multibar
@@ -1386,6 +1635,7 @@ class LilyPondVoiceBuilder:
                 else:
                     duration_factor = Rational (diff.numerator ())
             else:
+                # for skips of a whole or more, simply use s1*factor
                 duration_log = 0
                 duration_factor = diff
             skip.duration.duration_log = duration_log
@@ -1429,8 +1679,11 @@ class LilyPondVoiceBuilder:
 
 class VoiceData:
     def __init__ (self):
+        self.voicename = None
         self.voicedata = None
         self.ly_voice = None
+        self.figured_bass = None
+        self.chordnames = None
         self.lyrics_dict = {}
         self.lyrics_order = []
 
@@ -1454,9 +1707,13 @@ def musicxml_voice_to_lily_voice (voice):
     inside_slur = False
     is_tied = False
     is_chord = False
+    is_beamed = False
     ignore_lyrics = False
 
     current_staff = None
+    
+    pending_figured_bass = []
+    pending_chordnames = []
 
     # Make sure that the keys in the dict don't get reordered, since
     # we need the correct ordering of the lyrics stanzas! By default,
@@ -1465,7 +1722,9 @@ def musicxml_voice_to_lily_voice (voice):
     for k in return_value.lyrics_order:
         lyrics[k] = []
 
-    voice_builder = LilyPondVoiceBuilder()
+    voice_builder = LilyPondVoiceBuilder ()
+    figured_bass_builder = LilyPondVoiceBuilder ()
+    chordnames_builder = LilyPondVoiceBuilder ()
 
     for n in voice._elements:
         if n.get_name () == 'forward':
@@ -1497,6 +1756,14 @@ def musicxml_voice_to_lily_voice (voice):
                     voice_builder.add_dynamics (a)
                 else:
                     voice_builder.add_command (a)
+            for a in musicxml_harmony_to_lily_chordname (n):
+                pending_chordnames.append (a)
+            continue
+
+        if isinstance (n, musicxml.FiguredBass):
+            a = musicxml_figured_bass_to_lily (n)
+            if a:
+                pending_figured_bass.append (a)
             continue
 
         is_chord = n.get_maybe_exist_named_child ('chord')
@@ -1515,6 +1782,8 @@ def musicxml_voice_to_lily_voice (voice):
                     number = 0
                 if number > 0:
                     voice_builder.add_bar_check (number)
+                    figured_bass_builder.add_bar_check (number)
+                    chordnames_builder.add_bar_check (number)
 
             for a in musicxml_attributes_to_lily (n):
                 voice_builder.add_command (a)
@@ -1547,11 +1816,14 @@ def musicxml_voice_to_lily_voice (voice):
                 num = 0
             if num > 0:
                 voice_builder.add_bar_check (num)
+                figured_bass_builder.add_bar_check (num)
+                chordnames_builder.add_bar_check (num)
 
         main_event = musicxml_note_to_lily_main_event (n)
         if main_event and not first_pitch:
             first_pitch = main_event.pitch
-        ignore_lyrics = inside_slur or is_tied or is_chord
+        # ignore lyrics for notes inside a slur, tie, chord or beam
+        ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed
 
         if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type:
             modes_found['drummode'] = True
@@ -1586,6 +1858,35 @@ def musicxml_voice_to_lily_voice (voice):
             if voice_builder.current_duration () == 0 and n._duration > 0:
                 voice_builder.set_duration (n._duration)
         
+        # if we have a figured bass, set its voice builder to the correct position
+        # and insert the pending figures
+        if pending_figured_bass:
+            try:
+                figured_bass_builder.jumpto (n._when)
+            except NegativeSkip, neg:
+                pass
+            for fb in pending_figured_bass:
+                # if a duration is given, use that, otherwise the one of the note
+                dur = fb.real_duration
+                if not dur:
+                    dur = ev_chord.get_length ()
+                if not fb.duration:
+                    fb.duration = ev_chord.get_duration ()
+                figured_bass_builder.add_music (fb, dur)
+            pending_figured_bass = []
+        
+        if pending_chordnames:
+            try:
+                chordnames_builder.jumpto (n._when)
+            except NegativeSkip, neg:
+                pass
+            for cn in pending_chordnames:
+                # Assign the duration of the EventChord
+                cn.duration = ev_chord.get_duration ()
+                chordnames_builder.add_music (cn, ev_chord.get_length ())
+            pending_chordnames = []
+
+
         notations_children = n.get_typed_children (musicxml.Notations)
         tuplet_event = None
         span_events = []
@@ -1650,7 +1951,13 @@ def musicxml_voice_to_lily_voice (voice):
                 ev = musicxml_spanner_to_lily_event (a)
                 if ev:
                     ev_chord.append (ev)
-                
+
+            # accidental-marks are direct children of <notation>!
+            for a in notations.get_named_children ('accidental-mark'):
+                ev = musicxml_articulation_to_lily_event (a)
+                if ev:
+                    ev_chord.append (ev)
+
             # Articulations can contain the following child elements:
             #         accent | strong-accent | staccato | tenuto |
             #         detached-legato | staccatissimo | spiccato |
@@ -1683,22 +1990,6 @@ def musicxml_voice_to_lily_voice (voice):
                     if ev:
                         ev_chord.append (ev)
 
-        # Extract the lyrics
-        if not rest and not ignore_lyrics:
-            note_lyrics_processed = []
-            note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
-            for l in note_lyrics_elements:
-                if l.get_number () < 0:
-                    for k in lyrics.keys ():
-                        lyrics[k].append (l.lyric_to_text ())
-                        note_lyrics_processed.append (k)
-                else:
-                    lyrics[l.number].append(l.lyric_to_text ())
-                    note_lyrics_processed.append (l.number)
-            for lnr in lyrics.keys ():
-                if not lnr in note_lyrics_processed:
-                    lyrics[lnr].append ("\skip4")
-
 
         mxl_beams = [b for b in n.get_named_children ('beam')
                      if (b.get_type () in ('begin', 'end')
@@ -1707,6 +1998,10 @@ def musicxml_voice_to_lily_voice (voice):
             beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
             if beam_ev:
                 ev_chord.append (beam_ev)
+                if beam_ev.span_direction == -1: # beam and thus melisma starts here
+                    is_beamed = True
+                elif beam_ev.span_direction == 1: # beam and thus melisma ends here
+                    is_beamed = False
             
         if tuplet_event:
             mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
@@ -1716,6 +2011,22 @@ def musicxml_voice_to_lily_voice (voice):
                 
             tuplet_events.append ((ev_chord, tuplet_event, frac))
 
+        # Extract the lyrics
+        if not rest and not ignore_lyrics:
+            note_lyrics_processed = []
+            note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
+            for l in note_lyrics_elements:
+                if l.get_number () < 0:
+                    for k in lyrics.keys ():
+                        lyrics[k].append (l.lyric_to_text ())
+                        note_lyrics_processed.append (k)
+                else:
+                    lyrics[l.number].append(l.lyric_to_text ())
+                    note_lyrics_processed.append (l.number)
+            for lnr in lyrics.keys ():
+                if not lnr in note_lyrics_processed:
+                    lyrics[lnr].append ("\skip4")
+
     ## force trailing mm rests to be written out.   
     voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
     
@@ -1751,6 +2062,24 @@ def musicxml_voice_to_lily_voice (voice):
         v.mode = mode
         return_value.ly_voice = v
     
+    # create \figuremode { figured bass elements }
+    if figured_bass_builder.has_relevant_elements:
+        fbass_music = musicexp.SequentialMusic ()
+        fbass_music.elements = figured_bass_builder.elements
+        v = musicexp.ModeChangingMusicWrapper()
+        v.mode = 'figuremode'
+        v.element = fbass_music
+        return_value.figured_bass = v
+    
+    # create \chordmode { chords }
+    if chordnames_builder.has_relevant_elements:
+        cname_music = musicexp.SequentialMusic ()
+        cname_music.elements = chordnames_builder.elements
+        v = musicexp.ModeChangingMusicWrapper()
+        v.mode = 'chordmode'
+        v.element = cname_music
+        return_value.chordnames = v
+    
     return return_value
 
 def musicxml_id_to_lily (id):
@@ -1827,26 +2156,30 @@ def get_all_voices (parts):
 
 
 def option_parser ():
-    p = ly.get_option_parser (usage = _ ("musicxml2ly [options] FILE.xml"),
-                             description = _ ("Convert %s to LilyPond input.") % 'MusicXML' + "\n",
-                             add_help_option=False)
+    p = ly.get_option_parser (usage = _ ("musicxml2ly [OPTION]... FILE.xml"),
+                             description =
+_ ("""Convert MusicXML from FILE.xml to LilyPond input.
+If the given filename is -, musicxml2ly reads from the command line.
+"""), add_help_option=False)
 
     p.add_option("-h", "--help",
                  action="help",
                  help=_ ("show this help and exit"))
 
     p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
-                                      +
-_ ("""This program is free software.  It is covered by the GNU General Public
-License and you are welcome to change it and/or distribute copies of it
-under certain conditions.  Invoke as `%s --warranty' for more
-information.""") % 'lilypond'
-+ """
-Copyright (c) 2005--2008 by
++
+_ ("""Copyright (c) 2005--2008 by
     Han-Wen Nienhuys <hanwen@xs4all.nl>,
     Jan Nieuwenhuizen <janneke@gnu.org> and
     Reinhold Kainhofer <reinhold@kainhofer.com>
-""")
+"""
++
+"""
+This program is free software.  It is covered by the GNU General Public
+License and you are welcome to change it and/or distribute copies of it
+under certain conditions.  Invoke as `%s --warranty' for more
+information.""") % 'lilypond')
+
     p.add_option("--version",
                  action="version",
                  help=_ ("show version number and exit"))
@@ -1902,8 +2235,8 @@ Copyright (c) 2005--2008 by
                   default = None,
                   type = 'string',
                   dest = 'output_name',
-                  help = _ ("set output filename to FILE"))
-    p.add_option_group (ly.display_encode (_ ('Bugs')),
+                  help = _ ("set output filename to FILE, stdout if -"))
+    p.add_option_group ('',
                         description = (_ ("Report bugs via")
                                      + ''' http://post.gmane.org/post.php'''
                                      '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
@@ -1917,6 +2250,14 @@ def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr):
     str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr)
     return musicxml_id_to_lily (str) 
 
+def music_xml_figuredbass_name_to_lily_name (part_id, voicename):
+    str = "Part%sVoice%sFiguredBass" % (part_id, voicename)
+    return musicxml_id_to_lily (str) 
+
+def music_xml_chordnames_name_to_lily_name (part_id, voicename):
+    str = "Part%sVoice%sChords" % (part_id, voicename)
+    return musicxml_id_to_lily (str) 
+
 def print_voice_definitions (printer, part_list, voices):
     for part in part_list:
         part_id = part.id
@@ -1926,11 +2267,21 @@ def print_voice_definitions (printer, part_list, voices):
             printer.dump ('%s = ' % k)
             voice.ly_voice.print_ly (printer)
             printer.newline()
+            if voice.chordnames:
+                cnname = music_xml_chordnames_name_to_lily_name (part_id, name)
+                printer.dump ('%s = ' % cnname )
+                voice.chordnames.print_ly (printer)
+                printer.newline()
             for l in voice.lyrics_order:
                 lname = music_xml_lyrics_name_to_lily_name (part_id, name, l)
-                printer.dump ('%s = ' %lname )
+                printer.dump ('%s = ' % lname )
                 voice.lyrics_dict[l].print_ly (printer)
                 printer.newline()
+            if voice.figured_bass:
+                fbname = music_xml_figuredbass_name_to_lily_name (part_id, name)
+                printer.dump ('%s = ' % fbname )
+                voice.figured_bass.print_ly (printer)
+                printer.newline()
 
 
 def uniq_list (l):
@@ -1939,19 +2290,25 @@ def uniq_list (l):
 # format the information about the staff in the form 
 #     [staffid,
 #         [
-#            [voiceid1, [lyricsid11, lyricsid12,...] ...],
-#            [voiceid2, [lyricsid21, lyricsid22,...] ...],
+#            [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
+#            [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
 #            ...
 #         ]
 #     ]
-# raw_voices is of the form [(voicename, lyricsids)*]
+# raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
 def format_staff_info (part_id, staff_id, raw_voices):
     voices = []
-    for (v, lyricsids) in raw_voices:
+    for (v, lyricsids, figured_bass, chordnames) in raw_voices:
         voice_name = music_xml_voice_name_to_lily_name (part_id, v)
         voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l)
                    for l in lyricsids]
-        voices.append ([voice_name, voice_lyrics])
+        figured_bass_name = ''
+        if figured_bass:
+            figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v)
+        chordnames_name = ''
+        if chordnames:
+            chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v)
+        voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name])
     return [staff_id, voices]
 
 def update_score_setup (score_structure, part_list, voices):
@@ -1973,12 +2330,12 @@ def update_score_setup (score_structure, part_list, voices):
             staves = uniq_list (staves)
             staves.sort ()
             for s in staves:
-                thisstaff_raw_voices = [(voice_name, voice.lyrics_order) 
+                thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames
                     for (voice_name, voice) in nv_dict.items ()
                     if voice.voicedata._start_staff == s]
                 staves_info.append (format_staff_info (part_id, s, thisstaff_raw_voices))
         else:
-            thisstaff_raw_voices = [(voice_name, voice.lyrics_order) 
+            thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames
                 for (voice_name, voice) in nv_dict.items ()]
             staves_info.append (format_staff_info (part_id, None, thisstaff_raw_voices))
         score_structure.set_part_information (part_id, staves_info)
@@ -2021,8 +2378,12 @@ def read_xml (io_object, use_lxml):
 def read_musicxml (filename, compressed, use_lxml):
     raw_string = None
     if compressed:
-        progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
-        z = zipfile.ZipFile (filename, "r")
+        if filename == "-":
+             progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") )
+             z = zipfile.ZipFile (sys.stdin)
+        else:
+            progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
+            z = zipfile.ZipFile (filename, "r")
         container_xml = z.read ("META-INF/container.xml")
         if not container_xml:
             return None
@@ -2039,15 +2400,21 @@ def read_musicxml (filename, compressed, use_lxml):
         if mxml_file:
             raw_string = z.read (mxml_file)
 
-    io_object = filename
     if raw_string:
         io_object = StringIO.StringIO (raw_string)
+    elif filename == "-":
+        io_object = sys.stdin
+    else:
+        io_object = filename
 
     return read_xml (io_object, use_lxml)
 
 
 def convert (filename, options):
-    progress (_ ("Reading MusicXML from %s ...") % filename)
+    if filename == "-":
+        progress (_ ("Reading MusicXML from Standard input ...") )
+    else:
+        progress (_ ("Reading MusicXML from %s ...") % filename)
 
     tree = read_musicxml (filename, options.compressed, options.use_lxml)
     score_information = extract_score_information (tree)
@@ -2074,13 +2441,19 @@ def convert (filename, options):
         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'
+    #defs_ly_name = options.output_name + '-defs.ly'
+    if (options.output_name == "-"):
+      output_ly_name = 'Standard output'
+    else:
+      output_ly_name = options.output_name + '.ly'
 
+    progress (_ ("Output to `%s'") % output_ly_name)
     printer = musicexp.Output_printer()
-    progress (_ ("Output to `%s'") % defs_ly_name)
-    printer.set_file (codecs.open (defs_ly_name, 'wb', encoding='utf-8'))
-
+    #progress (_ ("Output to `%s'") % defs_ly_name)
+    if (options.output_name == "-"):
+      printer.set_file (codecs.getwriter ("utf-8")(sys.stdout))
+    else:
+      printer.set_file (codecs.open (output_ly_name, 'wb', encoding='utf-8'))
     print_ly_preamble (printer, filename)
     print_ly_additional_definitions (printer, filename)
     if score_information:
@@ -2091,14 +2464,9 @@ def convert (filename, options):
         layout_information.print_ly (printer)
     print_voice_definitions (printer, part_list, voices)
     
-    printer.close ()
-    
-    
-    progress (_ ("Output to `%s'") % driver_ly_name)
-    printer = musicexp.Output_printer()
-    printer.set_file (codecs.open (driver_ly_name, 'wb', encoding='utf-8'))
-    print_ly_preamble (printer, filename)
-    printer.dump (r'\include "%s"' % os.path.basename (defs_ly_name))
+    printer.newline ()
+    printer.dump ("% The score definition")
+    printer.newline ()
     score_structure.print_ly (printer)
     printer.newline ()
 
@@ -2131,11 +2499,14 @@ def main ():
     conversion_settings.ignore_beaming = not options.convert_beaming
 
     # Allow the user to leave out the .xml or xml on the filename
-    filename = get_existing_filename_with_extension (args[0], "xml")
-    if not filename:
-        filename = get_existing_filename_with_extension (args[0], "mxl")
-        options.compressed = True
-    if filename and os.path.exists (filename):
+    if args[0]=="-": # Read from stdin
+        filename="-"
+    else:
+        filename = get_existing_filename_with_extension (args[0], "xml")
+        if not filename:
+            filename = get_existing_filename_with_extension (args[0], "mxl")
+            options.compressed = True
+    if filename and (filename == "-" or os.path.exists (filename)):
         voices = convert (filename, options)
     else:
         progress (_ ("Unable to find input file %s") % args[0])