From bc7dc27063eb6062ad56ec4cb5bf5d14f8550aa4 Mon Sep 17 00:00:00 2001 From: Reinhold Kainhofer Date: Thu, 13 Sep 2007 23:09:58 +0200 Subject: [PATCH] MusicXML: Cleanup of span start/end and direction, coding style Follow Han-Wen's tips: -) Split the type of spanners into direction (start/stop) and type (crescendo/decrescendo, up/down octave-shifts, ...) -) Get rid of some ifs and use dicts instead -) Get rid of unnecessary empty cases in dicts -) Treat hairpins like all other spanners -) Invert logic of quoting of lyrics and header fields to always quote unless the text consists entirely of letters. -) Implement all durations from MusicXML -) If an ouput filename is given, the include needs to use only the basename of the file, not the whole path -) Change logic of adding .xml to the input file: First try the supplied filename, then with .xml appended and only then with xml (no dot) appended. -) Use progress (..) for the error message when input file does not exist. Signed-off-by: Reinhold Kainhofer --- make/musicxml-vars.make | 6 --- python/musicexp.py | 54 ++++++++++++-------------- python/musicxml.py | 41 ++++++++++++-------- scripts/musicxml2ly.py | 86 +++++++++++++++++------------------------ 4 files changed, 85 insertions(+), 102 deletions(-) diff --git a/make/musicxml-vars.make b/make/musicxml-vars.make index ecfd1a07cc..ccf8c74ec2 100644 --- a/make/musicxml-vars.make +++ b/make/musicxml-vars.make @@ -1,11 +1,5 @@ # rules for directories with MusicXML files. -# empty - -# UGH UGH -include $(make-dir)/lilypond-vars.make - -# huh ? these are for documentation?! MUSICXML_FILES := $(call src-wildcard,*.xml) # LY_FILES=$(addprefix $(outdir)/, $(addsuffix .ly, $(MUSICXML_FILE))) # LY_FILES = $(MUSICXML_FILES:%.xml=$(outdir)/%.ly) diff --git a/python/musicexp.py b/python/musicexp.py index 5c3b91a804..f4efdd54db 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -492,14 +492,17 @@ class Event(Music): class SpanEvent (Event): def __init__(self): Event.__init__ (self) - self.span_direction = 0 - self.line_type = 0 - self.size = 0 + self.span_direction = 0 # start/stop + self.line_type = 'solid' + self.span_type = 0 # e.g. cres/decrescendo, ottava up/down + self.size = 0 # size of e.g. ocrave shift def wait_for_note (self): return True def get_properties(self): return "'span-direction %d" % self.span_direction - + def set_span_type (self, type): + self.span_type = type + class SlurEvent (SpanEvent): def ly_expression (self): before = '' @@ -507,58 +510,49 @@ class SlurEvent (SpanEvent): # TODO: setting dashed/dotted line style does not work, because that # command needs to be written before the note, not when the # event is observed after the note! - #if self.line_type == 1: - #before = '\\slurDotted' - #elif self.line_type == 2: - #before = '\\slurDashed' + #before = {'dotted': '\\slurDotted', + # 'dashed' : '\\slurDashed'}.get (self.line_type, '') #if before: #after = '\\slurSolid' return {-1: before + '(' + after, - 0:'', 1:')'}.get (self.span_direction, '') class BeamEvent (SpanEvent): def ly_expression (self): return {-1: '[', - 0:'', 1:']'}.get (self.span_direction, '') class PedalEvent (SpanEvent): def ly_expression (self): return {-1: '\\sustainDown', - 0:'', 1:'\\sustainUp'}.get (self.span_direction, '') # type==-1 means octave up, type==-2 means octave down class OctaveShiftEvent (SpanEvent): def wait_for_note (self): return False; + def set_span_type (self, type): + self.span_type = {'up': 1, 'down': -1}.get (type, 0) def ly_octave_shift_indicator (self): - if self.size == 8: - value = 1 - elif self.size == 15: - value = 2 - else: - value = 0 - # -2 means up - if self.span_direction == -2: - value = -value + # convert 8/15 to lilypond indicators (+-1/+-2) + value = {8: 1, 15: 2}.get (self.size, 0) + # negative values go up! + value *= -1*self.span_type return value def ly_expression (self): dir = self.ly_octave_shift_indicator () value = '' if dir: value = '#(set-octavation %s)' % dir - return {-2: value, - -1: value, - 0: '', + return { + -1: value, 1: '#(set-octavation 0)'}.get (self.span_direction, '') class TrillSpanEvent (SpanEvent): def ly_expression (self): return {-1: '\\startTrillSpan', - 0:'', + 0: '', # no need to write out anything for type='continue' 1:'\\stopTrillSpan'}.get (self.span_direction, '') class GlissandoEvent (SpanEvent): @@ -566,11 +560,10 @@ class GlissandoEvent (SpanEvent): style = '' # TODO: wavy-line glissandos don't work, becasue the style has to be # set before the note, at the \glissando it's already too late! - #if self.line_type == 3: # wavy-line: + #if self.line_type == 'wavy': #style = "\once\override Glissando #'style = #'zigzag" # In lilypond, glissando is NOT a spanner, unlike MusicXML. return {-1: style + '\\glissando', - 0:'', 1:''}.get (self.span_direction, '') class ArpeggioEvent(Event): @@ -586,10 +579,13 @@ class TieEvent(Event): class HairpinEvent (SpanEvent): - def __init__ (self, type): - self.type = type + def set_span_type (self, type): + self.span_type = {'crescendo' : 1, 'decrescendo' : -1, 'diminuendo' : -1 }.get (type, 0) def hairpin_to_ly (self): - return { 0: '\!', 1: '\<', -1: '\>' }.get (self.type, '') + if self.span_direction == 1: + return '\!' + else: + return {1: '\<', -1: '\>'}.get (self.span_type, '') def ly_expression (self): return self.hairpin_to_ly () diff --git a/python/musicxml.py b/python/musicxml.py index 543e47786b..6234459187 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -5,10 +5,9 @@ import re def escape_ly_output_string (input_string): return_string = input_string - needs_quotes = re.search ("[-0-9\" ,._()]", return_string); - return_string = string.replace (return_string, "\"", "\\\"") + needs_quotes = not re.match ("^[a-zA-ZäöüÜÄÖßñ]*$", return_string); if needs_quotes: - return_string = "\"" + return_string + "\"" + return_string = "\"" + string.replace (return_string, "\"", "\\\"") + "\"" return return_string @@ -271,26 +270,30 @@ class Note (Measure_element): self.instrument_name = '' def get_duration_log (self): - ch = self.get_maybe_exist_typed_child (get_class (u'type')) + ch = self.get_maybe_exist_typed_child (get_class (u'type')) - if ch: - log = ch.get_text ().strip() - return {'eighth': 3, + if ch: + log = ch.get_text ().strip() + return {'256th': 8, + '128th': 7, + '64th': 6, + '32nd': 5, + '16th': 4, + 'eighth': 3, 'quarter': 2, 'half': 1, - '16th': 4, - '32nd': 5, - 'breve': -1, - 'long': -2, - 'whole': 0}.get (log) - else: - return 0 + 'whole': 0, + 'breve': -1, + 'long': -2}.get (log, 0) + else: + sys.stderr.write ("Encountered note without duration (no element): %s\n" % self) + return 0 def get_factor (self): - return 1 + return 1 def get_pitches (self): - return self.get_typed_children (get_class (u'pitch')) + return self.get_typed_children (get_class (u'pitch')) class Part_list (Music_xml_node): def __init__ (self): @@ -570,7 +573,10 @@ class Music_xml_spanner (Music_xml_node): else: return 0 -class Tuplet(Music_xml_spanner): +class Wedge (Music_xml_spanner): + pass + +class Tuplet (Music_xml_spanner): pass class Slur (Music_xml_spanner): @@ -671,6 +677,7 @@ class_dict = { 'tuplet': Tuplet, 'type': Type, 'wavy-line': Wavy_line, + 'wedge': Wedge, 'work': Work, } diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index e3dd61dcd0..c82e9ce589 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -72,14 +72,9 @@ def print_ly_information (printer, score_information): def musicxml_duration_to_lily (mxl_note): d = musicexp.Duration () - if mxl_note.get_maybe_exist_typed_child (musicxml.Type): - duration_log = mxl_note.get_duration_log () - if duration_log: - d.duration_log = mxl_note.get_duration_log () - else: - d.duration_log = 0 - else: - d.duration_log = 0 + # if the note has no Type child, then that method spits out a warning and + # returns 0, i.e. a whole note + d.duration_log = mxl_note.get_duration_log () d.dots = len (mxl_note.get_typed_children (musicxml.Dot)) d.factor = mxl_note._duration / d.get_length () @@ -195,22 +190,21 @@ spanner_event_dict = { 'glissando' : musicexp.GlissandoEvent, 'pedal' : musicexp.PedalEvent, 'wavy-line' : musicexp.TrillSpanEvent, - 'octave-shift' : musicexp.OctaveShiftEvent + 'octave-shift' : musicexp.OctaveShiftEvent, + 'wedge' : musicexp.HairpinEvent } spanner_type_dict = { 'start': -1, 'begin': -1, - 'up': -2, + 'crescendo': -1, + 'decreschendo': -1, + 'diminuendo': -1, + 'continue': 0, + 'up': -1, 'down': -1, 'stop': 1, 'end' : 1 } -spanner_line_type_dict = { - 'solid': 0, - 'dashed': 1, - 'dotted': 2, - 'wavy': 3 -} def musicxml_spanner_to_lily_event (mxl_event): ev = None @@ -223,17 +217,18 @@ def musicxml_spanner_to_lily_event (mxl_event): print 'unknown span event ', mxl_event - key = mxl_event.get_type () - span_direction = spanner_type_dict.get (key) - if span_direction: + type = mxl_event.get_type () + span_direction = spanner_type_dict.get (type) + # really check for None, because some types will be translated to 0, which + # would otherwise also lead to the unknown span warning + if span_direction != None: ev.span_direction = span_direction else: - print 'unknown span type', key, 'for', name + print 'unknown span type', type, 'for', name + + ev.set_span_type (type) + ev.line_type = getattr (mxl_event, 'line-type', 'solid') - if hasattr (mxl_event, 'line-type'): - span_line_type = spanner_line_type_dict.get (getattr (mxl_event, 'line-type')) - if span_line_type: - ev.line_type = span_line_type # assign the size, which is used for octave-shift, etc. ev.size = mxl_event.get_size () @@ -371,7 +366,7 @@ def musicxml_dynamics_to_lily_event (dynentry): return event -direction_spanners = [ 'octave-shift', 'pedal' ] +direction_spanners = [ 'octave-shift', 'pedal', 'wedge' ] def musicxml_direction_to_lily (n): # TODO: Handle the element! @@ -386,20 +381,8 @@ def musicxml_direction_to_lily (n): ev = musicxml_dynamics_to_lily_event (dynentry) if ev: res.append (ev) - - if entry.get_name() == "wedge": - if hasattr (entry, 'type'): - wedgetype = entry.type; - wedgetypeval = {"crescendo" : 1, "decrescendo" : -1, - "diminuendo" : -1, "stop" : 0 }.get (wedgetype) - # Really check for != None, becaus otherwise 0 will also cause - # the code to be executed! - if wedgetypeval != None: - event = musicexp.HairpinEvent (wedgetypeval) - res.append (event) - - - # octave shifts. pedal marks etc. are spanners: + + # octave shifts. pedal marks, hairpins etc. are spanners: if entry.get_name() in direction_spanners: event = musicxml_spanner_to_lily_event (entry) if event: @@ -1018,19 +1001,22 @@ def convert (filename, options): printer = musicexp.Output_printer() printer.set_file (open (driver_ly_name, 'w')) print_ly_preamble (printer, filename) - printer.dump (r'\include "%s"' % defs_ly_name) + printer.dump (r'\include "%s"' % os.path.basename (defs_ly_name)) print_score_setup (printer, part_list, voices) printer.newline () return voices def get_existing_filename_with_extension (filename, ext): - if not os.path.exists (filename): - if filename[-1] == '.': - filename += "xml" - elif not re.match ("\.xml$", filename): - filename += ".xml" - return filename + if os.path.exists (filename): + return filename + newfilename = filename + ".xml" + if os.path.exists (newfilename): + return newfilename; + newfilename = filename + "xml" + if os.path.exists (newfilename): + return newfilename; + return '' def main (): opt_parser = option_parser() @@ -1040,12 +1026,12 @@ def main (): opt_parser.print_usage() sys.exit (2) - # Allow the user to leave out the .xml on the filename + # Allow the user to leave out the .xml or xml on the filename filename = get_existing_filename_with_extension (args[0], "xml") - if not os.path.exists (filename): - print "Unable to find input file %s" % args[0] - else: + if filename and os.path.exists (filename): voices = convert (filename, options) + else: + progress ("Unable to find input file %s" % args[0]) if __name__ == '__main__': main() -- 2.39.2