]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/musicxml2ly.py
Update makelsr.py to work with local copy
[lilypond.git] / scripts / musicxml2ly.py
index 6f70baea076de06f18ecc608fc496d16c084869a..69125dc9db6cdccbb52d47c19296c6191974fa4e 100644 (file)
@@ -7,6 +7,7 @@ import os
 import string
 import codecs
 import zipfile
+import tempfile
 import StringIO
 
 """
@@ -34,14 +35,6 @@ conversion_settings = Conversion_Settings ()
 # 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 = {
 
@@ -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.baseMoment = $beat
-
-% TODO: Implement beatGrouping and auto-beam-settings!!!
-#} ))
-"""
 }
 
 def round_to_two_digits (val):
@@ -273,16 +165,26 @@ def extract_score_information (tree):
         if value:
             header.set_field (field, musicxml.escape_ly_output_string (value))
 
-    movement_title = tree.get_maybe_exist_named_child ('movement-title')
-    if movement_title:
-        set_if_exists ('title', movement_title.get_text ())
     work = tree.get_maybe_exist_named_child ('work')
     if work:
-        # Overwrite the title from movement-title with work->title
-        set_if_exists ('title', work.get_work_title ())
         set_if_exists ('worknumber', work.get_work_number ())
         set_if_exists ('opus', work.get_opus ())
 
+    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 ())
@@ -291,12 +193,14 @@ def extract_score_information (tree):
         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
@@ -321,7 +225,10 @@ def extract_score_information (tree):
             app_description = ignore_beaming_software.get (s, False);
             if app_description:
                 conversion_settings.ignore_beaming = True
-                progress (_ ("Encountered file created by %s, containing wrong beaming information. All beaming information in the MusicXML file will be ignored") % app_description)
+                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
@@ -337,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:
@@ -351,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()
@@ -605,9 +519,9 @@ def rational_to_lily_duration (rational_len):
         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
 
@@ -832,8 +746,6 @@ def musicxml_time_to_lily (attributes):
         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'):
@@ -854,7 +766,7 @@ def musicxml_time_to_lily (attributes):
 def musicxml_key_to_lily (attributes):
     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()
@@ -881,7 +793,7 @@ def musicxml_key_to_lily (attributes):
             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()
@@ -896,7 +808,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):
@@ -1014,7 +931,7 @@ class Marker (musicexp.Music):
         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 ""
@@ -1108,7 +1025,7 @@ def musicxml_spanner_to_lily_event (mxl_event):
     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 ()
@@ -1118,7 +1035,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')
@@ -1538,12 +1455,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:
@@ -1745,7 +1662,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):
@@ -1873,11 +1790,11 @@ def musicxml_note_to_lily_main_event (n):
             # 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
@@ -2046,8 +1963,8 @@ class LilyPondVoiceBuilder:
         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):
@@ -2116,12 +2033,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:
@@ -2529,7 +2440,7 @@ def musicxml_voice_to_lily_voice (voice):
 
 
     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 ()
@@ -2586,11 +2497,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):
@@ -2637,7 +2548,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)
 
@@ -2660,7 +2571,7 @@ If the given filename is -, musicxml2ly reads from the command line.
 
     p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
 +
-_ ("""Copyright (c) 2005--2010 by
+_ ("""Copyright (c) 2005--2012 by
     Han-Wen Nienhuys <hanwen@xs4all.nl>,
     Jan Nieuwenhuizen <janneke@gnu.org> and
     Reinhold Kainhofer <reinhold@kainhofer.com>
@@ -2677,8 +2588,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',
@@ -2709,6 +2621,14 @@ information.""") % 'lilypond')
                   action = "store",
                   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',
                   action = "store_false",
                   default = True,
@@ -2740,6 +2660,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 = _("add midi-block to .ly file"))
+
     p.add_option_group ('',
                         description = (
             _ ("Report bugs via %s")
@@ -2822,7 +2749,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,
@@ -2852,7 +2779,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:
@@ -2884,10 +2811,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:
@@ -2917,9 +2854,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)
@@ -2952,9 +2889,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:
@@ -2997,6 +2934,9 @@ 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)
@@ -3019,7 +2959,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()