X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmusicxml2ly.py;h=2b630029b6223bbb061580201fa54f3874d66c31;hb=d959f8d548dc073c162c4599cfef2a596f9b66db;hp=d9dfaf3be64171ae4de27938ac1f56be4b22434c;hpb=36b9825a04fd050c1de107b5d68db3c13916fe33;p=lilypond.git diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index d9dfaf3be6..2b630029b6 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -7,6 +7,7 @@ import os import string import codecs import zipfile +import tempfile import StringIO """ @@ -30,7 +31,7 @@ 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 () @@ -45,12 +46,12 @@ def error_message (str): 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 +59,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 +68,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): @@ -282,7 +182,7 @@ def extract_score_information (tree): set_if_exists ('title', work.get_work_title ()) set_if_exists ('worknumber', work.get_work_number ()) set_if_exists ('opus', work.get_opus ()) - + identifications = tree.get_named_children ('identification') for ids in identifications: set_if_exists ('copyright', ids.get_rights ()) @@ -290,17 +190,17 @@ 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 ('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 () @@ -342,6 +242,13 @@ class PartGroupInfo: error_message (_ ("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 +258,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() @@ -401,6 +308,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 () @@ -412,8 +321,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 @@ -422,7 +334,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 @@ -439,9 +351,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): @@ -559,7 +482,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 () @@ -580,10 +503,11 @@ 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 ()) @@ -702,7 +626,7 @@ def group_repeats (music_list): return music_list -# Extract the settings for tuplets from the and the +# Extract the settings for tuplets from the and the # elements of the note: def musicxml_tuplet_to_lily (tuplet_elt, time_modification): tsm = musicexp.TimeScaledMusic () @@ -760,7 +684,7 @@ def group_tuplets (music_list, events): MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects. """ - + indices = [] brackets = {} @@ -808,15 +732,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'): @@ -829,19 +751,19 @@ 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!")) 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 @@ -879,7 +801,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): @@ -894,10 +821,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), + (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') @@ -914,6 +841,22 @@ 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 = [] @@ -922,37 +865,57 @@ def musicxml_attributes_to_lily (attrs): '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 # # elts = [] - if (hasattr (el, "new-system")): + 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")): + 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 @@ -1049,7 +1012,7 @@ 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: @@ -1251,7 +1214,7 @@ 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": @@ -1338,7 +1301,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 @@ -1356,11 +1319,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: @@ -1370,32 +1333,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) @@ -1455,7 +1418,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'): @@ -1521,7 +1484,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 () @@ -1546,6 +1509,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 @@ -1710,15 +1674,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: @@ -1731,7 +1695,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 @@ -1744,12 +1708,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: @@ -1761,7 +1725,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 @@ -1813,16 +1777,18 @@ 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 + # let's not force accs everywhere. + 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 @@ -1934,7 +1900,7 @@ 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, relevant = True): assert isinstance (music, musicexp.Music) if self.pending_multibar > Rational (0): @@ -1944,7 +1910,7 @@ class LilyPondVoiceBuilder: 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: @@ -1962,8 +1928,8 @@ class LilyPondVoiceBuilder: # 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 + 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 @@ -1988,9 +1954,9 @@ class LilyPondVoiceBuilder: 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)') % + error_message (_ ('Negative skip %s (from position %s to %s)') % (diff, current_end, moment)) diff = Rational (0) @@ -1999,7 +1965,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: @@ -2042,7 +2008,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 @@ -2060,12 +2026,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: @@ -2078,7 +2038,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 @@ -2090,7 +2050,7 @@ def musicxml_voice_to_lily_voice (voice): ignore_lyrics = False current_staff = None - + pending_figured_bass = [] pending_chordnames = [] @@ -2220,7 +2180,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 @@ -2231,7 +2191,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) @@ -2244,7 +2204,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): @@ -2275,7 +2235,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: @@ -2292,7 +2252,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) @@ -2309,9 +2269,9 @@ def musicxml_voice_to_lily_voice (voice): span_events = [] # The 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(): @@ -2358,7 +2318,7 @@ def musicxml_voice_to_lily_voice (voice): 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') @@ -2389,7 +2349,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 | @@ -2399,7 +2359,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') @@ -2421,7 +2381,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: @@ -2455,7 +2415,7 @@ def musicxml_voice_to_lily_voice (voice): ## 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) @@ -2465,16 +2425,16 @@ def musicxml_voice_to_lily_voice (voice): ## \key 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 ()) - + if options.relative: v = musicexp.RelativeMusic () v.element = seq_music @@ -2487,7 +2447,7 @@ 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 () @@ -2496,7 +2456,7 @@ def musicxml_voice_to_lily_voice (voice): 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 () @@ -2505,13 +2465,13 @@ def musicxml_voice_to_lily_voice (voice): 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) @@ -2530,11 +2490,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): @@ -2604,7 +2564,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--2011 by Han-Wen Nienhuys , Jan Nieuwenhuizen and Reinhold Kainhofer @@ -2651,21 +2611,27 @@ 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 ('--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", @@ -2687,19 +2653,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: @@ -2730,7 +2696,7 @@ def print_voice_definitions (printer, part_list, voices): def uniq_list (l): 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], @@ -2773,12 +2739,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) @@ -2802,7 +2768,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: @@ -2823,7 +2789,17 @@ def read_musicxml (filename, compressed, use_lxml): if compressed: if filename == "-": progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") ) - z = zipfile.ZipFile (sys.stdin) + # 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) z = zipfile.ZipFile (filename, "r") @@ -2878,7 +2854,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] @@ -2901,12 +2877,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 () @@ -2938,13 +2914,14 @@ def main (): 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: