]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/musicxml2ly.py
Merge remote branch 'origin/release/unstable' into HEAD
[lilypond.git] / scripts / musicxml2ly.py
index 52e697148786be8a306b4a50bd964bacfbd86e10..e6a6807440a30a3a630e7d57001f7d87af226966 100644 (file)
@@ -7,6 +7,7 @@ import os
 import string
 import codecs
 import zipfile
+import tempfile
 import StringIO
 
 """
@@ -21,7 +22,7 @@ import musicexp
 
 from rational import Rational
 
-# Store command-line options in a global variable, so we can access them everythwere
+# Store command-line options in a global variable, so we can access them everywhere
 options = None
 
 class Conversion_Settings:
@@ -30,27 +31,19 @@ class Conversion_Settings:
 
 conversion_settings = Conversion_Settings ()
 # Use a global variable to store the setting needed inside a \layout block.
-# whenever we need to change a setting or add/remove an engraver, we can access 
+# whenever we need to change a setting or add/remove an engraver, we can access
 # this layout and add the corresponding settings
 layout_information = musicexp.Layout ()
 
-def progress (str):
-    ly.stderr_write (str + '\n')
-    sys.stderr.flush ()
-
-def error_message (str):
-    ly.stderr_write (str + '\n')
-    sys.stderr.flush ()
-
 needed_additional_definitions = []
 additional_definitions = {
 
-  "tuplet-note-wrapper": """      % a formatter function, which is simply a wrapper around an existing 
+  "tuplet-note-wrapper": """      % a formatter function, which is simply a wrapper around an existing
       % tuplet formatter function. It takes the value returned by the given
-      % function and appends a note of given length. 
+      % function and appends a note of given length.
   #(define-public ((tuplet-number::append-note-wrapper function note) grob)
     (let* ((txt (if function (function grob) #f)))
-      (if txt 
+      (if txt
         (markup txt #:fontsize -5 #:note note UP)
         (markup #:fontsize -5 #:note note UP)
       )
@@ -58,8 +51,8 @@ additional_definitions = {
   )""",
 
   "tuplet-non-default-denominator": """#(define ((tuplet-number::non-default-tuplet-denominator-text denominator) grob)
-  (number->string (if denominator 
-                      denominator 
+  (number->string (if denominator
+                      denominator
                       (ly:event-property (event-cause grob) 'denominator))))
 """,
 
@@ -67,109 +60,8 @@ additional_definitions = {
     (let* ((ev (event-cause grob))
            (den (if denominator denominator (ly:event-property ev 'denominator)))
            (num (if numerator numerator (ly:event-property ev 'numerator))))
-       (format "~a:~a" den num)))
+       (format #f "~a:~a" den num)))
 """,
-
-  "compound-time-signature": """%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% Formatting of (possibly complex) compound time signatures
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-#(define-public (insert-markups l m)
-  (let* ((ll (reverse l)))
-    (let join-markups ((markups (list (car ll)))
-                       (remaining (cdr ll)))
-      (if (pair? remaining)
-        (join-markups (cons (car remaining) (cons m markups)) (cdr remaining))
-        markups))))
-
-% Use a centered-column inside a left-column, because the centered column 
-% moves its reference point to the center, which the left-column undoes. 
-% The center-column also aligns its contented centered, which is not undone...
-#(define-public (format-time-fraction time-sig-fraction)
-  (let* ((revargs (reverse (map number->string time-sig-fraction)))
-         (den (car revargs))
-         (nums (reverse (cdr revargs))))
-    (make-override-markup '(baseline-skip . 0)
-      (make-number-markup 
-        (make-left-column-markup (list
-          (make-center-column-markup (list
-            (make-line-markup (insert-markups nums "+"))
-            den))))))))
-
-#(define-public (format-complex-compound-time time-sig)
-  (let* ((sigs (map format-time-fraction time-sig)))
-    (make-override-markup '(baseline-skip . 0)
-      (make-number-markup
-        (make-line-markup 
-          (insert-markups sigs (make-vcenter-markup "+")))))))
-
-#(define-public (format-compound-time time-sig)
-  (cond
-    ((not (pair? time-sig)) (null-markup))
-    ((pair? (car time-sig)) (format-complex-compound-time time-sig))
-    (else (format-time-fraction time-sig))))
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% Measure length calculation of (possibly complex) compound time signatures
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-#(define-public (calculate-time-fraction time-sig-fraction)
-  (let* ((revargs (reverse time-sig-fraction))
-         (den (car revargs))
-         (nums (cdr revargs)))
-    (ly:make-moment (apply + nums) den)))
-
-#(define-public (calculate-complex-compound-time time-sig)
-  (let* ((sigs (map calculate-time-fraction time-sig)))
-    (let add-moment ((moment ZERO-MOMENT)
-                     (remaining sigs))
-      (if (pair? remaining)
-        (add-moment (ly:moment-add moment (car remaining)) (cdr remaining))
-        moment))))
-
-#(define-public (calculate-compound-measure-length time-sig)
-  (cond
-    ((not (pair? time-sig)) (ly:make-moment 4 4))
-    ((pair? (car time-sig)) (calculate-complex-compound-time time-sig))
-    (else (calculate-time-fraction time-sig))))
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% Base beat lenth
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-#(define-public (calculate-compound-base-beat-full time-sig)
-  (let* ((den (map last time-sig)))
-    (apply max den)))
-
-#(define-public (calculate-compound-base-beat time-sig)
-  (ly:make-moment 1 (cond
-    ((not (pair? time-sig)) 4)
-    ((pair? (car time-sig)) (calculate-compound-base-beat-full time-sig))
-    (else (calculate-compound-base-beat-full (list time-sig))))))
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% The music function to set the complex time signature
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-compoundMeter =
-#(define-music-function (parser location args) (pair?)
-  (let ((mlen (calculate-compound-measure-length args))
-        (beat (calculate-compound-base-beat args)))
-  #{
-\once \override Staff.TimeSignature #'stencil = #ly:text-interface::print
-\once \override Staff.TimeSignature #'text = #(format-compound-time $args)
-% \set Staff.beatGrouping = #(reverse (cdr (reverse $args)))
-\set Timing.measureLength = $mlen
-\set Timing.timeSignatureFraction = #(cons (ly:moment-main-numerator $mlen)
-                                           (ly:moment-main-denominator $mlen))
-\set Timing.beatLength = $beat
-
-% TODO: Implement beatGrouping and auto-beam-settings!!!
-#} ))
-"""
 }
 
 def round_to_two_digits (val):
@@ -275,14 +167,24 @@ def extract_score_information (tree):
 
     work = tree.get_maybe_exist_named_child ('work')
     if work:
-        set_if_exists ('title', work.get_work_title ())
         set_if_exists ('worknumber', work.get_work_number ())
         set_if_exists ('opus', work.get_opus ())
-    else:
-        movement_title = tree.get_maybe_exist_named_child ('movement-title')
-        if movement_title:
+
+    movement_title = tree.get_maybe_exist_named_child ('movement-title')
+
+    # use either work-title or movement-title as title.
+    # if both exist use movement-title as subtitle.
+    # if there is only a movement-title (or work-title is empty or missing) the movement-title should be typeset as a title
+    if work:
+        work_title = work.get_work_title ()
+        set_if_exists ('title', work_title)
+        if work_title == '':
             set_if_exists ('title', movement_title.get_text ())
-    
+        elif movement_title:
+            set_if_exists ('subtitle', movement_title.get_text ())
+    elif movement_title:
+        set_if_exists ('title', movement_title.get_text ())
+
     identifications = tree.get_named_children ('identification')
     for ids in identifications:
         set_if_exists ('copyright', ids.get_rights ())
@@ -290,35 +192,45 @@ def extract_score_information (tree):
         set_if_exists ('arranger', ids.get_arranger ())
         set_if_exists ('editor', ids.get_editor ())
         set_if_exists ('poet', ids.get_poet ())
-            
-        set_if_exists ('tagline', ids.get_encoding_software ())
+
         set_if_exists ('encodingsoftware', ids.get_encoding_software ())
         set_if_exists ('encodingdate', ids.get_encoding_date ())
         set_if_exists ('encoder', ids.get_encoding_person ())
         set_if_exists ('encodingdescription', ids.get_encoding_description ())
-        
+
+        set_if_exists ('source', ids.get_source ())
+
+        # miscellaneous --> texidoc
         set_if_exists ('texidoc', ids.get_file_description ());
 
         # Finally, apply the required compatibility modes
-        # Some applications created wrong MusicXML files, so we need to 
+        # Some applications created wrong MusicXML files, so we need to
         # apply some compatibility mode, e.g. ignoring some features/tags
         # in those files
         software = ids.get_encoding_software_list ()
 
         # Case 1: "Sibelius 5.1" with the "Dolet 3.4 for Sibelius" plugin
         #         is missing all beam ends => ignore all beaming information
-        if "Dolet 3.4 for Sibelius" in software:
-            conversion_settings.ignore_beaming = True
-            progress (_ ("Encountered file created by Dolet 3.4 for Sibelius, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
-        # ditto for Dolet 3.5
-        if "Dolet 3.5 for Sibelius" in software:
-            conversion_settings.ignore_beaming = True
-            progress (_ ("Encountered file created by Dolet 3.5 for Sibelius, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
-        if "Noteworthy Composer" in software:
-            conversion_settings.ignore_beaming = True
-            progress (_ ("Encountered file created by Noteworthy Composer's nwc2xml, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
-        # TODO: Check for other unsupported features
-
+        ignore_beaming_software = {
+            "Dolet 4 for Sibelius, Beta 2": "Dolet 4 for Sibelius, Beta 2",
+            "Dolet 3.5 for Sibelius": "Dolet 3.5 for Sibelius",
+            "Dolet 3.4 for Sibelius": "Dolet 3.4 for Sibelius",
+            "Dolet 3.3 for Sibelius": "Dolet 3.3 for Sibelius",
+            "Dolet 3.2 for Sibelius": "Dolet 3.2 for Sibelius",
+            "Dolet 3.1 for Sibelius": "Dolet 3.1 for Sibelius",
+            "Dolet for Sibelius 1.3": "Dolet for Sibelius 1.3",
+            "Noteworthy Composer": "Noteworthy Composer's nwc2xm[",
+        }
+        for s in software:
+            app_description = ignore_beaming_software.get (s, False);
+            if app_description:
+                conversion_settings.ignore_beaming = True
+                ly.warning (_ ("Encountered file created by %s, containing "
+                               "wrong beaming information. All beaming "
+                               "information in the MusicXML file will be "
+                               "ignored") % app_description)
+
+    # TODO: Check for other unsupported features
     return header
 
 class PartGroupInfo:
@@ -332,11 +244,18 @@ class PartGroupInfo:
     def add_end (self, g):
         self.end[getattr (g, 'number', "1")] = g
     def print_ly (self, printer):
-        error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
+        ly.warning (_ ("Unprocessed PartGroupInfo %s encountered") % self)
     def ly_expression (self):
-        error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
+        ly.warning (_ ("Unprocessed PartGroupInfo %s encountered") % self)
         return ''
 
+def musicxml_step_to_lily (step):
+    if step:
+        return (ord (step) - ord ('A') + 7 - 2) % 7
+    else:
+        return None
+
+
 def staff_attributes_to_string_tunings (mxl_attr):
     details = mxl_attr.get_maybe_exist_named_child ('staff-details')
     if not details:
@@ -346,7 +265,7 @@ def staff_attributes_to_string_tunings (mxl_attr):
     if staff_lines:
         lines = string.atoi (staff_lines.get_text ())
 
-    tunings = [0]*lines
+    tunings = [musicexp.Pitch()] * lines
     staff_tunings = details.get_named_children ('staff-tuning')
     for i in staff_tunings:
         p = musicexp.Pitch()
@@ -396,6 +315,8 @@ def staff_attributes_to_lily_staff (mxl_attr):
         if staff_lines:
             lines = string.atoi (staff_lines.get_text ())
 
+    # TODO: Handle other staff attributes like staff-space, etc.
+
     staff = None
     if clef_sign == "percussion" and lines == 1:
         staff = musicexp.RhythmicStaff ()
@@ -407,8 +328,11 @@ def staff_attributes_to_lily_staff (mxl_attr):
         staff.string_tunings = staff_attributes_to_string_tunings (attributes)
         # staff.tablature_format = ???
     else:
-        # TODO: Handle case with lines <> 5!
         staff = musicexp.Staff ()
+        # TODO: Handle case with lines <> 5!
+        if (lines != 5):
+            staff.add_context_modification ("\\override StaffSymbol #'line-count = #%s" % lines)
+
 
     return staff
 
@@ -417,7 +341,7 @@ def extract_score_structure (part_list, staffinfo):
     score = musicexp.Score ()
     structure = musicexp.StaffGroup (None)
     score.set_contents (structure)
-    
+
     if not part_list:
         return structure
 
@@ -434,9 +358,20 @@ def extract_score_structure (part_list, staffinfo):
         # Finale gives unnamed parts the name "MusicXML Part" automatically!
         if partname and partname.get_text() != "MusicXML Part":
             staff.instrument_name = partname.get_text ()
-        if el.get_maybe_exist_named_child ('part-abbreviation'):
-            staff.short_instrument_name = el.get_maybe_exist_named_child ('part-abbreviation').get_text ()
+        # part-name-display overrides part-name!
+        partname = el.get_maybe_exist_named_child ("part-name-display")
+        if partname:
+            staff.instrument_name = extract_display_text (partname)
+
+        partdisplay = el.get_maybe_exist_named_child ('part-abbreviation')
+        if partdisplay:
+            staff.short_instrument_name = partdisplay.get_text ()
+        # part-abbreviation-display overrides part-abbreviation!
+        partdisplay = el.get_maybe_exist_named_child ("part-abbreviation-display")
+        if partdisplay:
+            staff.short_instrument_name = extract_display_text (partdisplay)
         # TODO: Read in the MIDI device / instrument
+
         return staff
 
     def read_score_group (el):
@@ -539,8 +474,6 @@ def extract_score_structure (part_list, staffinfo):
                 group_starts.append (pos)
         pos += 1
 
-    if len (staves) == 1:
-        return staves[0]
     for i in staves:
         structure.append_staff (i)
     return score
@@ -554,7 +487,7 @@ def musicxml_duration_to_lily (mxl_note):
         d = musicexp.Duration ()
         d.duration_log = dur[0]
         d.dots = dur[1]
-        # Grace notes by specification have duration 0, so no time modification 
+        # Grace notes by specification have duration 0, so no time modification
         # factor is possible. It even messes up the output with *0/1
         if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace):
             d.factor = mxl_note._duration / d.get_length ()
@@ -575,17 +508,18 @@ def rational_to_lily_duration (rational_len):
     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) ):
+    dots = {1: 0, 3: 1, 7: 2, 15: 3, 31: 4, 63: 5, 127: 6}.get (rational_len.numerator(), -1)
+    if ( d_log >= dots >= 0 ):
         # account for the dots!
-        d.dots = (rational_len.numerator()-1)/2
-        d.duration_log = d_log - d.dots
+        d.duration_log = d_log - dots
+        d.dots = 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, "
+        ly.warning (_ ("Encountered rational duration with denominator %s, "
                        "unable to convert to lilypond duration") %
-                       rational_len.denominator ())
+                    rational_len.denominator ())
         # TODO: Test the above error message
         return None
 
@@ -597,7 +531,7 @@ def musicxml_partial_to_lily (partial_len):
         p.partial = rational_to_lily_duration (partial_len)
         return p
     else:
-        return Null
+        return None
 
 # Detect repeats and alternative endings in the chord event list (music_list)
 # and convert them to the corresponding musicexp objects, containing nested
@@ -681,15 +615,15 @@ def group_repeats (music_list):
                 r.repeat_count = repeat_times
                 # don't erase the first element for "implicit" repeats (i.e. no
                 # starting repeat bars at the very beginning)
-                start = repeat_start+1
+                start = repeat_start + 1
                 if repeat_start == music_start:
                     start = music_start
                 r.set_music (music_list[start:repeat_end])
                 for (start, end) in endings:
                     s = musicexp.SequentialMusic ()
-                    s.elements = music_list[start+1:end]
+                    s.elements = music_list[start + 1:end]
                     r.add_ending (s)
-                del music_list[repeat_start:final_marker+1]
+                del music_list[repeat_start:final_marker + 1]
                 music_list.insert (repeat_start, r)
                 repeat_replaced = True
             pos += 1
@@ -697,15 +631,15 @@ def group_repeats (music_list):
     return music_list
 
 
-# Extract the settings for tuplets from the <notations><tuplet> and the 
+# Extract the settings for tuplets from the <notations><tuplet> and the
 # <time-modification> elements of the note:
 def musicxml_tuplet_to_lily (tuplet_elt, time_modification):
     tsm = musicexp.TimeScaledMusic ()
-    fraction = (1,1)
+    fraction = (1, 1)
     if time_modification:
         fraction = time_modification.get_fraction ()
     tsm.numerator = fraction[0]
-    tsm.denominator  = fraction[1]
+    tsm.denominator = fraction[1]
 
 
     normal_type = tuplet_elt.get_normal_type ()
@@ -755,7 +689,7 @@ def group_tuplets (music_list, events):
     MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
     """
 
-    
+
     indices = []
     brackets = {}
 
@@ -803,15 +737,13 @@ def musicxml_clef_to_lily (attributes):
     change = musicexp.ClefChange ()
     (change.type, change.position, change.octave) = attributes.get_clef_information ()
     return change
-    
+
 def musicxml_time_to_lily (attributes):
     sig = attributes.get_time_signature ()
     if not sig:
         return None
     change = musicexp.TimeSignatureChange()
     change.fractions = sig
-    if (len(sig) != 2) or isinstance (sig[0], list):
-        needed_additional_definitions.append ("compound-time-signature")
 
     time_elm = attributes.get_maybe_exist_named_child ('time')
     if time_elm and hasattr (time_elm, 'symbol'):
@@ -824,42 +756,42 @@ def musicxml_time_to_lily (attributes):
 
     # TODO: Handle senza-misura measures
     # TODO: Handle hidden time signatures (print-object="no")
-    # TODO: What shall we do if the symbol clashes with the sig? e.g. "cut" 
+    # TODO: What shall we do if the symbol clashes with the sig? e.g. "cut"
     #       with 3/8 or "single-number" with (2+3)/8 or 3/8+2/4?
 
     return change
 
 def musicxml_key_to_lily (attributes):
-    key_sig = attributes.get_key_signature () 
+    key_sig = attributes.get_key_signature ()
     if not key_sig or not (isinstance (key_sig, list) or isinstance (key_sig, tuple)):
-        error_message (_ ("Unable to extract key signature!"))
+        ly.warning (_ ("Unable to extract key signature!"))
         return None
-    
+
     change = musicexp.KeySignatureChange()
-    
+
     if len (key_sig) == 2 and not isinstance (key_sig[0], list):
         # standard key signature, (fifths, mode)
         (fifths, mode) = key_sig
         change.mode = mode
 
-        start_pitch  = musicexp.Pitch ()
+        start_pitch = musicexp.Pitch ()
         start_pitch.octave = 0
         try:
-            (n,a) = {
-                'major'     : (0,0),
-                'minor'     : (5,0),
-                'ionian'    : (0,0),
-                'dorian'    : (1,0),
-                'phrygian'  : (2,0),
-                'lydian'    : (3,0),
-                'mixolydian': (4,0),
-                'aeolian'   : (5,0),
-                'locrian'   : (6,0),
+            (n, a) = {
+                'major'     : (0, 0),
+                'minor'     : (5, 0),
+                'ionian'    : (0, 0),
+                'dorian'    : (1, 0),
+                'phrygian'  : (2, 0),
+                'lydian'    : (3, 0),
+                'mixolydian': (4, 0),
+                'aeolian'   : (5, 0),
+                'locrian'   : (6, 0),
                 }[mode]
             start_pitch.step = n
             start_pitch.alteration = a
         except  KeyError:
-            error_message (_ ("unknown mode %s, expecting 'major' or 'minor' "
+            ly.warning (_ ("unknown mode %s, expecting 'major' or 'minor' "
                 "or a church mode!") % mode)
 
         fifth = musicexp.Pitch()
@@ -874,7 +806,12 @@ def musicxml_key_to_lily (attributes):
 
     else:
         # Non-standard key signature of the form [[step,alter<,octave>],...]
-        change.non_standard_alterations = key_sig
+        # MusicXML contains C,D,E,F,G,A,B as steps, lily uses 0-7, so convert
+        alterations = []
+        for k in key_sig:
+            k[0] = musicxml_step_to_lily (k[0])
+            alterations.append (k)
+        change.non_standard_alterations = alterations
     return change
 
 def musicxml_transpose_to_lily (attributes):
@@ -889,10 +826,10 @@ def musicxml_transpose_to_lily (attributes):
     chromatic_shift = string.atoi (transpose.get_named_child ('chromatic').get_text ())
     chromatic_shift_normalized = chromatic_shift % 12;
     (shift.step, shift.alteration) = [
-        (0,0), (0,1), (1,0), (2,-1), (2,0), 
-        (3,0), (3,1), (4,0), (5,-1), (5,0), 
-        (6,-1), (6,0)][chromatic_shift_normalized];
-    
+        (0, 0), (0, 1), (1, 0), (2, -1), (2, 0),
+        (3, 0), (3, 1), (4, 0), (5, -1), (5, 0),
+        (6, -1), (6, 0)][chromatic_shift_normalized];
+
     shift.octave += (chromatic_shift - chromatic_shift_normalized) / 12
 
     diatonic = transpose.get_maybe_exist_named_child ('diatonic')
@@ -909,30 +846,90 @@ def musicxml_transpose_to_lily (attributes):
     transposition.pitch = musicexp.Pitch ().transposed (shift)
     return transposition
 
+def musicxml_staff_details_to_lily (attributes):
+    details = attributes.get_maybe_exist_named_child ('staff-details')
+    if not details:
+        return None
+
+    ## TODO: Handle staff-type, staff-lines, staff-tuning, capo, staff-size
+    ret = []
+
+    stafflines = details.get_maybe_exist_named_child ('staff-lines')
+    if stafflines:
+        lines = string.atoi (stafflines.get_text ());
+        lines_event = musicexp.StaffLinesEvent (lines);
+        ret.append (lines_event);
+
+    return ret;
+
 
 def musicxml_attributes_to_lily (attrs):
     elts = []
-    attr_dispatch =  {
+    attr_dispatch = {
         'clef': musicxml_clef_to_lily,
         'time': musicxml_time_to_lily,
         'key': musicxml_key_to_lily,
         'transpose': musicxml_transpose_to_lily,
+        'staff-details': musicxml_staff_details_to_lily,
     }
     for (k, func) in attr_dispatch.items ():
         children = attrs.get_named_children (k)
         if children:
             ev = func (attrs)
-            if ev:
+            if isinstance (ev, list):
+              for e in ev:
+                elts.append (e)
+            elif ev:
                 elts.append (ev)
-    
+
+    return elts
+
+def extract_display_text (el):
+    child = el.get_maybe_exist_named_child ("display-text")
+    if child:
+        return child.get_text ()
+    else:
+        return False
+
+
+def musicxml_print_to_lily (el):
+    # TODO: Implement other print attributes
+    #  <!ELEMENT print (page-layout?, system-layout?, staff-layout*,
+    #          measure-layout?, measure-numbering?, part-name-display?,
+    #          part-abbreviation-display?)>
+    #  <!ATTLIST print
+    #      staff-spacing %tenths; #IMPLIED
+    #      new-system %yes-no; #IMPLIED
+    #      new-page %yes-no-number; #IMPLIED
+    #      blank-page NMTOKEN #IMPLIED
+    #      page-number CDATA #IMPLIED
+    #  >
+    elts = []
+    if (hasattr (el, "new-system") and conversion_settings.convert_page_layout):
+        val = getattr (el, "new-system")
+        if (val == "yes"):
+            elts.append (musicexp.Break ("break"))
+    if (hasattr (el, "new-page") and conversion_settings.convert_page_layout):
+        val = getattr (el, "new-page")
+        if (val == "yes"):
+            elts.append (musicexp.Break ("pageBreak"))
+    child = el.get_maybe_exist_named_child ("part-name-display")
+    if child:
+        elts.append (musicexp.SetEvent ("Staff.instrumentName",
+                                        "\"%s\"" % extract_display_text (child)))
+    child = el.get_maybe_exist_named_child ("part-abbreviation-display")
+    if child:
+        elts.append (musicexp.SetEvent ("Staff.shortInstrumentName",
+                                        "\"%s\"" % extract_display_text (child)))
     return elts
 
+
 class Marker (musicexp.Music):
     def __init__ (self):
         self.direction = 0
         self.event = None
     def print_ly (self, printer):
-        ly.stderr_write (_ ("Encountered unprocessed marker %s\n") % self)
+        ly.warning (_ ("Encountered unprocessed marker %s\n") % self)
         pass
     def ly_expression (self):
         return ""
@@ -1020,13 +1017,13 @@ spanner_type_dict = {
 
 def musicxml_spanner_to_lily_event (mxl_event):
     ev = None
-    
+
     name = mxl_event.get_name()
     func = spanner_event_dict.get (name)
     if func:
         ev = func()
     else:
-        error_message (_ ('unknown span event %s') % mxl_event)
+        ly.warning (_ ('unknown span event %s') % mxl_event)
 
 
     type = mxl_event.get_type ()
@@ -1036,7 +1033,7 @@ def musicxml_spanner_to_lily_event (mxl_event):
     if span_direction != None:
         ev.span_direction = span_direction
     else:
-        error_message (_ ('unknown span type %s for %s') % (type, name))
+        ly.warning (_ ('unknown span type %s for %s') % (type, name))
 
     ev.set_span_type (type)
     ev.line_type = getattr (mxl_event, 'line-type', 'solid')
@@ -1170,7 +1167,7 @@ articulations_dict = {
     #"shake": "?",
     "snap-pizzicato": "snappizzicato",
     #"spiccato": "?",
-    "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo"
+    "staccatissimo": (musicexp.ShortArticulationEvent, "!"), # or "staccatissimo"
     "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato"
     "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped"
     #"stress": "?",
@@ -1222,12 +1219,12 @@ def musicxml_articulation_to_lily_event (mxl_event):
 
 def musicxml_dynamics_to_lily_event (dynentry):
     dynamics_available = (
-        "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf", 
+        "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf",
         "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" )
     dynamicsname = dynentry.get_name ()
     if dynamicsname == "other-dynamics":
         dynamicsname = dynentry.get_text ()
-    if not dynamicsname or dynamicsname=="#text":
+    if not dynamicsname or dynamicsname == "#text":
         return
 
     if not dynamicsname in dynamics_available:
@@ -1254,7 +1251,7 @@ def hexcolorval_to_nr (hex_val):
 def hex_to_color (hex_val):
     res = re.match (r'#([0-9a-f][0-9a-f]|)([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$', hex_val, re.IGNORECASE)
     if res:
-        return map (lambda x: hexcolorval_to_nr (x), res.group (2,3,4))
+        return map (lambda x: hexcolorval_to_nr (x), res.group (2, 3, 4))
     else:
         return None
 
@@ -1309,7 +1306,7 @@ def musicxml_words_to_lily_event (words):
     # TODO: How should I best convert the font-family attribute?
 
     # TODO: How can I represent the underline, overline and line-through
-    #       attributes in Lilypond? Values of these attributes indicate
+    #       attributes in LilyPond? Values of these attributes indicate
     #       the number of lines
 
     return event
@@ -1327,11 +1324,11 @@ def musicxml_accordion_to_markup (mxl_event):
     if high:
         commandname += "H"
         command += """\\combine
-          \\raise #2.5 \\musicglyph #\"accordion.accDot\"
+          \\raise #2.5 \\musicglyph #\"accordion.dot\"
           """
     middle = mxl_event.get_maybe_exist_named_child ('accordion-middle')
     if middle:
-        # By default, use one dot (when no or invalid content is given). The 
+        # By default, use one dot (when no or invalid content is given). The
         # MusicXML spec is quiet about this case...
         txt = 1
         try:
@@ -1341,32 +1338,32 @@ def musicxml_accordion_to_markup (mxl_event):
         if txt == 3:
             commandname += "MMM"
             command += """\\combine
-          \\raise #1.5 \\musicglyph #\"accordion.accDot\"
+          \\raise #1.5 \\musicglyph #\"accordion.dot\"
           \\combine
-          \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.accDot\"
+          \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.dot\"
           \\combine
-          \\raise #1.5 \\translate #(cons -1 0) \\musicglyph #\"accordion.accDot\"
+          \\raise #1.5 \\translate #(cons -1 0) \\musicglyph #\"accordion.dot\"
           """
         elif txt == 2:
             commandname += "MM"
             command += """\\combine
-          \\raise #1.5 \\translate #(cons 0.5 0) \\musicglyph #\"accordion.accDot\"
+          \\raise #1.5 \\translate #(cons 0.5 0) \\musicglyph #\"accordion.dot\"
           \\combine
-          \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.accDot\"
+          \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.dot\"
           """
         elif not txt <= 0:
             commandname += "M"
             command += """\\combine
-          \\raise #1.5 \\musicglyph #\"accordion.accDot\"
+          \\raise #1.5 \\musicglyph #\"accordion.dot\"
           """
     low = mxl_event.get_maybe_exist_named_child ('accordion-low')
     if low:
         commandname += "L"
         command += """\\combine
-          \\raise #0.5 \musicglyph #\"accordion.accDot\"
+          \\raise #0.5 \musicglyph #\"accordion.dot\"
           """
 
-    command += "\musicglyph #\"accordion.accDiscant\""
+    command += "\musicglyph #\"accordion.discant\""
     command = "\\markup { \\normalsize %s }" % command
     # Define the newly built command \accReg[H][MMM][L]
     additional_definitions[commandname] = "%s = %s" % (commandname, command)
@@ -1426,7 +1423,7 @@ def musicxml_metronome_to_ly (mxl_event):
 
     index = -1
     index = next_non_hash_index (children, index)
-    if isinstance (children[index], musicxml.BeatUnit): 
+    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'):
@@ -1456,12 +1453,12 @@ def musicxml_metronome_to_ly (mxl_event):
             except ValueError:
                 pass
         else:
-            error_message (_ ("Unknown metronome mark, ignoring"))
+            ly.warning (_ ("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."))
+        ly.warning (_ ("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
         return
 
 # translate directions into Events, possible values:
@@ -1492,7 +1489,7 @@ def musicxml_direction_to_lily (n):
     if hasattr (n, 'placement') and options.convert_directions:
         dir = musicxml_direction_to_indicator (n.placement)
     dirtype_children = []
-    # TODO: The direction-type is used for grouping (e.g. dynamics with text), 
+    # TODO: The direction-type is used for grouping (e.g. dynamics with text),
     #       so we can't simply flatten them out!
     for dt in n.get_typed_children (musicxml.DirType):
         dirtype_children += dt.get_all_children ()
@@ -1517,6 +1514,7 @@ def musicxml_direction_to_lily (n):
         if ev:
             # TODO: set the correct direction! Unfortunately, \mark in ly does
             #       not seem to support directions!
+            ev.force_direction = dir
             res.append (ev)
             continue
 
@@ -1619,6 +1617,7 @@ chordkind_dict = {
     'diminished': 'dim5',
         # Sevenths:
     'dominant': '7',
+    'dominant-seventh': '7',
     'major-seventh': 'maj7',
     'minor-seventh': 'm7',
     'diminished-seventh': 'dim7',
@@ -1661,7 +1660,7 @@ 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)
+        ly.warning (_ ("Unable to convert chord type %s to lilypond.") % kind)
     return res
 
 def musicxml_harmony_to_lily_chordname (n):
@@ -1680,15 +1679,15 @@ def musicxml_harmony_to_lily_chordname (n):
             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?
+            # 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 
+            # 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:
@@ -1701,7 +1700,7 @@ def musicxml_harmony_to_lily_chordname (n):
             d.step = deg.get_value ()
             d.alteration = deg.get_alter ()
             ev.add_modification (d)
-        #TODO: convert the user-symbols attribute: 
+        #TODO: convert the user-symbols attribute:
             #major: a triangle, like Unicode 25B3
             #minor: -, like Unicode 002D
             #augmented: +, like Unicode 002B
@@ -1714,12 +1713,12 @@ def musicxml_harmony_to_lily_chordname (n):
 
 def musicxml_figured_bass_note_to_lily (n):
     res = musicexp.FiguredBassNote ()
-    suffix_dict = { 'sharp' : "+", 
-                    'flat' : "-", 
-                    'natural' : "!", 
-                    'double-sharp' : "++", 
-                    'flat-flat' : "--", 
-                    'sharp-sharp' : "++", 
+    suffix_dict = { 'sharp' : "+",
+                    'flat' : "-",
+                    'natural' : "!",
+                    'double-sharp' : "++",
+                    'flat-flat' : "--",
+                    'sharp-sharp' : "++",
                     'slash' : "/" }
     prefix = n.get_maybe_exist_named_child ('prefix')
     if prefix:
@@ -1731,7 +1730,7 @@ def musicxml_figured_bass_note_to_lily (n):
     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 
+        # 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
@@ -1752,7 +1751,7 @@ def musicxml_figured_bass_to_lily (n):
     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)
+        length = Rational(int(dur.get_text()), n._divisions) * Rational(1, 4)
         res.set_real_duration (length)
         duration = rational_to_lily_duration (length)
         if duration:
@@ -1771,7 +1770,7 @@ instrument_drumtype_dict = {
 }
 
 def musicxml_note_to_lily_main_event (n):
-    pitch  = None
+    pitch = None
     duration = None
     event = None
 
@@ -1783,16 +1782,22 @@ def musicxml_note_to_lily_main_event (n):
 
         acc = n.get_maybe_exist_named_child ('accidental')
         if acc:
-            # let's not force accs everywhere. 
-            event.cautionary = acc.editorial
+            # AccidentalCautionary in lily has parentheses
+            # so treat accidental explicitly in parentheses as cautionary
+            if hasattr(acc, 'parentheses') and acc.parentheses == "yes":
+                event.cautionary = True
+            else:
+                event.cautionary = acc.cautionary
+            # TODO: Handle editorial accidentals
+            # TODO: Handle the level-display setting for displaying brackets/parentheses
 
     elif n.get_maybe_exist_typed_child (musicxml.Unpitched):
-       # Unpitched elements have display-step and can also have
-       # display-octave.
-       unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched)
-       event = musicexp.NoteEvent ()
-       event.pitch = musicxml_unpitched_to_lily (unpitched)
-        
+        # Unpitched elements have display-step and can also have
+        # display-octave.
+        unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched)
+        event = musicexp.NoteEvent ()
+        event.pitch = musicxml_unpitched_to_lily (unpitched)
+
     elif n.get_maybe_exist_typed_child (musicxml.Rest):
         # rests can have display-octave and display-step, which are
         # treated like an ordinary note pitch
@@ -1904,17 +1909,17 @@ class LilyPondVoiceBuilder:
         self.end_moment = self.begin_moment + duration
     def current_duration (self):
         return self.end_moment - self.begin_moment
-        
-    def add_music (self, music, duration):
+
+    def add_music (self, music, duration, relevant=True):
         assert isinstance (music, musicexp.Music)
         if self.pending_multibar > Rational (0):
             self._insert_multibar ()
 
-        self.has_relevant_elements = True
+        self.has_relevant_elements = self.has_relevant_elements or relevant
         self.elements.append (music)
         self.begin_moment = self.end_moment
         self.set_duration (duration)
-        
+
         # Insert all pending dynamics right after the note/rest:
         if isinstance (music, musicexp.ChordEvent) and self.pending_dynamics:
             for d in self.pending_dynamics:
@@ -1922,18 +1927,27 @@ class LilyPondVoiceBuilder:
             self.pending_dynamics = []
 
     # Insert some music command that does not affect the position in the measure
-    def add_command (self, command):
+    def add_command (self, command, relevant=True):
         assert isinstance (command, musicexp.Music)
         if self.pending_multibar > Rational (0):
             self._insert_multibar ()
-        self.has_relevant_elements = True
+        self.has_relevant_elements = self.has_relevant_elements or relevant
         self.elements.append (command)
-    def add_barline (self, barline):
-        # TODO: Implement merging of default barline and custom bar line
-        self.add_music (barline, Rational (0))
+    def add_barline (self, barline, relevant=False):
+        # Insert only if we don't have a barline already
+        # TODO: Implement proper merging of default barline and custom bar line
+        has_relevant = self.has_relevant_elements
+        if (not (self.elements) or
+            not (isinstance (self.elements[-1], musicexp.BarLine)) or
+            (self.pending_multibar > Rational (0))):
+            self.add_music (barline, Rational (0))
+        self.has_relevant_elements = has_relevant or relevant
     def add_partial (self, command):
         self.ignore_skips = True
+        # insert the partial, but restore relevant_elements (partial is not relevant)
+        relevant = self.has_relevant_elements
         self.add_command (command)
+        self.has_relevant_elements = relevant
 
     def add_dynamics (self, dynamic):
         # store the dynamic item(s) until we encounter the next note/rest:
@@ -1942,19 +1956,17 @@ class LilyPondVoiceBuilder:
     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
         diff = moment - current_end
-        
+
         if diff < Rational (0):
-            error_message (_ ('Negative skip %s (from position %s to %s)') % 
-                             (diff, current_end, moment))
+            ly.warning (_ ('Negative skip %s (from position %s to %s)')
+                % (diff, current_end, moment))
             diff = Rational (0)
 
         if diff > Rational (0) and not (self.ignore_skips and moment == 0):
@@ -1962,7 +1974,7 @@ class LilyPondVoiceBuilder:
             duration_factor = 1
             duration_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (diff.denominator (), -1)
             duration_dots = 0
-            # TODO: Use the time signature for skips, too. Problem: The skip 
+            # TODO: Use the time signature for skips, too. Problem: The skip
             #       might not start at a measure boundary!
             if duration_log > 0: # denominator is a power of 2...
                 if diff.numerator () == 3:
@@ -1980,7 +1992,7 @@ class LilyPondVoiceBuilder:
 
             evc = musicexp.ChordEvent ()
             evc.elements.append (skip)
-            self.add_music (evc, diff)
+            self.add_music (evc, diff, False)
 
         if diff > Rational (0) and moment == 0:
             self.ignore_skips = False
@@ -1990,7 +2002,7 @@ class LilyPondVoiceBuilder:
         value = None
 
         # if the position matches, find the last ChordEvent, do not cross a bar line!
-        at = len( self.elements ) - 1
+        at = len(self.elements) - 1
         while (at >= 0 and
                not isinstance (self.elements[at], musicexp.ChordEvent) and
                not isinstance (self.elements[at], musicexp.BarLine)):
@@ -2005,7 +2017,7 @@ class LilyPondVoiceBuilder:
             self.jumpto (starting_at)
             value = None
         return value
-        
+
     def correct_negative_skip (self, goto):
         self.end_moment = goto
         self.begin_moment = goto
@@ -2023,12 +2035,6 @@ class VoiceData:
         self.lyrics_dict = {}
         self.lyrics_order = []
 
-def musicxml_step_to_lily (step):
-    if step:
-       return (ord (step) - ord ('A') + 7 - 2) % 7
-    else:
-       return None
-
 def measure_length_from_attributes (attr, current_measure_length):
     len = attr.get_measure_length ()
     if not len:
@@ -2041,7 +2047,7 @@ def musicxml_voice_to_lily_voice (voice):
     lyrics = {}
     return_value = VoiceData ()
     return_value.voicedata = voice
-    
+
     # First pitch needed for relative mode (if selected in command-line options)
     first_pitch = None
 
@@ -2053,7 +2059,7 @@ def musicxml_voice_to_lily_voice (voice):
     ignore_lyrics = False
 
     current_staff = None
-    
+
     pending_figured_bass = []
     pending_chordnames = []
 
@@ -2071,6 +2077,7 @@ def musicxml_voice_to_lily_voice (voice):
     voice_builder.set_measure_length (current_measure_length)
 
     for n in voice._elements:
+        tie_started = False
         if n.get_name () == 'forward':
             continue
         staff = n.get_maybe_exist_named_child ('staff')
@@ -2084,6 +2091,8 @@ def musicxml_voice_to_lily_voice (voice):
             a = musicxml_partial_to_lily (n.partial)
             if a:
                 voice_builder.add_partial (a)
+                figured_bass_builder.add_partial (a)
+                chordnames_builder.add_partial (a)
             continue
 
         is_chord = n.get_maybe_exist_named_child ('chord')
@@ -2091,8 +2100,12 @@ def musicxml_voice_to_lily_voice (voice):
         if not is_chord and not is_after_grace:
             try:
                 voice_builder.jumpto (n._when)
+                figured_bass_builder.jumpto (n._when)
+                chordnames_builder.jumpto (n._when)
             except NegativeSkip, neg:
                 voice_builder.correct_negative_skip (n._when)
+                figured_bass_builder.correct_negative_skip (n._when)
+                chordnames_builder.correct_negative_skip (n._when)
                 n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here))
 
         if isinstance (n, musicxml.Barline):
@@ -2100,8 +2113,18 @@ def musicxml_voice_to_lily_voice (voice):
             for a in barlines:
                 if isinstance (a, musicexp.BarLine):
                     voice_builder.add_barline (a)
+                    figured_bass_builder.add_barline (a, False)
+                    chordnames_builder.add_barline (a, False)
                 elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker):
                     voice_builder.add_command (a)
+                    figured_bass_builder.add_barline (a, False)
+                    chordnames_builder.add_barline (a, False)
+            continue
+
+
+        if isinstance (n, musicxml.Print):
+            for a in musicxml_print_to_lily (n):
+                voice_builder.add_command (a, False)
             continue
 
         # Continue any multimeasure-rests before trying to add bar checks!
@@ -2166,7 +2189,7 @@ def musicxml_voice_to_lily_voice (voice):
         if not n.__class__.__name__ == 'Note':
             n.message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline'))
             continue
-        
+
         main_event = musicxml_note_to_lily_main_event (n)
         if main_event and not first_pitch:
             first_pitch = main_event.pitch
@@ -2177,7 +2200,7 @@ def musicxml_voice_to_lily_voice (voice):
             modes_found['drummode'] = True
 
         ev_chord = voice_builder.last_event_chord (n._when)
-        if not ev_chord: 
+        if not ev_chord:
             ev_chord = musicexp.ChordEvent()
             voice_builder.add_music (ev_chord, n._duration)
 
@@ -2190,7 +2213,7 @@ def musicxml_voice_to_lily_voice (voice):
             grace_chord = None
 
             # after-graces and other graces use different lists; Depending on
-            # whether we have a chord or not, obtain either a new ChordEvent or 
+            # whether we have a chord or not, obtain either a new ChordEvent or
             # the previous one to create a chord
             if is_after_grace:
                 if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
@@ -2221,7 +2244,7 @@ def musicxml_voice_to_lily_voice (voice):
             # with duration 0. The following correct this when we hit the real note!
             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:
@@ -2238,7 +2261,7 @@ def musicxml_voice_to_lily_voice (voice):
                     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)
@@ -2255,9 +2278,9 @@ def musicxml_voice_to_lily_voice (voice):
         span_events = []
 
         # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
-        # +tied | +slur | +tuplet | glissando | slide | 
+        # +tied | +slur | +tuplet | glissando | slide |
         #    ornaments | technical | articulations | dynamics |
-        #    +fermata | arpeggiate | non-arpeggiate | 
+        #    +fermata | arpeggiate | non-arpeggiate |
         #    accidental-mark | other-notation
         for notations in notations_children:
             for tuplet_event in notations.get_tuplets():
@@ -2297,13 +2320,14 @@ def musicxml_voice_to_lily_voice (voice):
                 if mxl_tie and mxl_tie.type == 'start':
                     ev_chord.append (musicexp.TieEvent ())
                     is_tied = True
+                    tie_started = True
                 else:
                     is_tied = False
 
             fermatas = notations.get_named_children ('fermata')
             for a in fermatas:
                 ev = musicxml_fermata_to_lily_event (a)
-                if ev: 
+                if ev:
                     ev_chord.append (ev)
 
             arpeggiate = notations.get_named_children ('arpeggiate')
@@ -2334,7 +2358,7 @@ def musicxml_voice_to_lily_voice (voice):
             # Articulations can contain the following child elements:
             #         accent | strong-accent | staccato | tenuto |
             #         detached-legato | staccatissimo | spiccato |
-            #         scoop | plop | doit | falloff | breath-mark | 
+            #         scoop | plop | doit | falloff | breath-mark |
             #         caesura | stress | unstress
             # Technical can contain the following child elements:
             #         up-bow | down-bow | harmonic | open-string |
@@ -2344,7 +2368,7 @@ def musicxml_voice_to_lily_voice (voice):
             #         toe | fingernails | other-technical
             # Ornaments can contain the following child elements:
             #         trill-mark | turn | delayed-turn | inverted-turn |
-            #         shake | wavy-line | mordent | inverted-mordent | 
+            #         shake | wavy-line | mordent | inverted-mordent |
             #         schleifer | tremolo | other-ornament, accidental-mark
             ornaments = notations.get_named_children ('ornaments')
             ornaments += notations.get_named_children ('articulations')
@@ -2366,7 +2390,7 @@ def musicxml_voice_to_lily_voice (voice):
 
         mxl_beams = [b for b in n.get_named_children ('beam')
                      if (b.get_type () in ('begin', 'end')
-                         and b.is_primary ())] 
+                         and b.is_primary ())]
         if mxl_beams and not conversion_settings.ignore_beaming:
             beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
             if beam_ev:
