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:
def add_end (self, g):
self.end[getattr (g, 'number', "1")] = g
def print_ly (self, printer):
- error_message ("Unprocessed PartGroupInfo %s encountered" % self)
+ error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
def ly_expression (self):
- error_message ("Unprocessed PartGroupInfo %s encountered" % self)
+ error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self)
return ''
def staff_attributes_to_string_tunings (mxl_attr):
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
d.duration_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (rational_len.denominator (), -1)
d.factor = Rational (rational_len.numerator ())
if d.duration_log < 0:
- error_message ("Encountered rational duration with denominator %s, "
- "unable to convert to lilypond duration" %
+ error_message (_ ("Encountered rational duration with denominator %s, "
+ "unable to convert to lilypond duration") %
rational_len.denominator ())
# TODO: Test the above error message
return None
start_pitch.step = n
start_pitch.alteration = a
except KeyError:
- error_message ('unknown mode %s' % mode)
+ error_message (_ ("unknown mode %s, expecting 'major' or 'minor'") % mode)
fifth = musicexp.Pitch()
fifth.step = 4
self.direction = 0
self.event = None
def print_ly (self, printer):
- sys.stderr.write ("Encountered unprocessed marker %s\n" % self)
+ ly.stderr_write (_ ("Encountered unprocessed marker %s\n") % self)
pass
def ly_expression (self):
return ""
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,
+ 'slide' : musicexp.GlissandoEvent,
'slur' : musicexp.SlurEvent,
'wavy-line' : musicexp.TrillSpanEvent,
'wedge' : musicexp.HairpinEvent
if func:
ev = func()
else:
- error_message ('unknown span event %s' % mxl_event)
+ error_message (_ ('unknown span event %s') % mxl_event)
type = mxl_event.get_type ()
if span_direction != None:
ev.span_direction = span_direction
else:
- error_message ('unknown span type %s for %s' % (type, name))
+ error_message (_ ('unknown span type %s for %s') % (type, name))
ev.set_span_type (type)
ev.line_type = getattr (mxl_event, 'line-type', 'solid')
return ev
def musicxml_direction_to_indicator (direction):
- return { "above": 1, "upright": 1, "up":1, "below": -1, "downright": -1, "down": -1, "inverted": -1 }.get (direction, 0)
+ return { "above": 1, "upright": 1, "up": 1, "below": -1, "downright": -1, "down": -1, "inverted": -1 }.get (direction, 0)
def musicxml_fermata_to_lily_event (mxl_event):
ev = musicexp.ArticulationEvent ()
ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
return ev
+def musicxml_nonarpeggiate_to_lily_event (mxl_event):
+ ev = musicexp.ArpeggioEvent ()
+ ev.non_arpeggiate = True
+ ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None))
+ return ev
+
def musicxml_tremolo_to_lily_event (mxl_event):
ev = musicexp.TremoloEvent ()
txt = mxl_event.get_text ()
dynamicstext = dynamicsname
dynamicsname = string.replace (dynamicsname, "-", "")
additional_definitions[dynamicsname] = dynamicsname + \
- "=#(make-dynamic-script \"" + dynamicstext + "\")"
+ " = #(make-dynamic-script \"" + dynamicstext + "\")"
needed_additional_definitions.append (dynamicsname)
event = musicexp.DynamicsEvent ()
event.type = dynamicsname
return event
+# convert accordion-registration to lilypond.
+# Since lilypond does not have any built-in commands, we need to create
+# the markup commands manually and define our own variables.
+# Idea was taken from: http://lsr.dsi.unimi.it/LSR/Item?id=194
+def musicxml_accordion_to_markup (mxl_event):
+ commandname = "accReg"
+ command = ""
+
+ high = mxl_event.get_maybe_exist_named_child ('accordion-high')
+ if high:
+ commandname += "H"
+ command += """\\combine
+ \\raise #2.5 \\musicglyph #\"accordion.accDot\"
+ """
+ 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
+ # MusicXML spec is quiet about this case...
+ txt = 1
+ try:
+ txt = string.atoi (middle.get_text ())
+ except ValueError:
+ pass
+ if txt == 3:
+ commandname += "MMM"
+ command += """\\combine
+ \\raise #1.5 \\musicglyph #\"accordion.accDot\"
+ \\combine
+ \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.accDot\"
+ \\combine
+ \\raise #1.5 \\translate #(cons -1 0) \\musicglyph #\"accordion.accDot\"
+ """
+ elif txt == 2:
+ commandname += "MM"
+ command += """\\combine
+ \\raise #1.5 \\translate #(cons 0.5 0) \\musicglyph #\"accordion.accDot\"
+ \\combine
+ \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.accDot\"
+ """
+ elif not txt <= 0:
+ commandname += "M"
+ command += """\\combine
+ \\raise #1.5 \\musicglyph #\"accordion.accDot\"
+ """
+ low = mxl_event.get_maybe_exist_named_child ('accordion-low')
+ if low:
+ commandname += "L"
+ command += """\\combine
+ \\raise #0.5 \musicglyph #\"accordion.accDot\"
+ """
+
+ command += "\musicglyph #\"accordion.accDiscant\""
+ command = "\\markup { \\normalsize %s }" % command
+ # Define the newly built command \accReg[H][MMM][L]
+ additional_definitions[commandname] = "%s = %s" % (commandname, command)
+ needed_additional_definitions.append (commandname)
+ return "\\%s" % commandname
+
+def musicxml_accordion_to_ly (mxl_event):
+ txt = musicxml_accordion_to_markup (mxl_event)
+ if txt:
+ ev = musicexp.MarkEvent (txt)
+ return ev
+ return
+
def musicxml_rehearsal_to_ly_mark (mxl_event):
text = mxl_event.get_text ()
ev = musicexp.MarkEvent ("\\markup { %s }" % text)
return ev
-
# translate directions into Events, possible values:
# -) string (MarkEvent with that command)
# -) function (function(mxl_event) needs to return a full Event-derived object
# -) (class, name) (like string, only that a different class than MarkEvent is used)
directions_dict = {
-# 'accordion-registration' : musicxml_accordion_to_ly,
+ 'accordion-registration' : musicxml_accordion_to_ly,
'coda' : (musicexp.MusicGlyphMarkEvent, "coda"),
# 'damp' : ???
# 'damp-all' : ???
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)
diff = moment - current_end
if diff < Rational (0):
- error_message ('Negative skip %s' % diff)
+ error_message (_ ('Negative skip %s') % diff)
diff = Rational (0)
if diff > Rational (0) and not (self.ignore_skips and moment == 0):
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):
continue
if not n.__class__.__name__ == 'Note':
- error_message ('not a Note or Attributes? %s' % n)
+ error_message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline'))
continue
rest = n.get_maybe_exist_typed_child (musicxml.Rest)
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 s.get_type () in ('start','stop')]
if slurs:
if len (slurs) > 1:
- error_message ('more than 1 slur?')
+ error_message (_ ('cannot have two simultaneous slurs'))
# record the slur status for the next note in the loop
if not grace:
if slurs[0].get_type () == 'start':
if ev:
ev_chord.append (ev)
+ arpeggiate = notations.get_named_children ('non-arpeggiate')
+ for a in arpeggiate:
+ ev = musicxml_nonarpeggiate_to_lily_event (a)
+ if ev:
+ ev_chord.append (ev)
+
glissandos = notations.get_named_children ('glissando')
+ glissandos += notations.get_named_children ('slide')
for a in glissandos:
ev = musicxml_spanner_to_lily_event (a)
if ev:
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))
if len (modes_found) > 1:
- error_message ('Too many modes found %s' % modes_found.keys ())
+ error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ())
if options.relative:
v = musicexp.RelativeMusic ()
part_ly_voices = {}
for n, v in name_voice.items ():
- progress ("Converting to LilyPond expressions...")
+ progress (_ ("Converting to LilyPond expressions..."))
# musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
part_ly_voices[n] = musicxml_voice_to_lily_voice (v)
def option_parser ():
- p = ly.get_option_parser(usage=_ ("musicxml2ly [options] FILE.xml"),
- version=('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
+ p = ly.get_option_parser (usage = _ ("musicxml2ly [options] FILE.xml"),
+ 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
under certain conditions. Invoke as `%s --warranty' for more
information.""") % 'lilypond'
+ """
-Copyright (c) 2005--2007 by
+Copyright (c) 2005--2008 by
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',
- help=_ ("be verbose"))
+ action = "store_true",
+ dest = 'verbose',
+ help = _ ("be verbose"))
p.add_option ('', '--lxml',
- action="store_true",
- default=False,
- dest="use_lxml",
- help=_ ("Use lxml.etree; uses less memory and cpu time."))
+ action = "store_true",
+ default = False,
+ dest = "use_lxml",
+ 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."))
+ help = _ ("convert pitches in relative mode (default)"))
+
+ p.add_option ('-a', '--absolute',
+ action = "store_false",
+ dest = "relative",
+ help = _ ("convert pitches in absolute mode"))
p.add_option ('-l', '--language',
+ metavar = _ ("LANG"),
action = "store",
- help = _ ("Use a different language file, 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 ('--no-articulation-directions', '--nd',
+ p.add_option ('--nd', '--no-articulation-directions',
action = "store_false",
default = True,
dest = "convert_directions",
- help = _ ("Do not convert directions (^, _ or -) for articulations."))
+ 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"),
- action="store",
- default=None,
- type='string',
- dest='output_name',
- help=_ ("set output filename to FILE"))
- p.add_option_group ('bugs',
- description=(_ ("Report bugs via")
+ metavar = _ ("FILE"),
+ action = "store",
+ default = None,
+ type = 'string',
+ dest = 'output_name',
+ help = _ ("set output filename to FILE"))
+ 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
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)
+ error_message (_ ('unknown part in part-list: %s') % part_id)
continue
staves = reduce (lambda x,y: x+ y,
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 read_musicxml (filename, compressed, use_lxml):
raw_string = None
if compressed:
- progress ("Input file %s is compressed, extracting raw MusicXML data" % filename)
+ progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename)
z = zipfile.ZipFile (filename, "r")
container_xml = z.read ("META-INF/container.xml")
if not container_xml:
def convert (filename, options):
- progress ("Reading MusicXML from %s ..." % filename)
-
+ 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)
driver_ly_name = options.output_name + '.ly'
printer = musicexp.Output_printer()
- progress ("Output to `%s'" % defs_ly_name)
+ progress (_ ("Output to `%s'") % defs_ly_name)
printer.set_file (codecs.open (defs_ly_name, 'wb', encoding='utf-8'))
print_ly_preamble (printer, 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)
printer.close ()
- progress ("Output to `%s'" % driver_ly_name)
+ progress (_ ("Output to `%s'") % driver_ly_name)
printer = musicexp.Output_printer()
printer.set_file (codecs.open (driver_ly_name, 'wb', encoding='utf-8'))
print_ly_preamble (printer, filename)
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")
if filename and os.path.exists (filename):
voices = convert (filename, options)
else:
- progress ("Unable to find input file %s" % args[0])
+ progress (_ ("Unable to find input file %s") % args[0])
if __name__ == '__main__':
main()