import codecs
import zipfile
import StringIO
-from gettext import gettext as _
"""
@relocate-preamble@
"""
import lilylib as ly
+_ = ly._
import musicxml
import musicexp
# Store command-line options in a global variable, so we can access them everythwere
options = None
+class Conversion_Settings:
+ def __init__(self):
+ self.ignore_beaming = False
+
+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
+# this layout and add the corresponding settings
+layout_information = musicexp.Layout ()
+
def progress (str):
ly.stderr_write (str + '\n')
sys.stderr.flush ()
def round_to_two_digits (val):
return round (val * 100) / 100
-def extract_layout_information (tree):
+def extract_paper_information (tree):
paper = musicexp.Paper ()
defaults = tree.get_maybe_exist_named_child ('defaults')
if not defaults:
set_if_exists ('encoder', ids.get_encoding_person ())
set_if_exists ('encodingdescription', ids.get_encoding_description ())
+ # Finally, apply the required compatibility modes
+ # 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"))
+ # TODO: Check for other unsupported features
+
return header
class PartGroupInfo:
clef_sign = {"percussion": "percussion", "TAB": "tab"}.get (sign.get_text (), None)
lines = 5
- details = attributes.get_maybe_exist_named_child ('staff-details')
- if details:
- staff_lines = details.get_maybe_exist_named_child ('staff-lines')
+ details = attributes.get_named_children ('staff-details')
+ for d in details:
+ staff_lines = d.get_maybe_exist_named_child ('staff-lines')
if staff_lines:
lines = string.atoi (staff_lines.get_text ())
return staff
-def extract_score_layout (part_list, staffinfo):
- layout = musicexp.StaffGroup (None)
+def extract_score_structure (part_list, staffinfo):
+ structure = musicexp.StaffGroup (None)
if not part_list:
- return layout
+ return structure
def read_score_part (el):
if not isinstance (el, musicxml.Score_part):
if len (staves) == 1:
return staves[0]
for i in staves:
- layout.append_staff (i)
- return layout
+ structure.append_staff (i)
+ return structure
return retval.values ()
+# Brackets need a special engraver added the Staff context!
+def musicxml_bracket_to_ly ():
+ layout_information.set_context_item ('Staff', '\consists "Horizontal_bracket_engraver" % for \\startGroup and \\stopGroup brackets')
+ return musicexp.BracketSpannerEvent ()
+
spanner_event_dict = {
'beam' : musicexp.BeamEvent,
'dashes' : musicexp.TextSpannerEvent,
- 'bracket' : musicexp.BracketSpannerEvent,
+ 'bracket' : musicxml_bracket_to_ly,
'glissando' : musicexp.GlissandoEvent,
'octave-shift' : musicexp.OctaveShiftEvent,
'pedal' : musicexp.PedalEvent,
if drum_type:
event.drum_type = drum_type
else:
- n.message ("drum %s type unknown, please add to instrument_drumtype_dict" % n.instrument_name)
+ n.message (_ ("drum %s type unknown, please add to instrument_drumtype_dict") % n.instrument_name)
event.drum_type = 'acousticsnare'
else:
- n.message ("cannot find suitable event")
+ n.message (_ ("cannot find suitable event"))
if event:
event.duration = musicxml_duration_to_lily (n)
else:
duration_factor = Rational (diff.numerator ())
else:
+ # for skips of a whole or more, simply use s1*factor
duration_log = 0
duration_factor = diff
skip.duration.duration_log = duration_log
inside_slur = False
is_tied = False
is_chord = False
+ is_beamed = False
ignore_lyrics = False
current_staff = None
voice_builder.jumpto (n._when)
except NegativeSkip, neg:
voice_builder.correct_negative_skip (n._when)
- n.message ("Negative skip? from %s to %s, diff %s" % (neg.here, neg.dest, neg.dest - neg.here))
+ n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here))
if isinstance (n, musicxml.Attributes):
if n.is_first () and n._measure_position == Rational (0):
main_event = musicxml_note_to_lily_main_event (n)
if main_event and not first_pitch:
first_pitch = main_event.pitch
- ignore_lyrics = inside_slur or is_tied or is_chord
+ # ignore lyrics for notes inside a slur, tie, chord or beam
+ ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed
if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type:
modes_found['drummode'] = True
if ev:
ev_chord.append (ev)
- # Extract the lyrics
- if not rest and not ignore_lyrics:
- note_lyrics_processed = []
- note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
- for l in note_lyrics_elements:
- if l.get_number () < 0:
- for k in lyrics.keys ():
- lyrics[k].append (l.lyric_to_text ())
- note_lyrics_processed.append (k)
- else:
- lyrics[l.number].append(l.lyric_to_text ())
- note_lyrics_processed.append (l.number)
- for lnr in lyrics.keys ():
- if not lnr in note_lyrics_processed:
- lyrics[lnr].append ("\skip4")
-
mxl_beams = [b for b in n.get_named_children ('beam')
if (b.get_type () in ('begin', 'end')
and b.is_primary ())]
- if mxl_beams:
+ if mxl_beams and not conversion_settings.ignore_beaming:
beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
if beam_ev:
ev_chord.append (beam_ev)
+ if beam_ev.span_direction == -1: # beam and thus melisma starts here
+ is_beamed = True
+ elif beam_ev.span_direction == 1: # beam and thus melisma ends here
+ is_beamed = False
if tuplet_event:
mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
tuplet_events.append ((ev_chord, tuplet_event, frac))
+ # Extract the lyrics
+ if not rest and not ignore_lyrics:
+ note_lyrics_processed = []
+ note_lyrics_elements = n.get_typed_children (musicxml.Lyric)
+ for l in note_lyrics_elements:
+ if l.get_number () < 0:
+ for k in lyrics.keys ():
+ lyrics[k].append (l.lyric_to_text ())
+ note_lyrics_processed.append (k)
+ else:
+ lyrics[l.number].append(l.lyric_to_text ())
+ note_lyrics_processed.append (l.number)
+ for lnr in lyrics.keys ():
+ if not lnr in note_lyrics_processed:
+ lyrics[lnr].append ("\skip4")
+
## force trailing mm rests to be written out.
voice_builder.add_music (musicexp.ChordEvent (), Rational (0))
def option_parser ():
p = ly.get_option_parser (usage = _ ("musicxml2ly [options] FILE.xml"),
- version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
+ description = _ ("Convert %s to LilyPond input.") % 'MusicXML' + "\n",
+ add_help_option=False)
+
+ p.add_option("-h", "--help",
+ action="help",
+ help=_ ("show this help and exit"))
+
+ p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
+
_ ("""This program is free software. It is covered by the GNU General Public
License and you are welcome to change it and/or distribute copies of it
Han-Wen Nienhuys <hanwen@xs4all.nl>,
Jan Nieuwenhuizen <janneke@gnu.org> and
Reinhold Kainhofer <reinhold@kainhofer.com>
-"""),
- description = _ ("Convert %s to LilyPond input.") % 'MusicXML' + "\n")
+""")
+ p.add_option("--version",
+ action="version",
+ help=_ ("show version number and exit"))
+
p.add_option ('-v', '--verbose',
action = "store_true",
dest = 'verbose',
action = "store_true",
default = False,
dest = "use_lxml",
- help = _ ("Use lxml.etree; uses less memory and cpu time."))
+ help = _ ("use lxml.etree; uses less memory and cpu time"))
p.add_option ('-z', '--compressed',
action = "store_true",
dest = 'compressed',
default = False,
- help = _ ("Input file is a zip-compressed MusicXML file."))
+ help = _ ("input file is a zip-compressed MusicXML file"))
p.add_option ('-r', '--relative',
action = "store_true",
default = True,
dest = "relative",
- help = _ ("Convert pitches in relative mode. (Default)"))
+ help = _ ("convert pitches in relative mode (default)"))
p.add_option ('-a', '--absolute',
action = "store_false",
dest = "relative",
- help = _ ("Convert pitches in absolute mode."))
+ help = _ ("convert pitches in absolute mode"))
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 a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly"))
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."))
+ help = _ ("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
+
+ p.add_option ('--no-beaming',
+ action = "store_false",
+ default = True,
+ dest = "convert_beaming",
+ help = _ ("do not convert beaming information, use lilypond's automatic beaming instead"))
p.add_option ('-o', '--output',
metavar = _ ("FILE"),
type = 'string',
dest = 'output_name',
help = _ ("set output filename to FILE"))
- p.add_option_group ( _ ('Bugs'),
- description = ( _ ("Report bugs via")
+ p.add_option_group (ly.display_encode (_ ('Bugs')),
+ description = (_ ("Report bugs via")
+ ''' http://post.gmane.org/post.php'''
'''?group=gmane.comp.gnu.lilypond.bugs\n'''))
return p
staves_info.append (format_staff_info (part_id, None, thisstaff_raw_voices))
score_structure.set_part_information (part_id, staves_info)
+# Set global values in the \layout block, like auto-beaming etc.
+def update_layout_information ():
+ if not conversion_settings.ignore_beaming and layout_information:
+ layout_information.set_context_item ('Score', 'autoBeaming = ##f')
+
def print_ly_preamble (printer, filename):
printer.dump_version ()
printer.print_verbatim ('%% automatically converted from %s\n' % filename)
def convert (filename, options):
progress (_ ("Reading MusicXML from %s ...") % filename)
-
+
tree = read_musicxml (filename, options.compressed, options.use_lxml)
+ score_information = extract_score_information (tree)
+ paper_information = extract_paper_information (tree)
+
parts = tree.get_typed_children (musicxml.Part)
(voices, staff_info) = get_all_voices (parts)
score_structure = None
mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
if mxl_pl:
- score_structure = extract_score_layout (mxl_pl, staff_info)
+ score_structure = extract_score_structure (mxl_pl, staff_info)
part_list = mxl_pl.get_named_children ("score-part")
# score information is contained in the <work>, <identification> or <movement-title> tags
- score_information = extract_score_information (tree)
- layout_information = extract_layout_information (tree)
update_score_setup (score_structure, part_list, voices)
+ # After the conversion, update the list of settings for the \layout block
+ update_layout_information ()
if not options.output_name:
options.output_name = os.path.basename (filename)
print_ly_additional_definitions (printer, filename)
if score_information:
score_information.print_ly (printer)
+ if paper_information:
+ paper_information.print_ly (printer)
if layout_information:
layout_information.print_ly (printer)
print_voice_definitions (printer, part_list, voices)
musicexp.set_pitch_language (options.language)
needed_additional_definitions.append (options.language)
additional_definitions[options.language] = "\\include \"%s.ly\"\n" % options.language
+ conversion_settings.ignore_beaming = not options.convert_beaming
# Allow the user to leave out the .xml or xml on the filename
filename = get_existing_filename_with_extension (args[0], "xml")