@@ -2392,9 +2416,15 @@ def musicxml_voice_to_lily_voice (voice):
                 if not lnr in note_lyrics_processed:
                     lyrics[lnr].append ("\skip4")
 
-    ## force trailing mm rests to be written out.   
+        # Assume that a <tie> element only lasts for one note.
+        # This might not be correct MusicXML interpretation, but works for
+        # most cases and fixes broken files, which have the end tag missing
+        if is_tied and not tie_started:
+            is_tied = False
+
+    ## force trailing mm rests to be written out.
     voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
-    
+
     ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
     ly_voice = group_repeats (ly_voice)
 
@@ -2404,16 +2434,16 @@ def musicxml_voice_to_lily_voice (voice):
         ## \key <pitch> barfs in drummode.
         ly_voice = [e for e in ly_voice
                     if not isinstance(e, musicexp.KeySignatureChange)]
-    
+
     seq_music.elements = ly_voice
     for k in lyrics.keys ():
         return_value.lyrics_dict[k] = musicexp.Lyrics ()
         return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
-    
-    
+
+
     if len (modes_found) > 1:
-       error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
-       
+       ly.warning (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
+
     if options.relative:
         v = musicexp.RelativeMusic ()
         v.element = seq_music
@@ -2426,31 +2456,31 @@ def musicxml_voice_to_lily_voice (voice):
         v.element = seq_music
         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
+        fbass_music.elements = group_repeats (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
+        cname_music.elements = group_repeats (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):
     digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
               'Six', 'Seven', 'Eight', 'Nine', 'Ten']
-    
+
     for digit in digits:
         d = digits.index (digit)
         id = re.sub ('%d' % d, digit, id)
@@ -2469,11 +2499,11 @@ def musicxml_unpitched_to_lily (mxl_unpitched):
     p = None
     step = mxl_unpitched.get_step ()
     if step:
-       p = musicexp.Pitch ()
-       p.step = musicxml_step_to_lily (step)
+        p = musicexp.Pitch ()
+        p.step = musicxml_step_to_lily (step)
     octave = mxl_unpitched.get_octave ()
     if octave and p:
-       p.octave = octave - 4
+        p.octave = octave - 4
     return p
 
 def musicxml_restdisplay_to_lily (mxl_rest):
@@ -2498,7 +2528,17 @@ def voices_in_part (part):
 
 def voices_in_part_in_parts (parts):
     """return a Part -> Name -> Voice dictionary"""
-    return dict([(p.id, voices_in_part (p)) for p in parts])
+    # don't crash if p doesn't have an id (that's invalid MusicXML,
+    # but such files are out in the wild!
+    dictionary = {}
+    for p in parts:
+        voices = voices_in_part (p)
+        if (hasattr (p, "id")):
+             dictionary[p.id] = voices
+        else:
+             # TODO: extract correct part id from other sources
+             dictionary[None] = voices
+    return dictionary;
 
 
 def get_all_voices (parts):
@@ -2510,7 +2550,7 @@ def get_all_voices (parts):
 
         part_ly_voices = {}
         for n, v in name_voice.items ():
-            progress (_ ("Converting to LilyPond expressions..."))
+            ly.progress (_ ("Converting to LilyPond expressions..."), True)
             # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
             part_ly_voices[n] = musicxml_voice_to_lily_voice (v)
 
@@ -2533,7 +2573,7 @@ If the given filename is -, musicxml2ly reads from the command line.
 
     p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
 +
-_ ("""Copyright (c) 2005--2009 by
+_ ("""Copyright (c) 2005--2014 by
     Han-Wen Nienhuys <hanwen@xs4all.nl>,
     Jan Nieuwenhuizen <janneke@gnu.org> and
     Reinhold Kainhofer <reinhold@kainhofer.com>
@@ -2550,8 +2590,9 @@ information.""") % 'lilypond')
                  help=_ ("show version number and exit"))
 
     p.add_option ('-v', '--verbose',
-                  action = "store_true",
-                  dest = 'verbose',
+                  action="callback",
+                  callback=ly.handle_loglevel_option,
+                  callback_args=("DEBUG",),
                   help = _ ("be verbose"))
 
     p.add_option ('', '--lxml',
@@ -2580,21 +2621,35 @@ information.""") % 'lilypond')
     p.add_option ('-l', '--language',
                   metavar = _ ("LANG"),
                   action = "store",
-                  help = _ ("use a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly"))
+                  help = _ ("use LANG for pitch names, e.g. 'deutsch' for note names in German"))
+
+    p.add_option ("--loglevel",
+                  help=_ ("Print log messages according to LOGLEVEL "
+                          "(NONE, ERROR, WARNING, PROGRESS (default), DEBUG)"),
+                  metavar=_ ("LOGLEVEL"),
+                  action='callback',
+                  callback=ly.handle_loglevel_option,
+                  type='string')
 
-    p.add_option ('--nd', '--no-articulation-directions', 
+    p.add_option ('--nd', '--no-articulation-directions',
                   action = "store_false",
                   default = True,
                   dest = "convert_directions",
                   help = _ ("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
 
-    p.add_option ('--nrp', '--no-rest-positions', 
+    p.add_option ('--nrp', '--no-rest-positions',
                   action = "store_false",
                   default = True,
                   dest = "convert_rest_positions",
                   help = _ ("do not convert exact vertical positions of rests"))
 
-    p.add_option ('--no-beaming', 
+    p.add_option ('--npl', '--no-page-layout',
+                  action = "store_false",
+                  default = True,
+                  dest = "convert_page_layout",
+                  help = _ ("do not convert the exact page layout and breaks"))
+
+    p.add_option ('--no-beaming',
                   action = "store_false",
                   default = True,
                   dest = "convert_beaming",
@@ -2607,6 +2662,13 @@ information.""") % 'lilypond')
                   type = 'string',
                   dest = 'output_name',
                   help = _ ("set output filename to FILE, stdout if -"))
+
+    p.add_option ('-m', '--midi',
+                  action = "store_true",
+                  default = False,
+                  dest = "midi",
+                  help = _("activate midi-block"))
+
     p.add_option_group ('',
                         description = (
             _ ("Report bugs via %s")
@@ -2616,19 +2678,19 @@ information.""") % 'lilypond')
 
 def music_xml_voice_name_to_lily_name (part_id, name):
     str = "Part%sVoice%s" % (part_id, name)
-    return musicxml_id_to_lily (str) 
+    return musicxml_id_to_lily (str)
 
 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) 
+    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) 
+    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) 
+    return musicxml_id_to_lily (str)
 
 def print_voice_definitions (printer, part_list, voices):
     for part in part_list:
@@ -2657,9 +2719,9 @@ def print_voice_definitions (printer, part_list, voices):
 
 
 def uniq_list (l):
-    return dict ([(elt,1) for elt in l]).keys ()
+    return dict ([(elt, 1) for elt in l]).keys ()
 
-# format the information about the staff in the form 
+# format the information about the staff in the form
 #     [staffid,
 #         [
 #            [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
@@ -2689,7 +2751,7 @@ def update_score_setup (score_structure, part_list, voices):
         part_id = part_definition.id
         nv_dict = voices.get (part_id)
         if not nv_dict:
-            error_message (_ ('unknown part in part-list: %s') % part_id)
+            ly.warning (_ ('unknown part in part-list: %s') % part_id)
             continue
 
         staves = reduce (lambda x,y: x+ y,
@@ -2702,12 +2764,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, voice.figured_bass, voice.chordnames) 
+                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, voice.figured_bass, voice.chordnames) 
+            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)
@@ -2719,7 +2781,7 @@ def update_layout_information ():
 
 def print_ly_preamble (printer, filename):
     printer.dump_version ()
-    printer.print_verbatim ('%% automatically converted from %s\n' % filename)
+    printer.print_verbatim ('%% automatically converted by musicxml2ly from %s\n' % filename)
 
 def print_ly_additional_definitions (printer, filename):
     if needed_additional_definitions:
@@ -2731,7 +2793,7 @@ def print_ly_additional_definitions (printer, filename):
         printer.newline ()
     printer.newline ()
 
-# Read in the tree from the given I/O object (either file or string) and 
+# Read in the tree from the given I/O object (either file or string) and
 # demarshall it using the classes from the musicxml.py file
 def read_xml (io_object, use_lxml):
     if use_lxml:
@@ -2751,10 +2813,20 @@ def read_musicxml (filename, compressed, use_lxml):
     raw_string = None
     if compressed:
         if filename == "-":
-             progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") )
-             z = zipfile.ZipFile (sys.stdin)
+             ly.progress (_ ("Input is compressed, extracting raw MusicXML data from stdin"), True)
+             # unfortunately, zipfile.ZipFile can't read directly from
+             # stdin, so copy everything from stdin to a temp file and read
+             # that. TemporaryFile() will remove the file when it is closed.
+             tmp = tempfile.TemporaryFile()
+             sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) # Make sys.stdin binary
+             bytes_read = sys.stdin.read (8192)
+             while bytes_read:
+                 for b in bytes_read:
+                     tmp.write(b)
+                 bytes_read = sys.stdin.read (8192)
+             z = zipfile.ZipFile (tmp, "r")
         else:
-            progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
+            ly.progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename, True)
             z = zipfile.ZipFile (filename, "r")
         container_xml = z.read ("META-INF/container.xml")
         if not container_xml:
@@ -2784,9 +2856,9 @@ def read_musicxml (filename, compressed, use_lxml):
 
 def convert (filename, options):
     if filename == "-":
-        progress (_ ("Reading MusicXML from Standard input ...") )
+        ly.progress (_ ("Reading MusicXML from Standard input ..."), True)
     else:
-        progress (_ ("Reading MusicXML from %s ...") % filename)
+        ly.progress (_ ("Reading MusicXML from %s ...") % filename, True)
 
     tree = read_musicxml (filename, options.compressed, options.use_lxml)
     score_information = extract_score_information (tree)
@@ -2807,7 +2879,7 @@ def convert (filename, options):
     update_layout_information ()
 
     if not options.output_name:
-        options.output_name = os.path.basename (filename) 
+        options.output_name = os.path.basename (filename)
         options.output_name = os.path.splitext (options.output_name)[0]
     elif re.match (".*\.ly", options.output_name):
         options.output_name = os.path.splitext (options.output_name)[0]
@@ -2819,9 +2891,9 @@ def convert (filename, options):
     else:
       output_ly_name = options.output_name + '.ly'
 
-    progress (_ ("Output to `%s'") % output_ly_name)
+    ly.progress (_ ("Output to `%s'") % output_ly_name, True)
     printer = musicexp.Output_printer()
-    #progress (_ ("Output to `%s'") % defs_ly_name)
+    #ly.progress (_ ("Output to `%s'") % defs_ly_name, True)
     if (options.output_name == "-"):
       printer.set_file (codecs.getwriter ("utf-8")(sys.stdout))
     else:
@@ -2830,12 +2902,12 @@ def convert (filename, options):
     print_ly_additional_definitions (printer, filename)
     if score_information:
         score_information.print_ly (printer)
-    if paper_information:
+    if paper_information and conversion_settings.convert_page_layout:
         paper_information.print_ly (printer)
     if layout_information:
         layout_information.print_ly (printer)
     print_voice_definitions (printer, part_list, voices)
-    
+
     printer.newline ()
     printer.dump ("% The score definition")
     printer.newline ()
@@ -2864,16 +2936,20 @@ def main ():
         opt_parser.print_usage()
         sys.exit (2)
 
+    if options.midi:
+        musicexp.set_create_midi (options.midi)
+
     if options.language:
         musicexp.set_pitch_language (options.language)
         needed_additional_definitions.append (options.language)
-        additional_definitions[options.language] = "\\include \"%s.ly\"\n" % options.language
+        additional_definitions[options.language] = "\\language \"%s\"\n" % options.language
     conversion_settings.ignore_beaming = not options.convert_beaming
+    conversion_settings.convert_page_layout = options.convert_page_layout
 
     # Allow the user to leave out the .xml or xml on the filename
     basefilename = args[0].decode('utf-8')
     if basefilename == "-": # Read from stdin
-        basefilename = "-"
+        filename = "-"
     else:
         filename = get_existing_filename_with_extension (basefilename, "xml")
         if not filename:
@@ -2885,7 +2961,7 @@ def main ():
     if filename and (filename == "-" or os.path.exists (filename)):
         voices = convert (filename, options)
     else:
-        progress (_ ("Unable to find input file %s") % basefilename)
+        ly.error (_ ("Unable to find input file %s") % basefilename)
 
 if __name__ == '__main__':
     main()