From: John Gourlay Date: Thu, 26 May 2016 22:16:10 +0000 (-0400) Subject: Merge branch 'master' of ssh://git.sv.gnu.org/srv/git/lilypond into X-Git-Tag: release/2.19.44-1~24^2~1 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=57a6fdce712bbdc538b712b319d28366e05b7495;hp=a1467ba6de6240dda93009c907b05794ec9b8d54;p=lilypond.git Merge branch 'master' of ssh://git.sv.gnu.org/srv/git/lilypond into dev/johngourlay/issue-4751 to bring the development branch up to date. --- diff --git a/python/musicexp.py b/python/musicexp.py index f6b47c3c93..e8cbcb0167 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -3,7 +3,10 @@ import inspect import sys import string import re +import math import lilylib as ly +import warnings +import utilities _ = ly._ @@ -12,7 +15,8 @@ from rational import Rational # Store previously converted pitch for \relative conversion as a global state variable previous_pitch = None relative_pitches = False - +whatOrnament = "" +ly_dur = None # stores lilypond durations def escape_instrument_string (input_string): retstring = string.replace (input_string, "\"", "\\\"") @@ -35,13 +39,11 @@ class Output_stack_element: o.factor = self.factor return o -class Output_printer: - - """A class that takes care of formatting (eg.: indenting) a +class Output_printer(object): + """ + A class that takes care of formatting (eg.: indenting) a Music expression as a .ly file. - """ - def __init__ (self): self._line = '' self._indent = 4 @@ -55,9 +57,8 @@ class Output_printer: def set_file (self, file): self._file = file - def dump_version (self): - self.newline () - self.print_verbatim ('\\version "@TOPLEVEL_VERSION@"') + def dump_version (self, version): + self.print_verbatim ('\\version "' + version + '"') self.newline () def get_indent (self): @@ -69,7 +70,7 @@ class Output_printer: def add_factor (self, factor): self.override() - self._output_state_stack[-1].factor *= factor + self._output_state_stack[-1].factor *= factor def revert (self): del self._output_state_stack[-1] @@ -84,13 +85,13 @@ class Output_printer: def unformatted_output (self, str): # don't indent on \< and indent only once on << - self._nesting += ( str.count ('<') + self._nesting += (str.count ('<') - str.count ('\<') - str.count ('<<') - + str.count ('{') ) - self._nesting -= ( str.count ('>') - str.count ('\>') - str.count ('>>') + + str.count ('{')) + self._nesting -= (str.count ('>') - str.count ('\>') - str.count ('>>') - str.count ('->') - str.count ('_>') - str.count ('^>') - + str.count ('}') ) + + str.count ('}')) self.print_verbatim (str) def print_duration_string (self, str): @@ -99,6 +100,15 @@ class Output_printer: self.unformatted_output (str) +# def print_note_color (self, object, rgb=None): +# if rgb: +# str = ("\\once\\override %s #'color = #(rgb-color %s # %s %s)" % (object, rgb[0], rgb[1], rgb[2])) +# else: +# str = "\\revert %s #'color" % object +# self.newline() +# self.add_word(str) +# self.newline() + def add_word (self, str): if (len (str) + 1 + len (self._line) > self._line_len): self.newline() @@ -125,11 +135,11 @@ class Output_printer: self._skipspace = False self.unformatted_output (str) else: - words = string.split (str) + # Avoid splitting quoted strings (e.g. "1. Wie") when indenting. + words = utilities.split_string_and_preserve_doublequoted_substrings(str) for w in words: self.add_word (w) - def close (self): self.newline () self._file.close () @@ -137,19 +147,19 @@ class Output_printer: class Duration: - def __init__ (self): + def __init__(self): self.duration_log = 0 self.dots = 0 - self.factor = Rational (1) + self.factor = Rational(1) - def lisp_expression (self): + def lisp_expression(self): return '(ly:make-duration %d %d %d %d)' % (self.duration_log, self.dots, - self.factor.numerator (), - self.factor.denominator ()) + self.factor.numerator(), + self.factor.denominator()) - - def ly_expression (self, factor = None, scheme_mode = False): + def ly_expression(self, factor=None, scheme_mode=False): + global ly_dur # stores lilypond durations if not factor: factor = self.factor @@ -158,58 +168,131 @@ class Duration: longer_dict = {-1: "breve", -2: "longa"} else: longer_dict = {-1: "\\breve", -2: "\\longa"} - str = longer_dict.get (self.duration_log, "1") + dur_str = longer_dict.get(self.duration_log, "1") else: - str = '%d' % (1 << self.duration_log) - str += '.'*self.dots + dur_str = '%d' % (1 << self.duration_log) + dur_str += '.' * self.dots - if factor <> Rational (1,1): - if factor.denominator () <> 1: - str += '*%d/%d' % (factor.numerator (), factor.denominator ()) + if factor <> Rational(1, 1): + if factor.denominator() <> 1: + dur_str += '*%d/%d' % (factor.numerator(), factor.denominator()) else: - str += '*%d' % factor.numerator () + dur_str += '*%d' % factor.numerator() - return str + if dur_str.isdigit(): + ly_dur = int(dur_str) + # TODO: We need to deal with dotted notes and scaled durations + # otherwise ly_dur won't work in combination with tremolos. + return dur_str - def print_ly (self, outputter): - str = self.ly_expression (self.factor / outputter.duration_factor ()) - outputter.print_duration_string (str) + def print_ly(self, outputter): + dur_str = self.ly_expression(self.factor / outputter.duration_factor()) + outputter.print_duration_string(dur_str) def __repr__(self): return self.ly_expression() - def copy (self): - d = Duration () + def copy(self): + d = Duration() d.dots = self.dots d.duration_log = self.duration_log d.factor = self.factor return d - def get_length (self): - dot_fact = Rational( (1 << (1 + self.dots))-1, + def get_length(self): + dot_fact = Rational((1 << (1 + self.dots)) - 1, 1 << self.dots) - log = abs (self.duration_log) + log = abs(self.duration_log) dur = 1 << log if self.duration_log < 0: - base = Rational (dur) + base = Rational(dur) else: - base = Rational (1, dur) + base = Rational(1, dur) return base * dot_fact * self.factor -# implement the midi command line option '-m' and '--midi' -# if TRUE add midi-block to .ly file (see below) -def set_create_midi (option): +def set_create_midi(option): + """ + Implement the midi command line option '-m' and '--midi'. + If True, add midi-block to .ly file (see L{musicexp.Score.print_ly}). + + @param option: Indicates whether the midi-block has to be added or not. + @type option: boolean + """ global midi_option midi_option = option def get_create_midi (): + """ + Return, if exists the state of the midi-option. + + @return: The state of the midi-option. + @rtype: boolean + """ try: return midi_option except: return False +# implement the command line option '--transpose' +def set_transpose(option): + global transpose_option + transpose_option = option + +def get_transpose(optType): + try: + if(optType == "string"): + return '\\transpose c %s' % transpose_option + elif(optType == "integer"): + p = generic_tone_to_pitch(transpose_option) + return p.semitones() + except: + if(optType == "string"): + return "" + elif(optType == "integer"): + return 0 + +# implement the command line option '--tab-clef' +def set_tab_clef(option): + global tab_clef_option + tab_clef_option = option + +def get_tab_clef(): + try: + return ("tab", tab_clef_option)[tab_clef_option == "tab" or tab_clef_option == "moderntab"] + except: + return "tab" + +# definitions of the command line option '--string-numbers' +def set_string_numbers(option): + global string_numbers_option + string_numbers_option = option + +def get_string_numbers(): + try: + return ("t", string_numbers_option)[string_numbers_option == "t" or string_numbers_option == "f"] + except: + return "t" + +def generic_tone_to_pitch (tone): + accidentals_dict = { + "" : 0, + "es" : -1, + "s" : -1, + "eses" : -2, + "ses" : -2, + "is" : 1, + "isis" : 2 + } + p = Pitch () + tone_ = tone.strip().lower() + p.octave = tone_.count("'") - tone_.count(",") + tone_ = tone_.replace(",","").replace("'","") + p.step = ((ord (tone_[0]) - ord ('a') + 5) % 7) + p.alteration = accidentals_dict.get(tone_[1:], 0) + return p + # Implement the different note names for the various languages def pitch_generic (pitch, notenames, accidentals): str = notenames[pitch.step] @@ -224,7 +307,7 @@ def pitch_generic (pitch, notenames, accidentals): ly.warning (_ ("Language does not support microtones contained in the piece")) else: try: - str += {-0.5: accidentals[1], 0.5: accidentals[2]}[pitch.alteration-halftones] + str += {-0.5: accidentals[1], 0.5: accidentals[2]}[pitch.alteration - halftones] except KeyError: ly.warning (_ ("Language does not support microtones contained in the piece")) return str @@ -289,7 +372,6 @@ def set_pitch_language (language): # global variable to hold the formatting function. pitch_generating_function = pitch_general - class Pitch: def __init__ (self): self.alteration = 0 @@ -302,12 +384,12 @@ class Pitch: def transposed (self, interval): c = self.copy () - c.alteration += interval.alteration + c.alteration += interval.alteration c.step += interval.step c.octave += interval.octave c.normalize () - target_st = self.semitones() + interval.semitones() + target_st = self.semitones() + interval.semitones() c.alteration += target_st - c.semitones() return c @@ -316,7 +398,7 @@ class Pitch: c.step += 7 c.octave -= 1 c.octave += c.step / 7 - c.step = c.step % 7 + c.step = c.step % 7 def lisp_expression (self): return '(ly:make-pitch %d %d %d)' % (self.octave, @@ -328,13 +410,36 @@ class Pitch: p.alteration = self.alteration p.step = self.step p.octave = self.octave + p._force_absolute_pitch = self._force_absolute_pitch return p def steps (self): - return self.step + self.octave *7 + return self.step + self.octave * 7 def semitones (self): - return self.octave * 12 + [0,2,4,5,7,9,11][self.step] + self.alteration + return self.octave * 12 + [0, 2, 4, 5, 7, 9, 11][self.step] + self.alteration + + def normalize_alteration (c): + if(c.alteration < 0 and [True, False, False, True, False, False, False][c.step]): + c.alteration += 1 + c.step -= 1 + elif(c.alteration > 0 and [False, False, True, False, False, False, True][c.step]): + c.alteration -= 1 + c.step += 1 + c.normalize () + + def add_semitones (self, number): + semi = number + self.alteration + self.alteration = 0 + if(semi == 0): + return + sign = (1,-1)[semi < 0] + prev = self.semitones() + while abs((prev + semi) - self.semitones ()) > 1: + self.step += sign + self.normalize() + self.alteration += (prev + semi) - self.semitones () + self.normalize_alteration () def ly_step_expression (self): return pitch_generating_function (self) @@ -369,7 +474,6 @@ class Pitch: str += self.relative_pitch () else: str += self.absolute_pitch () - return str def print_ly (self, outputter): @@ -404,7 +508,7 @@ class Music: props = self.get_properties () - return "(make-music '%s %s)" % (name, props) + return "(make-music '%s %s)" % (name, props) def set_start (self, start): self.start = start @@ -414,7 +518,7 @@ class Music: return self return None - def print_comment (self, printer, text = None): + def print_comment (self, printer, text=None): if not text: text = self.comment @@ -595,12 +699,12 @@ class NestedMusic(Music): def get_subset_properties (self, predicate): return ("'elements (list %s)" % string.join (map (lambda x: x.lisp_expression(), - filter ( predicate, self.elements)))) + filter (predicate, self.elements)))) def get_neighbor (self, music, dir): assert music.parent == self idx = self.elements.index (music) idx += dir - idx = min (idx, len (self.elements) -1) + idx = min (idx, len (self.elements) - 1) idx = max (idx, 0) return self.elements[idx] @@ -630,7 +734,7 @@ class NestedMusic(Music): class SequentialMusic (NestedMusic): def get_last_event_chord (self): value = None - at = len( self.elements ) - 1 + at = len(self.elements) - 1 while (at >= 0 and not isinstance (self.elements[at], ChordEvent) and not isinstance (self.elements[at], BarLine)): @@ -640,7 +744,7 @@ class SequentialMusic (NestedMusic): value = self.elements[at] return value - def print_ly (self, printer, newline = True): + def print_ly (self, printer, newline=True): printer ('{') if self.comment: self.print_comment (printer) @@ -660,7 +764,7 @@ class SequentialMusic (NestedMusic): props = self.get_subset_properties (pred) - return "(make-music '%s %s)" % (name, props) + return "(make-music '%s %s)" % (name, props) def set_start (self, start): for e in self.elements: @@ -703,39 +807,60 @@ class Lyrics: self.lyrics_syllables = [] def print_ly (self, printer): - printer.dump ("\lyricmode {") - for l in self.lyrics_syllables: - printer.dump ( "%s " % l ) - printer.dump ("}") + printer.dump (self.ly_expression ()) + printer.newline() + printer.dump ('}') + printer.newline() def ly_expression (self): - lstr = "\lyricmode {\n " + lstr = "\lyricmode {\set ignoreMelismata = ##t" for l in self.lyrics_syllables: - lstr += l + " " - lstr += "\n}" + lstr += l + #lstr += "\n}" return lstr - class Header: + def __init__ (self): self.header_fields = {} + def set_field (self, field, value): self.header_fields[field] = value - def print_ly (self, printer): - printer.dump ("\header {") - printer.newline () - for (k,v) in self.header_fields.items (): + def format_header_strings(self, key, value, printer): + printer.dump(key + ' = ') + + # If a header item contains a line break, it is segmented. The + # substrings are formatted with the help of \markup, using + # \column and \line. An exception, however, are texidoc items, + # which should not contain LilyPond formatting commands. + if (key != 'texidoc') and ('\n' in value): + value = value.replace('"', '') + printer.dump(r'\markup \column {') + substrings = value.split('\n') + for s in substrings: + printer.newline() + printer.dump(r'\line { "' + s + '"}') + printer.dump('}') + printer.newline() + else: + printer.dump(value) + printer.newline() + + def print_ly(self, printer): + printer.dump("\header {") + printer.newline() + for (k, v) in self.header_fields.items(): if v: - printer.dump ('%s = %s' % (k,v)) - printer.newline () - printer.dump ("}") - printer.newline () - printer.newline () + self.format_header_strings(k, v, printer) + #printer.newline() + printer.dump("}") + printer.newline() + printer.newline() class Paper: - def __init__ (self): + def __init__(self): self.global_staff_size = -1 # page size self.page_width = -1 @@ -749,17 +874,31 @@ class Paper: self.system_right_margin = -1 self.system_distance = -1 self.top_system_distance = -1 + self.indent = 0 + self.short_indent = 0 + self.instrument_names = [] def print_length_field (self, printer, field, value): if value >= 0: printer.dump ("%s = %s\\cm" % (field, value)) printer.newline () + + def get_longest_instrument_name(self): + result = '' + for name in self.instrument_names: + lines = name.split('\n') + for line in lines: + if len(line) > len(result): + result = line + return result + def print_ly (self, printer): if self.global_staff_size > 0: printer.dump ('#(set-global-staff-size %s)' % self.global_staff_size) printer.newline () printer.dump ('\\paper {') printer.newline () + printer.newline () self.print_length_field (printer, "paper-width", self.page_width) self.print_length_field (printer, "paper-height", self.page_height) self.print_length_field (printer, "top-margin", self.top_margin) @@ -771,6 +910,14 @@ class Paper: # system_right_margin in LilyPond? self.print_length_field (printer, "between-system-space", self.system_distance) self.print_length_field (printer, "page-top-space", self.top_system_distance) + # TODO: Compute the indentation with the instrument name lengths + + # TODO: font width ? + char_per_cm = (len(self.get_longest_instrument_name()) * 13) / self.page_width + if (self.indent != 0): + self.print_length_field (printer, "indent", self.indent/char_per_cm) + if (self.short_indent != 0): + self.print_length_field (printer, "short-indent", self.short_indent/char_per_cm) printer.dump ('}') printer.newline () @@ -869,6 +1016,7 @@ class ChordEvent (NestedMusic): # Print all overrides and other settings needed by the # articulations/ornaments before the note + for e in other_events: e.print_before_note (printer) @@ -880,10 +1028,17 @@ class ChordEvent (NestedMusic): global previous_pitch pitches = [] basepitch = None + stem = None for x in note_events: + if(x.associated_events): + for aev in x.associated_events: + if (isinstance(aev, StemEvent) and aev.value): + stem = aev pitches.append (x.chord_element_ly ()) if not basepitch: basepitch = previous_pitch + if stem: + printer (stem.ly_expression ()) printer ('<%s>' % string.join (pitches)) previous_pitch = basepitch duration = self.get_duration () @@ -959,7 +1114,7 @@ class SpanEvent (Event): 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 + self.size = 0 # size of e.g. octave shift def wait_for_note (self): return True def get_properties(self): @@ -993,14 +1148,39 @@ class PedalEvent (SpanEvent): 1:'\\sustainOff'}.get (self.span_direction, '') class TextSpannerEvent (SpanEvent): + def print_before_note (self, printer): + if hasattr(self, 'style') and self.style=="wave": + printer.dump("\once \override TextSpanner #'style = #'trill") + try: + x = {-1:'\\textSpannerDown', 0:'\\textSpannerNeutral', 1: '\\textSpannerUp'}.get(self.force_direction, '') + printer.dump (x) + except: + pass + + def print_after_note (self, printer): + pass + def ly_expression (self): - return {-1: '\\startTextSpan', - 1:'\\stopTextSpan'}.get (self.span_direction, '') + global whatOrnament + if hasattr(self, 'style') and self.style=="ignore": + return "" + # if self.style=="wave": + if whatOrnament == "wave": + return {-1: '\\startTextSpan', + 1:'\\stopTextSpan'}.get (self.span_direction, '') + else: + if hasattr(self, 'style') and self.style=="stop" and whatOrnament != "trill": return "" + return {-1: '\\startTrillSpan', + 1:'\\stopTrillSpan'}.get (self.span_direction, '') class BracketSpannerEvent (SpanEvent): # Ligature brackets use prefix-notation!!! def print_before_note (self, printer): if self.span_direction == -1: + if self.force_direction == 1: + printer.dump("\once \override LigatureBracket #' direction = #UP") + elif self.force_direction == -1: + printer.dump("\once \override LigatureBracket #' direction = #DOWN") printer.dump ('\[') # the bracket after the last note def print_after_note (self, printer): @@ -1015,7 +1195,7 @@ 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) + self.span_type = {'up': 1, 'down':-1}.get (type, 0) def ly_octave_shift_indicator (self): # convert 8/15 to lilypond indicators (+-1/+-2) try: @@ -1024,7 +1204,7 @@ class OctaveShiftEvent (SpanEvent): ly.warning (_ ("Invalid octave shift size found: %s. Using no shift.") % self.size) value = 0 # negative values go up! - value *= -1*self.span_type + value *= -1 * self.span_type return value def ly_expression (self): dir = self.ly_octave_shift_indicator () @@ -1032,7 +1212,7 @@ class OctaveShiftEvent (SpanEvent): if dir: value = '\ottava #%s' % dir return { - -1: value, + - 1: value, 1: '\ottava #0'}.get (self.span_direction, '') class TrillSpanEvent (SpanEvent): @@ -1044,7 +1224,7 @@ class TrillSpanEvent (SpanEvent): class GlissandoEvent (SpanEvent): def print_before_note (self, printer): if self.span_direction == -1: - style= { + style = { "dashed" : "dashed-line", "dotted" : "dotted-line", "wavy" : "zigzag" @@ -1083,20 +1263,24 @@ class TieEvent(Event): class HairpinEvent (SpanEvent): def set_span_type (self, type): - self.span_type = {'crescendo' : 1, 'decrescendo' : -1, 'diminuendo' : -1 }.get (type, 0) + self.span_type = {'crescendo' : 1, 'decrescendo' :-1, 'diminuendo' :-1 }.get (type, 0) def hairpin_to_ly (self): if self.span_direction == 1: return '\!' else: return {1: '\<', -1: '\>'}.get (self.span_type, '') + def direction_mod (self): + return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-') + def ly_expression (self): return self.hairpin_to_ly () def print_ly (self, printer): val = self.hairpin_to_ly () if val: - printer.dump (val) + # printer.dump (val) + printer.dump ('%s%s' % (self.direction_mod (), val)) @@ -1104,6 +1288,7 @@ class DynamicsEvent (Event): def __init__ (self): Event.__init__ (self) self.type = None + self.force_direction = 0 def wait_for_note (self): return True def ly_expression (self): @@ -1112,9 +1297,12 @@ class DynamicsEvent (Event): else: return + def direction_mod (self): + return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-') + def print_ly (self, printer): if self.type: - printer.dump ("\\%s" % self.type) + printer.dump ('%s\\%s' % (self.direction_mod (), self.type)) class MarkEvent (Event): def __init__ (self, text="\\default"): @@ -1145,13 +1333,39 @@ class TextEvent (Event): self.force_direction = None self.markup = '' def wait_for_note (self): + """ This is problematic: the lilypond-markup ^"text" + requires wait_for_note to be true. Otherwise the + compilation will fail. So we are forced to set return to True. + But in some cases this might lead to a wrong placement of the text. + In case of words like Allegro the text should be put in a '\tempo'-command. + In this case we don't want to wait for the next note. + In some other cases the text is supposed to be used in a '\mark\markup' construct. + We would not want to wait for the next note either. + There might be other problematic situations. + In the long run we should differentiate between various contexts in MusicXML, e.g. + the following markup should be interpreted as '\tempo "Allegretto"': + + + Allegretto + + + + In the mean time arising problems have to be corrected manually after the conversion. + """ return True def direction_mod (self): + """ 1: placement="above"; -1: placement="below"; 0: no placement attribute. + see musicxml_direction_to_indicator in musicxml2ly_conversion.py """ return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-') def ly_expression (self): - base_string = '%s\"%s\"' + # self.text will be enclosed by quotes, and the direction + # modifier must be separated from the opening quote by a space. + # This is so that subsequent line breaking for the output file + # using utilities.split_string_and_preserve_doublequoted_strings() + # properly detects the opening quote. + base_string = '%s \"%s\"' if self.markup: base_string = '%s\markup{ ' + self.markup + ' {%s} }' return base_string % (self.direction_mod (), self.text) @@ -1181,9 +1395,21 @@ class ShortArticulationEvent (ArticulationEvent): return '' class NoDirectionArticulationEvent (ArticulationEvent): + + def is_breathing_sign(self): + return self.type == 'breathe' + + def print_after_note(self, printer): + # The breathing sign should, according to current LilyPond + # praxis, be treated as an independent musical + # event. Consequently, it should be printed _after_ the note + # to which it is attached. + if self.is_breathing_sign(): + printer.dump(r'\breathe') + def ly_expression (self): - if self.type: - return '\\%s' % self.type + if self.type and not self.is_breathing_sign(): + return '\\%s' % self.type else: return '' @@ -1212,11 +1438,11 @@ class FretEvent (MarkupEvent): if self.frets <> 4: val += "h:%s;" % self.frets if self.barre and len (self.barre) >= 3: - val += "c:%s-%s-%s;" % (self.barre[0], self.barre[1], self.barre[2]) + val += "c:%s-%s-%s;" % (self.barre[0], self.barre[1], self.barre[2]+get_transpose("integer")) have_fingering = False for i in self.elements: if len (i) > 1: - val += "%s-%s" % (i[0], i[1]) + val += "%s-%s" % (i[0], i[1]+(get_transpose("integer"),'')[isinstance(i[1],str)]) if len (i) > 2: have_fingering = True val += "-%s" % i[2] @@ -1228,9 +1454,35 @@ class FretEvent (MarkupEvent): else: return '' +class FretBoardNote (Music): + def __init__ (self): + Music.__init__ (self) + self.pitch = None + self.string = None + self.fingering = None + def ly_expression (self): + str = self.pitch.ly_expression() + if self.fingering: + str += "-%s" % self.fingering + if self.string: + str += "\%s" % self.string + return str + +class FretBoardEvent (NestedMusic): + def __init__ (self): + NestedMusic.__init__ (self) + self.duration = None + def print_ly (self, printer): + fretboard_notes = [n for n in self.elements if isinstance (n, FretBoardNote)] + if fretboard_notes: + notes = [] + for n in fretboard_notes: + notes.append (n.ly_expression ()) + contents = string.join (notes) + printer ('<%s>%s' % (contents,self.duration)) class FunctionWrapperEvent (Event): - def __init__ (self, function_name = None): + def __init__ (self, function_name=None): Event.__init__ (self) self.function_name = function_name def pre_note_ly (self, is_chord_element): @@ -1250,16 +1502,36 @@ class ParenthesizeEvent (FunctionWrapperEvent): def __init__ (self): FunctionWrapperEvent.__init__ (self, "parenthesize") -class NotestyleEvent (Event): +class StemEvent (Event): + """" + A class to take care of stem values (up, down, double, none) + """ + def __init__ (self): + Event.__init__ (self) + self.value = None + def pre_chord_ly (self): + if self.value: + return "\\%s" % self.value + else: + return '' + def pre_note_ly (self, is_chord_element): + return '' + def ly_expression (self): + return self.pre_chord_ly () + +class NotestyleEvent (Event): #class changed by DaLa: additional attribute color def __init__ (self): Event.__init__ (self) self.style = None self.filled = None + self.color = None def pre_chord_ly (self): + return_string = '' if self.style: - return "\\once \\override NoteHead.style = #%s" % self.style - else: - return '' + return_string += " \\once \\override NoteHead #'style = #%s" % self.style + if self.color: + return_string += " \\once \\override NoteHead #'color = #(rgb-color %s %s %s)" % (self.color[0], self.color[1], self.color[2]) + return return_string def pre_note_ly (self, is_chord_element): if self.style and is_chord_element: return "\\tweak style #%s" % self.style @@ -1268,6 +1540,20 @@ class NotestyleEvent (Event): def ly_expression (self): return self.pre_chord_ly () +class StemstyleEvent (Event): #class added by DaLa + def __init__ (self): + Event.__init__ (self) + self.color = None + def pre_chord_ly (self): + if self.color: + return "\\once \\override Stem #'color = #(rgb-color %s %s %s)" % (self.color[0], self.color[1], self.color[2]) + else: + return '' + def pre_note_ly (self, is_chord_element): + return '' + def ly_expression (self): + return self.pre_chord_ly () + class ChordPitch: def __init__ (self): @@ -1303,14 +1589,14 @@ class ChordNameEvent (Event): def add_modification (self, mod): self.modifications.append (mod) def ly_expression (self): + if not self.root: return '' value = self.root.ly_expression () if self.duration: value += self.duration.ly_expression () if self.kind: - value += ":" - value += self.kind + value = self.kind.format(value) # First print all additions/changes, and only afterwards all subtractions for m in self.modifications: if m.type == 1: @@ -1323,16 +1609,37 @@ class ChordNameEvent (Event): return value -class TremoloEvent (ArticulationEvent): - def __init__ (self): - Event.__init__ (self) - self.bars = 0 - - def ly_expression (self): - str='' - if self.bars and self.bars > 0: - str += ':%s' % (2 ** (2 + string.atoi (self.bars))) - return str +class TremoloEvent(ArticulationEvent): + def __init__(self): + Event.__init__(self) + self.strokes = 0 + + def ly_expression(self): + ly_str = '' + if self.strokes and int(self.strokes) > 0: + # ly_dur is a global variable defined in class Duration + # ly_dur stores the value of the reciprocal values of notes + # ly_dur is used here to check the current note duration + # if the duration is smaller than 8, e.g. + # quarter, half and whole notes, + # `:(2 ** (2 + number of tremolo strokes))' + # should be appended to the pitch and duration, e.g. + # 1 stroke: `c4:8' or `c2:8' or `c1:8' + # 2 strokes: `c4:16' or `c2:16' or `c1:16' + # ... + # else (if ly_dur is equal to or greater than 8): + # we need to make sure that the tremolo value that is to + # be appended to the pitch and duration is twice the + # duration (if there is only one tremolo stroke. + # Each additional stroke doubles the tremolo value, e.g.: + # 1 stroke: `c8:16', `c16:32', `c32:64', ... + # 2 strokes: `c8:32', `c16:64', `c32:128', ... + # ... + if ly_dur < 8: + ly_str += ':%s' % (2 ** (2 + int(self.strokes))) + else: + ly_str += ':%s' % (2 ** int((math.log(ly_dur, 2)) + int(self.strokes))) + return ly_str class BendEvent (ArticulationEvent): def __init__ (self): @@ -1388,6 +1695,10 @@ class RestEvent (RhythmicEvent): def print_ly (self, printer): for ev in self.associated_events: ev.print_ly (printer) +# if hasattr(self, 'color'): +# printer.print_note_color("NoteHead", self.color) +# printer.print_note_color("Stem", self.color) +# printer.print_note_color("Beam", self.color) if self.pitch: self.pitch.print_ly (printer) self.duration.print_ly (printer) @@ -1403,7 +1714,7 @@ class SkipEvent (RhythmicEvent): class NoteEvent(RhythmicEvent): def __init__ (self): RhythmicEvent.__init__ (self) - self.pitch = None + #self.pitch = None self.drum_type = None self.cautionary = False self.forced_accidental = False @@ -1451,6 +1762,11 @@ class NoteEvent(RhythmicEvent): def print_ly (self, printer): for ev in self.associated_events: ev.print_ly (printer) + if hasattr(self, 'color'): + printer.print_note_color("NoteHead", self.color) + printer.print_note_color("Stem", self.color) + printer.print_note_color("Beam", self.color) + if self.pitch: self.pitch.print_ly (printer) printer (self.pitch_mods ()) @@ -1459,6 +1775,11 @@ class NoteEvent(RhythmicEvent): self.duration.print_ly (printer) +# if hasattr(self, 'color'): +# printer.print_note_color("NoteHead") +# printer.print_note_color("Stem") +# printer.print_note_color("Beam") + class KeySignatureChange (Music): def __init__ (self): Music.__init__ (self) @@ -1468,9 +1789,9 @@ class KeySignatureChange (Music): def format_non_standard_alteration (self, a): alter_dict = { -2: ",DOUBLE-FLAT", - -1.5: ",THREE-Q-FLAT", - -1: ",FLAT", - -0.5: ",SEMI-FLAT", + - 1.5: ",THREE-Q-FLAT", + - 1: ",FLAT", + - 0.5: ",SEMI-FLAT", 0: ",NATURAL", 0.5: ",SEMI-SHARP", 1: ",SHARP", @@ -1499,11 +1820,42 @@ class KeySignatureChange (Music): else: return '' +class ShiftDurations (MusicWrapper): + def __init__ (self): + MusicWrapper.__init__ (self) + self.params = [0,0] + + def set_shift_durations_parameters(self, timeSigChange): + self.params = timeSigChange.get_shift_durations_parameters() + + def print_ly (self, func): + func (' \\shiftDurations #%d #%d ' % tuple(self.params)) + MusicWrapper.print_ly (self, func) + class TimeSignatureChange (Music): def __init__ (self): Music.__init__ (self) - self.fractions = [4,4] + self.fractions = [4, 4] self.style = None + # Used for the --time-signature option of musicxml2ly + self.originalFractions = [4, 4] + + def get_fractions_ratio (self): + """ + Calculate the ratio between the original time fraction and the new one. + Used for the "--time-signature" option. + + @return: The ratio between the two time fractions. + @rtype: float + """ + return (float(self.originalFractions[0])/self.originalFractions[1])*(float(self.fractions[1])/self.fractions[0]) + + def get_shift_durations_parameters (self): + dur = math.ceil(math.log(self.get_fractions_ratio(),2)) + dots = (1/self.get_fractions_ratio())/(math.pow(2,-dur)) + dots = int(math.log(2-dots,0.5)) + return [dur, dots] + def format_fraction (self, frac): if isinstance (frac, list): l = [self.format_fraction (f) for f in frac] @@ -1516,7 +1868,7 @@ class TimeSignatureChange (Music): # Print out the style if we have ome, but the '() should only be # forced for 2/2 or 4/4, since in all other cases we'll get numeric # signatures anyway despite the default 'C signature style! - is_common_signature = self.fractions in ([2,2], [4,4], [4,2]) + is_common_signature = self.fractions in ([2, 2], [4, 4], [4, 2]) if self.style: if self.style == "common": st = "\\defaultTimeSignature" @@ -1542,6 +1894,7 @@ class ClefChange (Music): def octave_modifier (self): return {1: "^8", 2: "^15", -1: "_8", -2: "_15"}.get (self.octave, '') + def clef_name (self): return {('G', 2): "treble", ('G', 1): "french", @@ -1554,9 +1907,10 @@ class ClefChange (Music): ('F', 4): "bass", ('F', 5): "subbass", ("percussion", 2): "percussion", - # Workaround: MuseScore uses PERC instead of percussion + # Workaround: MuseScore uses PERC instead of percussion ("PERC", 2): "percussion", - ("TAB", 5): "tab"}.get ((self.type, self.position), None) + ("TAB", 5): get_tab_clef ()}.get ((self.type, self.position), None) + def ly_expression (self): return '\\clef "%s%s"' % (self.clef_name (), self.octave_modifier ()) @@ -1753,10 +2107,11 @@ class Break (Music): printer.dump ("\\%s" % self.type) class StaffGroup: - def __init__ (self, command = "StaffGroup"): + def __init__ (self, command="StaffGroup"): self.stafftype = command self.id = None self.instrument_name = None + self.sound = None self.short_instrument_name = None self.symbol = None self.spanbar = None @@ -1786,6 +2141,10 @@ class StaffGroup: for c in self.children: if c: c.print_ly (printer) + #Intention: I want to put the content of new StaffGroup in angled brackets (<< >>) + #printer.dump ("test")# test is printed twice at the end of a staffgroup with two staves. + #printer ("test") # test is printed twice at the end of a staffgroup with two staves. + def needs_with (self): needs_with = False needs_with |= self.spanbar == "no" @@ -1793,6 +2152,7 @@ class StaffGroup: needs_with |= self.short_instrument_name != None needs_with |= (self.symbol != None) and (self.symbol != "bracket") return needs_with + def print_ly_context_mods (self, printer): if self.instrument_name or self.short_instrument_name: printer.dump ("\\consists \"Instrument_name_engraver\"") @@ -1812,24 +2172,45 @@ class StaffGroup: for m in self.context_modifications: printer.dump (m) printer.dump ("}") - - def print_ly_chords (self,printer): + printer.newline () + #print a single << after StaffGroup only when the with-block is not needed. + #This doesn't work. << is printed before and after StaffGroup! + #else: + # printer.dump (" <<") + #prints loads off << before and after StaffGroup and before \set Staff.instrumentName + #elif not needs_with: + # printer.dump (" <<") + + def print_chords(self, printer): try: for [staff_id, voices] in self.part_information: - for [v, lyrics, figuredbass, chordnames] in voices: + for [v, lyrics, figuredbass, chordnames, fretboards] in voices: if chordnames: - printer ('\context ChordNames = "%s" \\%s' % (chordnames, chordnames)) - printer.newline () + printer ('\context ChordNames = "%s" {%s \\%s}' % (chordnames, get_transpose ("string"), chordnames)) + printer.newline() + except TypeError: + return + + def print_fretboards(self, printer): + try: + for [staff_id, voices] in self.part_information: + for [v, lyrics, figuredbass, chordnames, fretboards] in voices: + if fretboards: + printer ('\context FretBoards = "%s" {%s \\%s}' % (fretboards, get_transpose ("string"), fretboards)) + printer.newline() except TypeError: return def print_ly (self, printer): - self.print_ly_chords (printer) + self.print_chords(printer) + self.print_fretboards(printer) if self.stafftype: printer.dump ("\\new %s" % self.stafftype) self.print_ly_overrides (printer) - printer.dump ("<<") printer.newline () + if self.stafftype: + printer.dump ("<<") + printer.newline () if self.stafftype and self.instrument_name: printer.dump ("\\set %s.instrumentName = %s" % (self.stafftype, escape_instrument_string (self.instrument_name))) @@ -1838,23 +2219,32 @@ class StaffGroup: printer.dump ("\\set %s.shortInstrumentName = %s" % (self.stafftype, escape_instrument_string (self.short_instrument_name))) printer.newline () + if self.sound: + printer.dump( + r'\set {stafftype}.midiInstrument = #"{sound}"'.format( + stafftype=self.stafftype, sound=self.sound)) + printer.newline () self.print_ly_contents (printer) printer.newline () - printer.dump (">>") - printer.newline () + if self.stafftype: + printer.dump (">>") + printer.newline () class Staff (StaffGroup): - def __init__ (self, command = "Staff"): + def __init__ (self, command="Staff"): StaffGroup.__init__ (self, command) self.is_group = False self.part = None self.voice_command = "Voice" self.substafftype = None + self.sound = None def needs_with (self): return False + def print_ly_context_mods (self, printer): + #printer.dump ("test") #does nothing. pass def print_ly_contents (self, printer): @@ -1863,39 +2253,52 @@ class Staff (StaffGroup): sub_staff_type = self.substafftype if not sub_staff_type: sub_staff_type = self.stafftype + #printer.dump ("test") #prints test in each staff after the definitions of the instrument name and before the definition of the contexts. + printer.newline() for [staff_id, voices] in self.part_information: + # now comes the real staff definition: if staff_id: printer ('\\context %s = "%s" << ' % (sub_staff_type, staff_id)) else: printer ('\\context %s << ' % sub_staff_type) printer.newline () + printer.dump("\mergeDifferentlyDottedOn\mergeDifferentlyHeadedOn") + printer.newline() n = 0 nr_voices = len (voices) - for [v, lyrics, figuredbass, chordnames] in voices: + for [v, lyrics, figuredbass, chordnames, fretboards] in voices: n += 1 voice_count_text = '' if nr_voices > 1: - voice_count_text = {1: ' \\voiceOne', 2: ' \\voiceTwo', - 3: ' \\voiceThree'}.get (n, ' \\voiceFour') - printer ('\\context %s = "%s" {%s \\%s }' % (self.voice_command, v, voice_count_text, v)) + """ +The next line contains a bug: The voices might not appear in numerical order! Some voices might be missing e.g. if the xml file contains only voice one, three and four, this would result in: \voiceOne, \voiceTwo and \voiceThree. This causes wrong stem directions and collisions. + """ + voice_count_text = {1: ' \\voiceOne', 2: ' \\voiceTwo', 3: ' \\voiceThree'}.get (n, ' \\voiceFour') + printer ('\\context %s = "%s" {%s %s \\%s }' % (self.voice_command, v, get_transpose ("string"), voice_count_text, v)) printer.newline () - + lyrics_id = 1 for l in lyrics: - printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (v,l)) + printer ('\\new Lyrics \\lyricsto "%s" { \\set stanza = "%s." \\%s }' % (v, lyrics_id, l)) + lyrics_id += 1 printer.newline() if figuredbass: printer ('\context FiguredBass = "%s" \\%s' % (figuredbass, figuredbass)) printer ('>>') + #printer.dump ("test") #prints test after each definition of a context. + #printer.newline () + #printer.dump ("test") #prints test after each definition of a context. def print_ly (self, printer): if self.part_information and len (self.part_information) > 1: self.stafftype = "PianoStaff" self.substafftype = "Staff" + #printer.dump ('test') StaffGroup.print_ly (self, printer) + class TabStaff (Staff): - def __init__ (self, command = "TabStaff"): + def __init__ (self, command="TabStaff"): Staff.__init__ (self, command) self.string_tunings = [] self.tablature_format = None @@ -1914,7 +2317,7 @@ class TabStaff (Staff): class DrumStaff (Staff): - def __init__ (self, command = "DrumStaff"): + def __init__ (self, command="DrumStaff"): Staff.__init__ (self, command) self.drum_style_table = None self.voice_command = "DrumVoice" @@ -1925,11 +2328,18 @@ class DrumStaff (Staff): printer.dump ("}") class RhythmicStaff (Staff): - def __init__ (self, command = "RhythmicStaff"): + def __init__ (self, command="RhythmicStaff"): Staff.__init__ (self, command) +#Test +#def print_staffgroup_closing_brackets (self, printer): #test see class Score / class Staff +# printer.dump ("test") + class Score: def __init__ (self): + """ + Constructs a new Score object. + """ self.contents = None self.create_midi = False @@ -1940,28 +2350,67 @@ class Score: if self.contents: self.contents.set_part_information (part_id, staves_info) + def set_tempo (self, tempo): + """ + Set the tempo attribute of the Score. + This attribute can be used in L{print_ly} for the midi output (see L{musicxml.Sound}). + + @param tempo: The value of the tempo, in beats per minute. + @type tempo: String + """ + self.tempo = tempo + #Test +# def print_staffgroup_closing_brackets (self, printer): #test see class Score / class Staff +# printer.dump ("test") + def print_ly (self, printer): - self.create_midi = get_create_midi () - printer.dump ("\\score {"); + """ + Print the content of the score to the printer, in lilypond format. + + @param printer: A printer given to display correctly the output. + @type printer: L{Output_printer} + """ + self.create_midi = get_create_midi() + printer.dump("\\score {") + printer.newline () + #prints opening <<: + printer.dump ('<<') printer.newline () if self.contents: - self.contents.print_ly (printer); - printer.dump ("\\layout {}"); + self.contents.print_ly(printer) + #printer.dump ("test") prints test once before the >> of the score block, independent of the existence of a staffgroup. + #if StaffGroup == False: # True or False: nothing happens. + # printer.dump ('>>') + printer.dump ('>>') printer.newline () - if not self.create_midi: - printer.dump ("% To create MIDI output, uncomment the following line:"); - printer.newline (); - printer.dump ("% "); - printer.dump ("\\midi {}"); + #StaffGroup.print_staffgroup_closing_brackets(self, printer) #TypeError: unbound method print_staffgroup_closing_brackets() must be called with StaffGroup instance as first argument (got Score instance instead) + #print_staffgroup_closing_brackets(self, printer) #NameError: global name 'print_staffgroup_closing_brackets' is not defined. prints test once before the >> of the score block, independent of the existence of a staffgroup. + printer.dump ("\\layout {}") printer.newline () - printer.dump ("}"); + # If the --midi option was not passed to musicxml2ly, that comments the "midi" line + if self.create_midi: + printer.dump ("}") + printer.newline() + printer.dump("\\score {") + printer.newline () + printer.dump("\\unfoldRepeats \\articulate {") + printer.newline () + self.contents.print_ly(printer) + printer.dump("}") + printer.newline () + else: + printer.dump ("% To create MIDI output, uncomment the following line:") + printer.newline () + printer.dump ("% ") + printer.dump ("\\midi {\\tempo 4 = "+self.tempo+" }") + printer.newline () + printer.dump ("}") printer.newline () - def test_pitch (): bflat = Pitch() bflat.alteration = -1 - bflat.step = 6 + bflat.step = 6 bflat.octave = -1 fifth = Pitch() fifth.step = 4 @@ -1971,7 +2420,7 @@ def test_pitch (): print bflat.semitones() - print bflat.transposed (fifth), bflat.transposed (fifth).transposed (fifth) + print bflat.transposed (fifth), bflat.transposed (fifth).transposed (fifth) print bflat.transposed (fifth).transposed (fifth).transposed (fifth) print bflat.semitones(), 'down' @@ -2043,8 +2492,8 @@ def test_expr (): tonic.step = 2 tonic.alteration = -2 n = KeySignatureChange() - n.tonic=tonic.copy() - n.scale = [0, 0, -2, 0, 0,-2,-2] + n.tonic = tonic.copy() + n.scale = [0, 0, -2, 0, 0, -2, -2] evc.insert_around (None, n, 0) m.insert_around (None, evc, 0) @@ -2060,11 +2509,10 @@ if __name__ == '__main__': expr = test_expr() expr.set_start (Rational (0)) print expr.ly_expression() - start = Rational (0,4) - stop = Rational (4,2) + start = Rational (0, 4) + stop = Rational (4, 2) def sub(x, start=start, stop=stop): - ok = x.start >= start and x.start +x.get_length() <= stop + ok = x.start >= start and x.start + x.get_length() <= stop return ok print expr.lisp_sub_expression(sub) - diff --git a/python/musicxml.py b/python/musicxml.py index a061b6ea6e..f0b82f1333 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -6,45 +6,18 @@ import re import sys import copy import lilylib as ly +import warnings _ = ly._ - -def escape_ly_output_string (input_string): - return_string = input_string - needs_quotes = not re.match (u"^[a-zA-ZäöüÜÄÖßñ]*$", return_string); - if needs_quotes: - return_string = "\"" + string.replace (return_string, "\"", "\\\"") + "\"" - return return_string - - -def musicxml_duration_to_log (dur): - return {'256th': 8, - '128th': 7, - '64th': 6, - '32nd': 5, - '16th': 4, - 'eighth': 3, - 'quarter': 2, - 'half': 1, - 'whole': 0, - 'breve': -1, - 'longa': -2, - 'long': -2}.get (dur, 0) - - - -def interpret_alter_element (alter_elm): - alter = 0 - if alter_elm: - val = eval(alter_elm.get_text ()) - if type (val) in (int, float): - alter = val - return alter +import musicexp +import musicxml2ly_conversion +import utilities class Xml_node: - def __init__ (self): + + def __init__(self): self._children = [] self._data = None self._original = None @@ -52,235 +25,422 @@ class Xml_node: self._parent = None self._attribute_dict = {} - def get_parent (self): + def get_parent(self): return self._parent - def is_first (self): - return self._parent.get_typed_children (self.__class__)[0] == self + def is_first(self): + return self._parent.get_typed_children(self.__class__)[0] == self - def original (self): + def original(self): return self._original - def get_name (self): + def get_name(self): return self._name - def get_text (self): + def get_text(self): if self._data: return self._data if not self._children: return '' - return ''.join ([c.get_text () for c in self._children]) + return ''.join([c.get_text() for c in self._children]) - def message (self, msg): - ly.warning (msg) + def message(self, msg): + ly.warning(msg) p = self while p: - ly.progress (' In: <%s %s>\n' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items ()]))) - p = p.get_parent () + ly.progress(' In: <%s %s>\n' %(p._name, ' '.join(['%s=%s' % item for item in p._attribute_dict.items()]))) + p = p.get_parent() - def dump (self, indent = ''): - ly.debug_output ('%s<%s%s>' % (indent, self._name, ''.join ([' %s=%s' % item for item in self._attribute_dict.items ()]))) - non_text_children = [c for c in self._children if not isinstance (c, Hash_text)] + def dump(self, indent=''): + ly.debug_output('%s<%s%s>' %(indent, self._name, ''.join([' %s=%s' % item for item in self._attribute_dict.items()]))) + non_text_children = [c for c in self._children if not isinstance(c, Hash_text)] if non_text_children: - ly.debug_output ('\n') + ly.debug_output('\n') for c in self._children: - c.dump (indent + " ") + c.dump(indent + " ") if non_text_children: - ly.debug_output (indent) - ly.debug_output ('\n' % self._name) + ly.debug_output(indent) + ly.debug_output('\n' % self._name) - def get_typed_children (self, klass): + def get_typed_children(self, klass): if not klass: return [] else: return [c for c in self._children if isinstance(c, klass)] - def get_named_children (self, nm): - return self.get_typed_children (get_class (nm)) + def get_named_children(self, nm): + return self.get_typed_children(get_class(nm)) - def get_named_child (self, nm): - return self.get_maybe_exist_named_child (nm) + def get_named_child(self, nm): + return self.get_maybe_exist_named_child(nm) - def get_children (self, predicate): + def get_children(self, predicate): return [c for c in self._children if predicate(c)] - def get_all_children (self): + def get_all_children(self): return self._children - def get_maybe_exist_named_child (self, name): - return self.get_maybe_exist_typed_child (get_class (name)) + def get_maybe_exist_named_child(self, name): + return self.get_maybe_exist_typed_child(get_class(name)) - def get_maybe_exist_typed_child (self, klass): - cn = self.get_typed_children (klass) - if len (cn)==0: + def get_maybe_exist_typed_child(self, klass): + cn = self.get_typed_children(klass) + if len(cn) == 0: return None - elif len (cn) == 1: - return cn[0] else: - raise "More than 1 child", klass - - def get_unique_typed_child (self, klass): + try: + assert len(cn) == 1 + return cn[0] + except: + msg = ' '.join( + ['more than one child of class ', + klass.__name__, + '...all but the first will be ignored!']) + warnings.warn(msg) + return cn[0] + + def get_unique_typed_child(self, klass): cn = self.get_typed_children(klass) - if len (cn) <> 1: - ly.error (self.__dict__) - raise 'Child is not unique for', (klass, 'found', cn) + if len(cn) <> 1: + ly.error(self.__dict__) + raise 'Child is not unique for',(klass, 'found', cn) return cn[0] - def get_named_child_value_number (self, name, default): - n = self.get_maybe_exist_named_child (name) + def get_named_child_value_number(self, name, default): + n = self.get_maybe_exist_named_child(name) if n: - return string.atoi (n.get_text()) + return int(n.get_text()) else: return default -class Music_xml_node (Xml_node): - def __init__ (self): - Xml_node.__init__ (self) - self.duration = Rational (0) - self.start = Rational (0) +class Music_xml_node(Xml_node): + def __init__(self): + Xml_node.__init__(self) + self.duration = Rational(0) + self.start = Rational(0) + self.converted = False self.voice_id = None; -class Work (Xml_node): - def get_work_information (self, tag): - wt = self.get_maybe_exist_named_child (tag) + +class Music_xml_spanner(Music_xml_node): + + def get_type(self): + if hasattr(self, 'type'): + return self.type + else: + return 0 + + def get_size(self): + if hasattr(self, 'size'): + return int(self.size) + else: + return 0 + + +class Measure_element(Music_xml_node): + + def get_voice_id(self): + voice_id = self.get_maybe_exist_named_child('voice') + if voice_id: + return voice_id.get_text() + else: + return None + + def is_first(self): + # Look at all measure elements(previously we had self.__class__, which + # only looked at objects of the same type! + cn = self._parent.get_typed_children(Measure_element) + # But only look at the correct voice; But include Attributes, too, which + # are not tied to any particular voice + cn = [c for c in cn if(c.get_voice_id() == self.get_voice_id()) or isinstance(c, Attributes)] + return cn[0] == self + + +class Work(Xml_node): + + def get_work_information(self, tag): + wt = self.get_maybe_exist_named_child(tag) if wt: - return wt.get_text () + return wt.get_text() else: return '' - def get_work_title (self): - return self.get_work_information ('work-title') - def get_work_number (self): - return self.get_work_information ('work-number') - def get_opus (self): - return self.get_work_information ('opus') + def get_work_title(self): + return self.get_work_information('work-title') -class Identification (Xml_node): - def get_rights (self): - rights = self.get_named_children ('rights') + def get_work_number(self): + return self.get_work_information('work-number') + + # def get_opus(self): + # return self.get_work_information('opus') + + +class Identification(Xml_node): + + def get_rights(self): + rights = self.get_named_children('rights') ret = [] for r in rights: - ret.append (r.get_text ()) - return string.join (ret, "\n") + text = r.get_text() + # if this Xml_node has an attribute, such as 'type="words"', + # include it in the header. Otherwise, it is assumed that + # the text contents of this node looks something like this: + # 'Copyright: X.Y.' and thus already contains the relevant + # information. + if hasattr(r, 'type'): + rights_type = r.type.title() # capitalize first letter + result = ''.join([rights_type, ': ', text]) + ret.append(result) + else: + ret.append(text) + return string.join(ret, "\n") - # get contents of the source-element (usually used for publishing information). (These contents are saved in a custom variable named "source" in the header of the .ly file.) - def get_source (self): - source = self.get_named_children ('source') + # get contents of the source-element(usually used for publishing information).(These contents are saved in a custom variable named "source" in the header of the .ly file.) + def get_source(self): + source = self.get_named_children('source') ret = [] for r in source: - ret.append (r.get_text ()) - return string.join (ret, "\n") + ret.append(r.get_text()) + return string.join(ret, "\n") - def get_creator (self, type): - creators = self.get_named_children ('creator') + def get_creator(self, type): + creators = self.get_named_children('creator') # return the first creator tag that has the particular type for i in creators: - if hasattr (i, 'type') and i.type == type: - return i.get_text () + if hasattr(i, 'type') and i.type == type: + return i.get_text() return None - def get_composer (self): - c = self.get_creator ('composer') + def get_composer(self): + c = self.get_creator('composer') if c: return c - creators = self.get_named_children ('creator') + creators = self.get_named_children('creator') # return the first creator tag that has no type at all for i in creators: - if not hasattr (i, 'type'): - return i.get_text () + if not hasattr(i, 'type'): + return i.get_text() return None - def get_arranger (self): - return self.get_creator ('arranger') - def get_editor (self): - return self.get_creator ('editor') - def get_poet (self): - v = self.get_creator ('lyricist') + + def get_arranger(self): + return self.get_creator('arranger') + + def get_editor(self): + return self.get_creator('editor') + + def get_poet(self): + v = self.get_creator('lyricist') if v: return v - v = self.get_creator ('poet') + v = self.get_creator('poet') return v - def get_encoding_information (self, type): - enc = self.get_named_children ('encoding') + def get_encoding_information(self, type): + enc = self.get_named_children('encoding') if enc: - children = enc[0].get_named_children (type) + children = enc[0].get_named_children(type) if children: - return children[0].get_text () + return children[0].get_text() else: return None - def get_encoding_software (self): - return self.get_encoding_information ('software') - def get_encoding_date (self): - return self.get_encoding_information ('encoding-date') - def get_encoding_person (self): - return self.get_encoding_information ('encoder') - def get_encoding_description (self): - return self.get_encoding_information ('encoding-description') - - def get_encoding_software_list (self): - enc = self.get_named_children ('encoding') + def get_encoding_software(self): + return self.get_encoding_information('software') + + def get_encoding_date(self): + return self.get_encoding_information('encoding-date') + + def get_encoding_person(self): + return self.get_encoding_information('encoder') + + def get_encoding_description(self): + return self.get_encoding_information('encoding-description') + + def get_encoding_software_list(self): + enc = self.get_named_children('encoding') software = [] for e in enc: - softwares = e.get_named_children ('software') + softwares = e.get_named_children('software') for s in softwares: - software.append (s.get_text ()) + software.append(s.get_text()) return software - def get_file_description (self): - misc = self.get_named_children ('miscellaneous') + def get_file_description(self): + misc = self.get_named_children('miscellaneous') for m in misc: - misc_fields = m.get_named_children ('miscellaneous-field') + misc_fields = m.get_named_children('miscellaneous-field') for mf in misc_fields: - if hasattr (mf, 'name') and mf.name == 'description': - return mf.get_text () + if hasattr(mf, 'name') and mf.name == 'description': + return mf.get_text() return None -class Duration (Music_xml_node): - def get_length (self): - dur = int (self.get_text ()) * Rational (1,4) + +class Credit(Xml_node): + + def get_type(self): + type = self.get_maybe_exist_named_child('credit-type') + if(type != None): + return type.get_text() + else: + return None + + def find_type(self, credits): + sizes = self.get_font_sizes(credits) + sizes.sort(reverse=True) + ys = self.get_default_ys(credits) + ys.sort(reverse=True) + xs = self.get_default_xs(credits) + xs.sort(reverse=True) + + # Words child of the self credit-element + words = self.get_maybe_exist_named_child('credit-words') + size = None + x = None + y = None + halign = None + valign = None + justify = None + if(words != None): + if hasattr(words, 'font-size'): + size = utilities.string_to_integer(getattr(words, 'font-size')) + if hasattr(words, 'default-x'): + x = round(float(getattr(words, 'default-x'))) + if hasattr(words, 'default-y'): + y = round(float(getattr(words, 'default-y'))) + if hasattr(words, 'halign'): + halign = getattr(words, 'halign') + if hasattr(words, 'valign'): + valign = getattr(words, 'valign') + if hasattr(words, 'justify'): + justify = getattr(words, 'justify') + if(size and size == max(sizes) and y and y == max(ys) and(justify or halign) and(justify == 'center' or halign == 'center')): + return 'title' + elif((y and y > min(ys) and y < max(ys)) and((justify or halign) and(justify == 'center' or halign == 'center'))): + return 'subtitle' + elif((justify or halign) and(justify == 'left' or halign == 'left') and(not(x) or x == min(xs))): + return 'lyricist' + elif((justify or halign) and(justify == 'right' or halign == 'right') and(not(x) or x == max(xs))): + return 'composer' + elif(size and size == min(sizes) and y == min(ys)): + return 'rights' + # Special cases for Finale NotePad + elif((valign and(valign == 'top')) and(y and y == ys[1])): + return 'subtitle' + elif((valign and(valign == 'top')) and(x and x == min(xs))): + return 'lyricist' + elif((valign and(valign == 'top')) and(y and y == min(ys))): + return 'rights' + # Other special cases + elif((valign and(valign == 'bottom'))): + return 'rights' + elif(len([item for item in range(len(ys)) if ys[item] == y]) == 2): + # The first one is the composer, the second one is the lyricist + return 'composer' + + return None # no type recognized + + def get_font_sizes(self, credits): + sizes = [] + for cred in credits: + words = cred.get_maybe_exist_named_child('credit-words') + if((words != None) and hasattr(words, 'font-size')): + sizes.append(getattr(words, 'font-size')) + return map(utilities.string_to_integer, sizes) + + def get_default_xs(self, credits): + default_xs = [] + for cred in credits: + words = cred.get_maybe_exist_named_child('credit-words') + if((words != None) and hasattr(words, 'default-x')): + default_xs.append(getattr(words, 'default-x')) + return map(round, map(float, default_xs)) + + def get_default_ys(self, credits): + default_ys = [] + for cred in credits: + words = cred.get_maybe_exist_named_child('credit-words') + if((words != None) and hasattr(words, 'default-y')): + default_ys.append(getattr(words, 'default-y')) + return map(round, map(float, default_ys)) + + def get_text(self): + words = self.get_maybe_exist_named_child('credit-words') + if(words != None): + return words.get_text() + else: + return '' + + +class Duration(Music_xml_node): + + def get_length(self): + dur = int(self.get_text()) * Rational(1, 4) return dur -class Hash_comment (Music_xml_node): - pass -class Hash_text (Music_xml_node): - def dump (self, indent = ''): - ly.debug_output ('%s' % string.strip (self._data)) - -class Pitch (Music_xml_node): - def get_step (self): - ch = self.get_unique_typed_child (get_class (u'step')) - step = ch.get_text ().strip () + +class Hash_text(Music_xml_node): + + def dump(self, indent=''): + ly.debug_output('%s' % string.strip(self._data)) + + +class Pitch(Music_xml_node): + + def get_step(self): + ch = self.get_unique_typed_child(get_class(u'step')) + step = ch.get_text().strip() return step - def get_octave (self): - ch = self.get_unique_typed_child (get_class (u'octave')) - octave = ch.get_text ().strip () - return int (octave) - - def get_alteration (self): - ch = self.get_maybe_exist_typed_child (get_class (u'alter')) - return interpret_alter_element (ch) - -class Unpitched (Music_xml_node): - def get_step (self): - ch = self.get_unique_typed_child (get_class (u'display-step')) - step = ch.get_text ().strip () + + def get_octave(self): + ch = self.get_unique_typed_child(get_class(u'octave')) + octave = ch.get_text().strip() + return int(octave) + + def get_alteration(self): + ch = self.get_maybe_exist_typed_child(get_class(u'alter')) + return utilities.interpret_alter_element(ch) + + def to_lily_object(self): + p = musicexp.Pitch() + p.alteration = self.get_alteration() + p.step = musicxml2ly_conversion.musicxml_step_to_lily(self.get_step()) + p.octave = self.get_octave() - 4 + return p + + +class Unpitched(Music_xml_node): + + def get_step(self): + ch = self.get_unique_typed_child(get_class(u'display-step')) + step = ch.get_text().strip() return step - def get_octave (self): - ch = self.get_unique_typed_child (get_class (u'display-octave')) + def get_octave(self): + ch = self.get_unique_typed_child(get_class(u'display-octave')) if ch: - octave = ch.get_text ().strip () - return int (octave) + octave = ch.get_text().strip() + return int(octave) else: return None + def to_lily_object(self): + p = None + step = self.get_step() + if step: + p = musicexp.Pitch() + p.step = musicxml2ly_conversion.musicxml_step_to_lily(step) + octave = self.get_octave() + if octave and p: + p.octave = octave - 4 + return p + + class Measure_element (Music_xml_node): def get_voice_id (self): voice = self.get_maybe_exist_named_child ('voice') @@ -289,345 +449,847 @@ class Measure_element (Music_xml_node): else: return self.voice_id; - def is_first (self): - # Look at all measure elements (previously we had self.__class__, which - # only looked at objects of the same type! - cn = self._parent.get_typed_children (Measure_element) - # But only look at the correct voice; But include Attributes, too, which - # are not tied to any particular voice - cn = [c for c in cn if (c.get_voice_id () == self.get_voice_id ()) or isinstance (c, Attributes)] - return cn[0] == self -class Attributes (Measure_element): - def __init__ (self): - Measure_element.__init__ (self) +class Attributes(Measure_element): + + def __init__(self): + Measure_element.__init__(self) self._dict = {} self._original_tag = None self._time_signature_cache = None - def is_first (self): - cn = self._parent.get_typed_children (self.__class__) + def is_first(self): + cn = self._parent.get_typed_children(self.__class__) if self._original_tag: return cn[0] == self._original_tag else: return cn[0] == self - def set_attributes_from_previous (self, dict): - self._dict.update (dict) + def set_attributes_from_previous(self, dict): + self._dict.update(dict) - def read_self (self): - for c in self.get_all_children (): + def read_self(self): + for c in self.get_all_children(): self._dict[c.get_name()] = c - def get_named_attribute (self, name): - return self._dict.get (name) + def get_named_attribute(self, name): + return self._dict.get(name) - def single_time_sig_to_fraction (self, sig): - if len (sig) < 2: + def single_time_sig_to_fraction(self, sig): + if len(sig) < 2: return 0 n = 0 for i in sig[0:-1]: n += i - return Rational (n, sig[-1]) + return Rational(n, sig[-1]) - def get_measure_length (self): - sig = self.get_time_signature () - if not sig or len (sig) == 0: + def get_measure_length(self): + sig = self.get_time_signature() + if not sig or len(sig) == 0: return 1 - if isinstance (sig[0], list): + if isinstance(sig[0], list): # Complex compound time signature l = 0 for i in sig: - l += self.single_time_sig_to_fraction (i) + l += self.single_time_sig_to_fraction(i) return l else: - # Simple (maybe compound) time signature of the form (beat, ..., type) - return self.single_time_sig_to_fraction (sig) + # Simple(maybe compound) time signature of the form(beat, ..., type) + return self.single_time_sig_to_fraction(sig) return 0 - def get_time_signature (self): - "Return time sig as a (beat, beat-type) tuple. For compound signatures," - "return either (beat, beat,..., beat-type) or ((beat,..., type), " + def get_time_signature(self): + "Return time sig as a(beat, beat-type) tuple. For compound signatures," + "return either(beat, beat,..., beat-type) or((beat,..., type), " "(beat,..., type), ...)." if self._time_signature_cache: return self._time_signature_cache try: - mxl = self.get_named_attribute ('time') + mxl = self.get_named_attribute('time') if not mxl: return None - if mxl.get_maybe_exist_named_child ('senza-misura'): + if mxl.get_maybe_exist_named_child('senza-misura'): # TODO: Handle pieces without a time signature! - ly.warning (_ ("Senza-misura time signatures are not yet supported!")) - return (4, 4) + ly.warning(_("Senza-misura time signatures are not yet supported!")) + return(4, 4) else: signature = [] current_sig = [] - for i in mxl.get_all_children (): - if isinstance (i, Beats): - beats = string.split (i.get_text ().strip (), "+") - current_sig = [int (j) for j in beats] - elif isinstance (i, BeatType): - current_sig.append (int (i.get_text ())) - signature.append (current_sig) + for i in mxl.get_all_children(): + if isinstance(i, Beats): + beats = string.split(i.get_text().strip(), "+") + current_sig = [int(j) for j in beats] + elif isinstance(i, BeatType): + current_sig.append(int(i.get_text())) + signature.append(current_sig) current_sig = [] - if isinstance (signature[0], list) and len (signature) == 1: + if isinstance(signature[0], list) and len(signature) == 1: signature = signature[0] self._time_signature_cache = signature return signature - except (KeyError, ValueError): - self.message (_ ("Unable to interpret time signature! Falling back to 4/4.")) - return (4, 4) + except(KeyError, ValueError): + self.message(_("Unable to interpret time signature! Falling back to 4/4.")) + return(4, 4) - # returns clef information in the form ("cleftype", position, octave-shift) - def get_clef_information (self): + # returns clef information in the form("cleftype", position, octave-shift) + def get_clef_information(self): clefinfo = ['G', 2, 0] - mxl = self.get_named_attribute ('clef') + mxl = self.get_named_attribute('clef') if not mxl: return clefinfo - sign = mxl.get_maybe_exist_named_child ('sign') + sign = mxl.get_maybe_exist_named_child('sign') if sign: clefinfo[0] = sign.get_text() - line = mxl.get_maybe_exist_named_child ('line') + line = mxl.get_maybe_exist_named_child('line') if line: - clefinfo[1] = string.atoi (line.get_text ()) - octave = mxl.get_maybe_exist_named_child ('clef-octave-change') + clefinfo[1] = int(line.get_text()) + octave = mxl.get_maybe_exist_named_child('clef-octave-change') if octave: - clefinfo[2] = string.atoi (octave.get_text ()) + clefinfo[2] = int(octave.get_text()) return clefinfo - def get_key_signature (self): - "return (fifths, mode) tuple if the key signatures is given as " + def get_key_signature(self): + "return(fifths, mode) tuple if the key signatures is given as " "major/minor in the Circle of fifths. Otherwise return an alterations" "list of the form [[step,alter<,octave>], [step,alter<,octave>], ...], " "where the octave values are optional." - key = self.get_named_attribute ('key') + key = self.get_named_attribute('key') if not key: return None - fifths_elm = key.get_maybe_exist_named_child ('fifths') + fifths_elm = key.get_maybe_exist_named_child('fifths') if fifths_elm: - mode_node = key.get_maybe_exist_named_child ('mode') + mode_node = key.get_maybe_exist_named_child('mode') mode = None if mode_node: - mode = mode_node.get_text () + mode = mode_node.get_text() if not mode or mode == '': mode = 'major' - fifths = int (fifths_elm.get_text ()) + fifths = int(fifths_elm.get_text()) # TODO: Shall we try to convert the key-octave and the cancel, too? - return (fifths, mode) + return(fifths, mode) else: alterations = [] current_step = 0 - for i in key.get_all_children (): - if isinstance (i, KeyStep): - current_step = i.get_text ().strip () - elif isinstance (i, KeyAlter): - alterations.append ([current_step, interpret_alter_element (i)]) - elif isinstance (i, KeyOctave): + for i in key.get_all_children(): + if isinstance(i, KeyStep): + current_step = i.get_text().strip() + elif isinstance(i, KeyAlter): + alterations.append([current_step, utilities.interpret_alter_element(i)]) + elif isinstance(i, KeyOctave): nr = -1 - if hasattr (i, 'number'): - nr = int (i.number) - if (nr > 0) and (nr <= len (alterations)): + if hasattr(i, 'number'): + nr = int(i.number) + if(nr > 0) and(nr <= len(alterations)): # MusicXML Octave 4 is middle C -> shift to 0 - alterations[nr-1].append (int (i.get_text ())-4) + alterations[nr - 1].append(int(i.get_text()) - 4) else: - i.message (_ ("Key alteration octave given for a " - "non-existing alteration nr. %s, available numbers: %s!") % (nr, len(alterations))) + i.message(_("Key alteration octave given for a " + "non-existing alteration nr. %s, available numbers: %s!") %(nr, len(alterations))) return alterations - def get_transposition (self): - return self.get_named_attribute ('transpose') + def get_transposition(self): + return self.get_named_attribute('transpose') + + +class Barline(Measure_element): + + def to_lily_object(self): + # retval contains all possible markers in the order: + # 0..bw_ending, 1..bw_repeat, 2..barline, 3..fw_repeat, 4..fw_ending + retval = {} + bartype_element = self.get_maybe_exist_named_child("bar-style") + repeat_element = self.get_maybe_exist_named_child("repeat") + ending_element = self.get_maybe_exist_named_child("ending") + + bartype = None + if bartype_element: + bartype = bartype_element.get_text() + + if repeat_element and hasattr(repeat_element, 'direction'): + repeat = musicxml2ly_conversion.RepeatMarker() + repeat.direction = {"forward":-1, "backward": 1}.get( + repeat_element.direction, 0) + + if((repeat_element.direction == "forward" and bartype == "heavy-light") or + (repeat_element.direction == "backward" and bartype == "light-heavy")): + bartype = None + if hasattr(repeat_element, 'times'): + try: + repeat.times = int(repeat_element.times) + except ValueError: + repeat.times = 2 + repeat.event = self + if repeat.direction == -1: + retval[3] = repeat + else: + retval[1] = repeat + + if ending_element and hasattr(ending_element, 'type'): + ending = musicxml2ly_conversion.EndingMarker() + ending.direction = {"start":-1, "stop": 1, "discontinue": 1}.get( + ending_element.type, 0) + ending.event = self + if ending.direction == -1: + retval[4] = ending + else: + retval[0] = ending + # TODO. ending number="" -class KeyAlter (Music_xml_node): - pass -class KeyStep (Music_xml_node): - pass -class KeyOctave (Music_xml_node): - pass + if bartype: + b = musicexp.BarLine() + b.type = bartype + retval[2] = b + return retval.values() -class Barline (Measure_element): - pass -class BarStyle (Music_xml_node): - pass -class Partial (Measure_element): - def __init__ (self, partial): - Measure_element.__init__ (self) + +class Partial(Measure_element): + def __init__(self, partial): + Measure_element.__init__(self) self.partial = partial -class Note (Measure_element): - def __init__ (self): - Measure_element.__init__ (self) + +class Stem(Music_xml_node): + + stem_value_dict = { + 'down': 'stemDown', + 'up': 'stemUp', + 'double': None, # TODO: Implement + 'none': 'stemNeutral' + } + + def to_stem_event(self): + values = [] + value = self.stem_value_dict.get(self.get_text(), None) + stem_value = musicexp.StemEvent() + if value: + stem_value.value = value + values.append(stem_value) + return values + + def to_stem_style_event(self): + styles = [] + style_elm = musicexp.StemstyleEvent() + if hasattr(self, 'color'): + style_elm.color = utilities.hex_to_color(getattr(self, 'color')) + if(style_elm.color != None): + styles.append(style_elm) + return styles + + +class Notehead(Music_xml_node): + + notehead_styles_dict = { + 'slash': '\'slash', + 'triangle': '\'triangle', + 'diamond': '\'diamond', + 'square': '\'la', # TODO: Proper squared note head + 'cross': None, # TODO: + shaped note head + 'x': '\'cross', + 'circle-x': '\'xcircle', + 'inverted triangle': None, # TODO: Implement + 'arrow down': None, # TODO: Implement + 'arrow up': None, # TODO: Implement + 'slashed': None, # TODO: Implement + 'back slashed': None, # TODO: Implement + 'normal': None, + 'cluster': None, # TODO: Implement + 'none': '#f', + 'do': '\'do', + 're': '\'re', + 'mi': '\'mi', + 'fa': '\'fa', + 'so': None, + 'la': '\'la', + 'ti': '\'ti', + } + + def to_lily_object(self): #function changed: additionally processcolor attribute + styles = [] + + # Notehead style + key = self.get_text().strip() + style = self.notehead_styles_dict.get(key, None) + event = musicexp.NotestyleEvent() + if style: + event.style = style + if hasattr(self, 'filled'): + event.filled =(getattr(self, 'filled') == "yes") + if hasattr(self, 'color'): + event.color = utilities.hex_to_color(getattr(self, 'color')) + if event.style or(event.filled != None) or(event.color != None): + styles.append(event) + # parentheses + if hasattr(self, 'parentheses') and(self.parentheses == "yes"): + styles.append(musicexp.ParenthesizeEvent()) + + return styles + + +class Note(Measure_element): + + drumtype_dict = { + 'Acoustic Snare Drum': 'acousticsnare', + 'Side Stick': 'sidestick', + 'Open Triangle': 'opentriangle', + 'Mute Triangle': 'mutetriangle', + 'Tambourine': 'tambourine', + 'Bass Drum': 'bassdrum', + } + + def __init__(self): + Measure_element.__init__(self) self.instrument_name = '' self._after_grace = False - def is_grace (self): - return self.get_maybe_exist_named_child (u'grace') - def is_after_grace (self): + self._duration = 1 + + def is_grace(self): + return self.get_maybe_exist_named_child(u'grace') + + def is_after_grace(self): if not self.is_grace(): return False; - gr = self.get_maybe_exist_typed_child (Grace) - return self._after_grace or hasattr (gr, 'steal-time-previous'); + gr = self.get_maybe_exist_typed_child(Grace) + return self._after_grace or hasattr(gr, 'steal-time-previous'); - def get_duration_log (self): - ch = self.get_maybe_exist_named_child (u'type') + def get_duration_log(self): + ch = self.get_maybe_exist_named_child(u'type') if ch: - log = ch.get_text ().strip() - return musicxml_duration_to_log (log) - elif self.get_maybe_exist_named_child (u'grace'): + log = ch.get_text().strip() + return utilities.musicxml_duration_to_log(log) + elif self.get_maybe_exist_named_child(u'grace'): # FIXME: is it ok to default to eight note for grace notes? return 3 else: return None - def get_duration_info (self): - log = self.get_duration_log () + def get_duration_info(self): + log = self.get_duration_log() if log != None: - dots = len (self.get_typed_children (Dot)) - return (log, dots) + dots = len(self.get_typed_children(Dot)) + return(log, dots) else: return None - def get_factor (self): + def get_factor(self): return 1 - def get_pitches (self): - return self.get_typed_children (get_class (u'pitch')) + def get_pitches(self): + return self.get_typed_children(get_class(u'pitch')) + + def set_notehead_style(self, event): + noteheads = self.get_named_children('notehead') + for nh in noteheads: + styles = nh.to_lily_object() + for style in styles: + event.add_associated_event(style) + + def set_stem_directions(self, event): + stems = self.get_named_children('stem') + for stem in stems: + values = stem.to_stem_event() + for v in values: + event.add_associated_event(v) + + def set_stem_style(self, event): + stems = self.get_named_children('stem') + for stem in stems: + styles = stem.to_stem_style_event() + for style in styles: + event.add_associated_event(style) + + def initialize_duration(self): + from musicxml2ly_conversion import rational_to_lily_duration + from musicexp import Duration + # if the note has no Type child, then that method returns None. In that case, + # use the tag instead. If that doesn't exist, either -> Error + dur = self.get_duration_info() + if dur: + d = Duration() + d.duration_log = dur[0] + d.dots = dur[1] + # 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 self.get_maybe_exist_typed_child(Grace): + d.factor = self._duration / d.get_length() + return d + else: + if self._duration > 0: + return rational_to_lily_duration(self._duration) + else: + self.message( + _("Encountered note at %s without type and duration(=%s)") \ + %(mxl_note.start, mxl_note._duration)) + return None + + def initialize_pitched_event(self): + mxl_pitch = self.get_maybe_exist_typed_child(Pitch) + pitch = mxl_pitch.to_lily_object() + event = musicexp.NoteEvent() + event.pitch = pitch + acc = self.get_maybe_exist_named_child('accidental') + if acc: + # let's not force accs everywhere. + event.cautionary = acc.cautionary + # TODO: Handle editorial accidentals + # TODO: Handle the level-display setting for displaying brackets/parentheses + return event + + def initialize_unpitched_event(self): + # Unpitched elements have display-step and can also have + # display-octave. + unpitched = self.get_maybe_exist_typed_child(Unpitched) + event = musicexp.NoteEvent() + event.pitch = unpitched.to_lily_object() + return event + + def initialize_rest_event(self, convert_rest_positions=True): + # rests can have display-octave and display-step, which are + # treated like an ordinary note pitch + rest = self.get_maybe_exist_typed_child(Rest) + event = musicexp.RestEvent() + if convert_rest_positions: + pitch = rest.to_lily_object() + event.pitch = pitch + return event + + def initialize_drum_event(self): + event = musicexp.NoteEvent() + drum_type = self.drumtype_dict.get(self.instrument_name) + if drum_type: + event.drum_type = drum_type + else: + self.message( + _("drum %s type unknown, please add to instrument_drumtype_dict") + % n.instrument_name) + event.drum_type = 'acousticsnare' + return event + + def to_lily_object(self, + convert_stem_directions=True, + convert_rest_positions=True): + pitch = None + duration = None + event = None + + if self.get_maybe_exist_typed_child(Pitch): + event = self.initialize_pitched_event() + elif self.get_maybe_exist_typed_child(Unpitched): + event = self.initialize_unpitched_event() + elif self.get_maybe_exist_typed_child(Rest): + event = self.initialize_rest_event(convert_rest_positions) + elif self.instrument_name: + event = self.initialize_drum_event() + else: + self.message(_("cannot find suitable event")) + + if event: + event.duration = self.initialize_duration() + + self.set_notehead_style(event) + self.set_stem_style(event) + if convert_stem_directions: + self.set_stem_directions(event) -class Part_list (Music_xml_node): - def __init__ (self): - Music_xml_node.__init__ (self) + return event + + +class Part_list(Music_xml_node): + + def __init__(self): + Music_xml_node.__init__(self) self._id_instrument_name_dict = {} - def generate_id_instrument_dict (self): + def generate_id_instrument_dict(self): ## not empty to make sure this happens only once. mapping = {1: 1} - for score_part in self.get_named_children ('score-part'): - for instr in score_part.get_named_children ('score-instrument'): + for score_part in self.get_named_children('score-part'): + for instr in score_part.get_named_children('score-instrument'): id = instr.id - name = instr.get_named_child ("instrument-name") - mapping[id] = name.get_text () + name = instr.get_named_child("instrument-name") + mapping[id] = name.get_text() self._id_instrument_name_dict = mapping - def get_instrument (self, id): + def get_instrument(self, id): if not self._id_instrument_name_dict: self.generate_id_instrument_dict() - instrument_name = self._id_instrument_name_dict.get (id) + instrument_name = self._id_instrument_name_dict.get(id) if instrument_name: return instrument_name else: - ly.warning (_ ("Unable to find instrument for ID=%s\n") % id) + ly.warning(_("Unable to find instrument for ID=%s\n") % id) return "Grand Piano" -class Part_group (Music_xml_node): - pass -class Score_part (Music_xml_node): - pass -class Measure (Music_xml_node): - def __init__ (self): - Music_xml_node.__init__ (self) +class Measure(Music_xml_node): + + def __init__(self): + Music_xml_node.__init__(self) self.partial = 0 - def is_implicit (self): - return hasattr (self, 'implicit') and self.implicit == 'yes' - def get_notes (self): - return self.get_typed_children (get_class (u'note')) -class Syllabic (Music_xml_node): - def continued (self): + def is_implicit(self): + return hasattr(self, 'implicit') and self.implicit == 'yes' + + def get_notes(self): + return self.get_typed_children(get_class(u'note')) + + +class Syllabic(Music_xml_node): + + def continued(self): text = self.get_text() - return (text == "begin") or (text == "middle") -class Elision (Music_xml_node): - pass -class Extend (Music_xml_node): - pass -class Text (Music_xml_node): - pass + return(text == "begin") or(text == "middle") + + def begin(self): + return(text == "begin") + + def middle(self): + return(text == "middle") -class Lyric (Music_xml_node): - def get_number (self): - if hasattr (self, 'number'): + def end(self): + return(text == "end") + + +class Lyric(Music_xml_node): + + def number(self): + """ + Return the number attribute(if it exists) of the lyric element. + + @rtype: number + @return: The value of the number attribute + """ + if hasattr(self, 'number'): return self.number else: return -1 + +class Sound(Music_xml_node): + + def get_tempo(self): + """ + Return the tempo attribute(if it exists) of the sound element. + This attribute can be used by musicxml2ly for the midi output(see L{musicexp.Score}). + + @rtype: string + @return: The value of the tempo attribute + """ + if hasattr(self, 'tempo'): + return self.tempo + else: + return None + + +class Notations(Music_xml_node): + + def get_tie(self): + ts = self.get_named_children('tied') + starts = [t for t in ts if t.type == 'start'] + if starts: + return starts[0] + else: + return None + + def get_tuplets(self): + return self.get_typed_children(Tuplet) + + +class Time_modification(Music_xml_node): + + def get_fraction(self): + b = self.get_maybe_exist_named_child('actual-notes') + a = self.get_maybe_exist_named_child('normal-notes') + return(int(a.get_text()), int(b.get_text())) + + def get_normal_type(self): + tuplet_type = self.get_maybe_exist_named_child('normal-type') + if tuplet_type: + dots = self.get_named_children('normal-dot') + log = utilities.musicxml_duration_to_log(tuplet_type.get_text().strip()) + return(log , len(dots)) + else: + return None + + +class Accidental(Music_xml_node): + + def __init__(self): + Music_xml_node.__init__(self) + self.editorial = False + self.cautionary = False + + +class Tuplet(Music_xml_spanner): + + def duration_info_from_tuplet_note(self, tuplet_note): + tuplet_type = tuplet_note.get_maybe_exist_named_child('tuplet-type') + if tuplet_type: + dots = tuplet_note.get_named_children('tuplet-dot') + log = utilities.musicxml_duration_to_log(tuplet_type.get_text().strip()) + return(log, len(dots)) + else: + return None + + # Return tuplet note type as(log, dots) + def get_normal_type(self): + tuplet = self.get_maybe_exist_named_child('tuplet-normal') + if tuplet: + return self.duration_info_from_tuplet_note(tuplet) + else: + return None + + def get_actual_type(self): + tuplet = self.get_maybe_exist_named_child('tuplet-actual') + if tuplet: + return self.duration_info_from_tuplet_note(tuplet) + else: + return None + + def get_tuplet_note_count(self, tuplet_note): + if tuplet_note: + tuplet_nr = tuplet_note.get_maybe_exist_named_child('tuplet-number') + if tuplet_nr: + return int(tuplet_nr.get_text()) + return None + + def get_normal_nr(self): + return self.get_tuplet_note_count(self.get_maybe_exist_named_child('tuplet-normal')) + + def get_actual_nr(self): + return self.get_tuplet_note_count(self.get_maybe_exist_named_child('tuplet-actual')) + + +class Slur(Music_xml_spanner): + + def get_type(self): + return self.type + + +class Tied(Music_xml_spanner): + + def get_type(self): + return self.type + + +class Beam(Music_xml_spanner): + def get_type(self): + return self.get_text() + def is_primary(self): + if hasattr(self, 'number'): + return self.number == "1" + else: + return True + +class Octave_shift(Music_xml_spanner): + # default is 8 for the octave-shift! + def get_size(self): + if hasattr(self, 'size'): + return int(self.size) + else: + return 8 + + +# Rests in MusicXML are blocks with a inside. This class is only +# for the inner element, not the whole rest block. +class Rest(Music_xml_node): + + def __init__(self): + Music_xml_node.__init__(self) + self._is_whole_measure = False + + def is_whole_measure(self): + return self._is_whole_measure + + def get_step(self): + ch = self.get_maybe_exist_typed_child(get_class(u'display-step')) + if ch: + return ch.get_text().strip() + else: + return None + + def get_octave(self): + ch = self.get_maybe_exist_typed_child(get_class(u'display-octave')) + if ch: + oct = ch.get_text().strip() + return int(oct) + else: + return None + + def to_lily_object(self): + p = None + step = self.get_step() + if step: + p = musicexp.Pitch() + p.step = musicxml2ly_conversion.musicxml_step_to_lily(step) + octave = self.get_octave() + if octave and p: + p.octave = octave - 4 + return p + + +class Bend(Music_xml_node): + + def bend_alter(self): + alter = self.get_maybe_exist_named_child('bend-alter') + return utilities.interpret_alter_element(alter) + + +class ChordPitch(Music_xml_node): + + def step_class_name(self): + return u'root-step' + + def alter_class_name(self): + return u'root-alter' + + def get_step(self): + ch = self.get_unique_typed_child(get_class(self.step_class_name())) + return ch.get_text().strip() + + def get_alteration(self): + ch = self.get_maybe_exist_typed_child(get_class(self.alter_class_name())) + return utilities.interpret_alter_element(ch) + + +class Bass(ChordPitch): + + def step_class_name(self): + return u'bass-step' + + def alter_class_name(self): + return u'bass-alter' + + +class ChordModification(Music_xml_node): + + def get_type(self): + ch = self.get_maybe_exist_typed_child(get_class(u'degree-type')) + return {'add': 1, 'alter': 1, 'subtract':-1}.get(ch.get_text().strip(), 0) + + def get_value(self): + ch = self.get_maybe_exist_typed_child(get_class(u'degree-value')) + value = 0 + if ch: + value = int(ch.get_text().strip()) + return value + + def get_alter(self): + ch = self.get_maybe_exist_typed_child(get_class(u'degree-alter')) + return utilities.interpret_alter_element(ch) + + +class Frame(Music_xml_node): + + def get_frets(self): + return self.get_named_child_value_number('frame-frets', 4) + + def get_strings(self): + return self.get_named_child_value_number('frame-strings', 6) + + def get_first_fret(self): + return self.get_named_child_value_number('first-fret', 1) + + +class Frame_Note(Music_xml_node): + + def get_string(self): + return self.get_named_child_value_number('string', 1) + + def get_fret(self): + return self.get_named_child_value_number('fret', 0) + + def get_fingering(self): + return self.get_named_child_value_number('fingering', -1) + + def get_barre(self): + n = self.get_maybe_exist_named_child('barre') + if n: + return getattr(n, 'type', '') + else: + return '' + + class Musicxml_voice: - def __init__ (self): + + def __init__(self): self._elements = [] self._staves = {} self._start_staff = None self._lyrics = [] self._has_lyrics = False - def add_element (self, e): - self._elements.append (e) - if (isinstance (e, Note) - and e.get_maybe_exist_typed_child (Staff)): - name = e.get_maybe_exist_typed_child (Staff).get_text () + def add_element(self, e): + self._elements.append(e) + if(isinstance(e, Note) + and e.get_maybe_exist_typed_child(Staff)): + name = e.get_maybe_exist_typed_child(Staff).get_text() - if not self._start_staff and not e.get_maybe_exist_typed_child (Grace): + if not self._start_staff and not e.get_maybe_exist_typed_child(Grace): self._start_staff = name self._staves[name] = True - lyrics = e.get_typed_children (Lyric) + lyrics = e.get_typed_children(Lyric) if not self._has_lyrics: - self.has_lyrics = len (lyrics) > 0 + self.has_lyrics = len(lyrics) > 0 for l in lyrics: - nr = l.get_number() - if (nr > 0) and not (nr in self._lyrics): - self._lyrics.append (nr) + nr = l.number + if(nr > 0) and not(nr in self._lyrics): + self._lyrics.append(nr) - def insert (self, idx, e): - self._elements.insert (idx, e) + def insert(self, idx, e): + self._elements.insert(idx, e) - def get_lyrics_numbers (self): - if (len (self._lyrics) == 0) and self._has_lyrics: + def get_lyrics_numbers(self): + if(len(self._lyrics) == 0) and self._has_lyrics: #only happens if none of the tags has a number attribute return [1] else: return self._lyrics -def graces_to_aftergraces (pending_graces): - for gr in pending_graces: - gr._when = gr._prev_when - gr._measure_position = gr._prev_measure_position - gr._after_grace = True - +class Part(Music_xml_node): -class Part (Music_xml_node): - def __init__ (self): - Music_xml_node.__init__ (self) + def __init__(self): + Music_xml_node.__init__(self) self._voices = {} self._staff_attributes_dict = {} - def get_part_list (self): + def get_part_list(self): n = self while n and n.get_name() != 'score-partwise': n = n._parent - return n.get_named_child ('part-list') + return n.get_named_child('part-list') - def interpret (self): + def graces_to_aftergraces(self, pending_graces): + for gr in pending_graces: + gr._when = gr._prev_when + gr._measure_position = gr._prev_measure_position + gr._after_grace = True + + def interpret(self): """Set durations and starting points.""" """The starting point of the very first note is 0!""" - part_list = self.get_part_list () + part_list = self.get_part_list() - now = Rational (0) - factor = Rational (1) + now = Rational(0) + factor = Rational(1) attributes_dict = {} attributes_object = None - measures = self.get_typed_children (Measure) - last_moment = Rational (-1) - last_measure_position = Rational (-1) - measure_position = Rational (0) + measures = self.get_typed_children(Measure) + last_moment = Rational(-1) + last_measure_position = Rational(-1) + measure_position = Rational(0) measure_start_moment = now is_first_measure = True previous_measure = None @@ -638,14 +1300,14 @@ class Part (Music_xml_node): # implicit measures are used for artificial measures, e.g. when # a repeat bar line splits a bar into two halves. In this case, # don't reset the measure position to 0. They are also used for - # upbeats (initial value of 0 fits these, too). + # upbeats(initial value of 0 fits these, too). # Also, don't reset the measure position at the end of the loop, - # but rather when starting the next measure (since only then do we + # but rather when starting the next measure(since only then do we # know if the next measure is implicit and continues that measure) - if not m.is_implicit (): + if not m.is_implicit(): # Warn about possibly overfull measures and reset the position if attributes_object and previous_measure and previous_measure.partial == 0: - length = attributes_object.get_measure_length () + length = attributes_object.get_measure_length() new_now = measure_start_moment + length if now <> new_now: problem = 'incomplete' @@ -653,10 +1315,10 @@ class Part (Music_xml_node): problem = 'overfull' ## only for verbose operation. if problem <> 'incomplete' and previous_measure: - previous_measure.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now)) + previous_measure.message('%s measure? Expected: %s, Difference: %s' %(problem, now, new_now - now)) now = new_now measure_start_moment = now - measure_position = Rational (0) + measure_position = Rational(0) voice_id = None; assign_to_next_voice = [] @@ -679,47 +1341,47 @@ class Part (Music_xml_node): # figured bass has a duration, but applies to the next note # and should not change the current measure position! - if isinstance (n, FiguredBass): - n._divisions = factor.denominator () + if isinstance(n, FiguredBass): + n._divisions = factor.denominator() n._when = now n._measure_position = measure_position continue - if isinstance (n, Hash_text): + if isinstance(n, Hash_text): continue - dur = Rational (0) + dur = Rational(0) if n.__class__ == Attributes: - n.set_attributes_from_previous (attributes_dict) - n.read_self () - attributes_dict = n._dict.copy () + n.set_attributes_from_previous(attributes_dict) + n.read_self() + attributes_dict = n._dict.copy() attributes_object = n - factor = Rational (1, - int (attributes_dict.get ('divisions').get_text ())) + factor = Rational(1, + int(attributes_dict.get('divisions').get_text())) - if (n.get_maybe_exist_typed_child (Duration)): - mxl_dur = n.get_maybe_exist_typed_child (Duration) - dur = mxl_dur.get_length () * factor + if(n.get_maybe_exist_typed_child(Duration)): + mxl_dur = n.get_maybe_exist_typed_child(Duration) + dur = mxl_dur.get_length() * factor if n.get_name() == 'backup': - dur = - dur + dur = -dur # reset all graces before the backup to after-graces: - graces_to_aftergraces (pending_graces) + self.graces_to_aftergraces(pending_graces) pending_graces = [] - if n.get_maybe_exist_typed_child (Grace): - dur = Rational (0) + if n.get_maybe_exist_typed_child(Grace): + dur = Rational(0) - rest = n.get_maybe_exist_typed_child (Rest) - if (rest + rest = n.get_maybe_exist_typed_child(Rest) + if(rest and attributes_object - and attributes_object.get_measure_length () == dur): + and attributes_object.get_measure_length() == dur): rest._is_whole_measure = True - if (dur > Rational (0) - and n.get_maybe_exist_typed_child (Chord)): + if(dur > Rational(0) + and n.get_maybe_exist_typed_child(Chord)): now = last_moment measure_position = last_measure_position @@ -728,27 +1390,27 @@ class Part (Music_xml_node): # For all grace notes, store the previous note, in case need # to turn the grace note into an after-grace later on! - if isinstance(n, Note) and n.is_grace (): + if isinstance(n, Note) and n.is_grace(): n._prev_when = last_moment n._prev_measure_position = last_measure_position # After-graces are placed at the same position as the previous note - if isinstance(n, Note) and n.is_after_grace (): + if isinstance(n, Note) and n.is_after_grace(): # TODO: We should do the same for grace notes at the end of # a measure with no following note!!! n._when = last_moment n._measure_position = last_measure_position - elif isinstance(n, Note) and n.is_grace (): - pending_graces.append (n) - elif (dur > Rational (0)): + elif isinstance(n, Note) and n.is_grace(): + pending_graces.append(n) + elif(dur > Rational(0)): pending_graces = []; n._duration = dur - if dur > Rational (0): + if dur > Rational(0): last_moment = now last_measure_position = measure_position now += dur measure_position += dur - elif dur < Rational (0): + elif dur < Rational(0): # backup element, reset measure position now += dur measure_position += dur @@ -759,425 +1421,297 @@ class Part (Music_xml_node): last_moment = now last_measure_position = measure_position if n._name == 'note': - instrument = n.get_maybe_exist_named_child ('instrument') + instrument = n.get_maybe_exist_named_child('instrument') if instrument: - n.instrument_name = part_list.get_instrument (instrument.id) + n.instrument_name = part_list.get_instrument(instrument.id) # reset all graces at the end of the measure to after-graces: - graces_to_aftergraces (pending_graces) + self.graces_to_aftergraces(pending_graces) pending_graces = [] # Incomplete first measures are not padded, but registered as partial if is_first_measure: is_first_measure = False # upbeats are marked as implicit measures - if attributes_object and m.is_implicit (): - length = attributes_object.get_measure_length () + if attributes_object and m.is_implicit(): + length = attributes_object.get_measure_length() measure_end = measure_start_moment + length if measure_end <> now: m.partial = now previous_measure = m # modify attributes so that only those applying to the given staff remain - def extract_attributes_for_staff (part, attr, staff): - attributes = copy.copy (attr) + def extract_attributes_for_staff(part, attr, staff): + attributes = copy.copy(attr) attributes._children = []; - attributes._dict = attr._dict.copy () + attributes._dict = attr._dict.copy() attributes._original_tag = attr # copy only the relevant children over for the given staff if staff == "None": staff = "1" for c in attr._children: - if (not (hasattr (c, 'number') and (c.number != staff)) and - not (isinstance (c, Hash_text))): - attributes._children.append (c) + if(not(hasattr(c, 'number') and(c.number != staff)) and + not(isinstance(c, Hash_text))): + attributes._children.append(c) if not attributes._children: return None else: return attributes - def extract_voices (part): + def extract_voices(part): + # The last indentified voice + last_voice = None + voices = {} - measures = part.get_typed_children (Measure) + measures = part.get_typed_children(Measure) elements = [] for m in measures: if m.partial > 0: - elements.append (Partial (m.partial)) - elements.extend (m.get_all_children ()) + elements.append(Partial(m.partial)) + elements.extend(m.get_all_children()) # make sure we know all voices already so that dynamics, clefs, etc. # can be assigned to the correct voices voice_to_staff_dict = {} for n in elements: - voice_id = n.get_maybe_exist_named_child (u'voice') + voice_id = n.get_maybe_exist_named_child(u'voice') vid = None if voice_id: - vid = voice_id.get_text () - elif isinstance (n, Note): + vid = voice_id.get_text() + elif isinstance(n, Note): # TODO: Check whether we shall really use "None" here, or # rather use "1" as the default? - vid = "None" + if n.get_maybe_exist_named_child(u'chord'): + vid = last_voice + else: + vid = "1" + + if(vid != None): + last_voice = vid - staff_id = n.get_maybe_exist_named_child (u'staff') + staff_id = n.get_maybe_exist_named_child(u'staff') sid = None if staff_id: - sid = staff_id.get_text () + sid = staff_id.get_text() else: # TODO: Check whether we shall really use "None" here, or # rather use "1" as the default? # If this is changed, need to change the corresponding # check in extract_attributes_for_staff, too. sid = "None" - if vid and not voices.has_key (vid): + if vid and not voices.has_key(vid): voices[vid] = Musicxml_voice() - if vid and sid and not n.get_maybe_exist_typed_child (Grace): - if not voice_to_staff_dict.has_key (vid): + if vid and sid and not n.get_maybe_exist_typed_child(Grace): + if not voice_to_staff_dict.has_key(vid): voice_to_staff_dict[vid] = sid - # invert the voice_to_staff_dict into a staff_to_voice_dict (since we + + # invert the voice_to_staff_dict into a staff_to_voice_dict(since we # need to assign staff-assigned objects like clefs, times, etc. to # all the correct voices. This will never work entirely correct due # to staff-switches, but that's the best we can do! staff_to_voice_dict = {} - for (v,s) in voice_to_staff_dict.items (): - if not staff_to_voice_dict.has_key (s): + for(v, s) in voice_to_staff_dict.items(): + if not staff_to_voice_dict.has_key(s): staff_to_voice_dict[s] = [v] else: - staff_to_voice_dict[s].append (v) - + staff_to_voice_dict[s].append(v) start_attr = None assign_to_next_note = [] id = None for n in elements: - voice_id = n.get_maybe_exist_typed_child (get_class ('voice')) + voice_id = n.get_maybe_exist_typed_child(get_class('voice')) if voice_id: - id = voice_id.get_text () + id = voice_id.get_text() else: - id = "None" + if n.get_maybe_exist_typed_child(get_class('chord')): + id = last_voice + else: + id = "1" + + if(id != "None"): + last_voice = id # We don't need backup/forward any more, since we have already # assigned the correct onset times. # TODO: Let Grouping through. Also: link, print, bokmark sound - if not (isinstance (n, Note) or isinstance (n, Attributes) or - isinstance (n, Direction) or isinstance (n, Partial) or - isinstance (n, Barline) or isinstance (n, Harmony) or - isinstance (n, FiguredBass) or isinstance (n, Print)): + if not(isinstance(n, Note) or isinstance(n, Attributes) or + isinstance(n, Direction) or isinstance(n, Partial) or + isinstance(n, Barline) or isinstance(n, Harmony) or + isinstance(n, FiguredBass) or isinstance(n, Print)): continue - if isinstance (n, Attributes) and not start_attr: + if isinstance(n, Attributes) and not start_attr: start_attr = n continue - if isinstance (n, Attributes): + if isinstance(n, Attributes): # assign these only to the voices they really belong to! - for (s, vids) in staff_to_voice_dict.items (): - staff_attributes = part.extract_attributes_for_staff (n, s) + for(s, vids) in staff_to_voice_dict.items(): + staff_attributes = part.extract_attributes_for_staff(n, s) if staff_attributes: for v in vids: - voices[v].add_element (staff_attributes) + voices[v].add_element(staff_attributes) continue - if isinstance (n, Partial) or isinstance (n, Barline) or isinstance (n, Print): - for v in voices.keys (): - voices[v].add_element (n) + if isinstance(n, Partial) or isinstance(n, Barline) or isinstance(n, Print): + for v in voices.keys(): + voices[v].add_element(n) continue - if isinstance (n, Direction): - if (n.voice_id): - voices[n.voice_id].add_element (n) + if isinstance(n, Direction): + staff_id = n.get_maybe_exist_named_child(u'staff') + if staff_id: + staff_id = staff_id.get_text() + if staff_id: + dir_voices = staff_to_voice_dict.get(staff_id, voices.keys()) else: - assign_to_next_note.append (n) + dir_voices = voices.keys() + for v in dir_voices: + voices[v].add_element(n) continue - if isinstance (n, Harmony) or isinstance (n, FiguredBass): + if isinstance(n, Harmony) or isinstance(n, FiguredBass): # store the harmony or figured bass element until we encounter # the next note and assign it only to that one voice. - assign_to_next_note.append (n) + assign_to_next_note.append(n) continue - if hasattr (n, 'print-object') and getattr (n, 'print-object') == "no": + if hasattr(n, 'print-object') and getattr(n, 'print-object') == "no": #Skip this note. pass else: for i in assign_to_next_note: - voices[id].add_element (i) + voices[id].add_element(i) assign_to_next_note = [] - voices[id].add_element (n) + voices[id].add_element(n) # Assign all remaining elements from assign_to_next_note to the voice # of the previous note: for i in assign_to_next_note: - voices[id].add_element (i) + voices[id].add_element(i) assign_to_next_note = [] if start_attr: - for (s, vids) in staff_to_voice_dict.items (): - staff_attributes = part.extract_attributes_for_staff (start_attr, s) - staff_attributes.read_self () + for(s, vids) in staff_to_voice_dict.items(): + staff_attributes = part.extract_attributes_for_staff(start_attr, s) + staff_attributes.read_self() part._staff_attributes_dict[s] = staff_attributes for v in vids: - voices[v].insert (0, staff_attributes) + voices[v].insert(0, staff_attributes) voices[v]._elements[0].read_self() part._voices = voices - def get_voices (self): + def get_voices(self): return self._voices - def get_staff_attributes (self): - return self._staff_attributes_dict - -class Notations (Music_xml_node): - def get_tie (self): - ts = self.get_named_children ('tied') - starts = [t for t in ts if t.type == 'start'] - if starts: - return starts[0] - else: - return None - - def get_tuplets (self): - return self.get_typed_children (Tuplet) - -class Time_modification(Music_xml_node): - def get_fraction (self): - b = self.get_maybe_exist_named_child ('actual-notes') - a = self.get_maybe_exist_named_child ('normal-notes') - return (int(a.get_text ()), int (b.get_text ())) - - def get_normal_type (self): - tuplet_type = self.get_maybe_exist_named_child ('normal-type') - if tuplet_type: - dots = self.get_named_children ('normal-dot') - log = musicxml_duration_to_log (tuplet_type.get_text ().strip ()) - return (log , len (dots)) - else: - return None + def get_staff_attributes(self): + return self._staff_attributes_dict -class Accidental (Music_xml_node): - def __init__ (self): - Music_xml_node.__init__ (self) - self.editorial = False - self.cautionary = False - -class Music_xml_spanner (Music_xml_node): - def get_type (self): - if hasattr (self, 'type'): - return self.type - else: - return 0 - def get_size (self): - if hasattr (self, 'size'): - return string.atoi (self.size) - else: - return 0 -class Wedge (Music_xml_spanner): +class BarStyle(Music_xml_node): pass -class Tuplet (Music_xml_spanner): - def duration_info_from_tuplet_note (self, tuplet_note): - tuplet_type = tuplet_note.get_maybe_exist_named_child ('tuplet-type') - if tuplet_type: - dots = tuplet_note.get_named_children ('tuplet-dot') - log = musicxml_duration_to_log (tuplet_type.get_text ().strip ()) - return (log, len (dots)) - else: - return None - - # Return tuplet note type as (log, dots) - def get_normal_type (self): - tuplet = self.get_maybe_exist_named_child ('tuplet-normal') - if tuplet: - return self.duration_info_from_tuplet_note (tuplet) - else: - return None +class BeatType(Music_xml_node): + pass - def get_actual_type (self): - tuplet = self.get_maybe_exist_named_child ('tuplet-actual') - if tuplet: - return self.duration_info_from_tuplet_note (tuplet) - else: - return None +class BeatUnit(Music_xml_node): + pass - def get_tuplet_note_count (self, tuplet_note): - if tuplet_note: - tuplet_nr = tuplet_note.get_maybe_exist_named_child ('tuplet-number') - if tuplet_nr: - return int (tuplet_nr.get_text ()) - return None - def get_normal_nr (self): - return self.get_tuplet_note_count (self.get_maybe_exist_named_child ('tuplet-normal')) - def get_actual_nr (self): - return self.get_tuplet_note_count (self.get_maybe_exist_named_child ('tuplet-actual')) +class BeatUnitDot(Music_xml_node): + pass -class Bracket (Music_xml_spanner): +class Beats(Music_xml_node): pass -class Dashes (Music_xml_spanner): +class Bracket(Music_xml_spanner): pass -class Slur (Music_xml_spanner): - def get_type (self): - return self.type +class Chord(Music_xml_node): + pass -class Beam (Music_xml_spanner): - def get_type (self): - return self.get_text () - def is_primary (self): - if hasattr (self, 'number'): - return self.number == "1" - else: - return True +class Dashes(Music_xml_spanner): + pass -class Wavy_line (Music_xml_spanner): +class DirType(Music_xml_node): pass -class Pedal (Music_xml_spanner): +class Direction(Music_xml_node): pass -class Glissando (Music_xml_spanner): +class Dot(Music_xml_node): pass -class Slide (Music_xml_spanner): +class Elision(Music_xml_node): pass -class Octave_shift (Music_xml_spanner): - # default is 8 for the octave-shift! - def get_size (self): - if hasattr (self, 'size'): - return string.atoi (self.size) - else: - return 8 +class Extend(Music_xml_node): + pass -class Chord (Music_xml_node): +class FiguredBass(Music_xml_node): pass -class Dot (Music_xml_node): +class Glissando(Music_xml_spanner): pass -# Rests in MusicXML are blocks with a inside. This class is only -# for the inner element, not the whole rest block. -class Rest (Music_xml_node): - def __init__ (self): - Music_xml_node.__init__ (self) - self._is_whole_measure = False - def is_whole_measure (self): - return self._is_whole_measure - def get_step (self): - ch = self.get_maybe_exist_typed_child (get_class (u'display-step')) - if ch: - return ch.get_text ().strip () - else: - return None - def get_octave (self): - ch = self.get_maybe_exist_typed_child (get_class (u'display-octave')) - if ch: - oct = ch.get_text ().strip () - return int (oct) - else: - return None +class Grace(Music_xml_node): + pass -class Type (Music_xml_node): +class Harmony(Music_xml_node): pass -class Grace (Music_xml_node): + +class Hash_comment(Music_xml_node): pass -class Staff (Music_xml_node): + +class KeyAlter(Music_xml_node): pass class Direction (Measure_element): pass -class DirType (Music_xml_node): + +class KeyOctave(Music_xml_node): pass -class Bend (Music_xml_node): - def bend_alter (self): - alter = self.get_maybe_exist_named_child ('bend-alter') - return interpret_alter_element (alter) +class KeyStep(Music_xml_node): + pass -class Words (Music_xml_node): +class Part_group(Music_xml_node): pass -class Harmony (Music_xml_node): +class Pedal(Music_xml_spanner): pass -class ChordPitch (Music_xml_node): - def step_class_name (self): - return u'root-step' - def alter_class_name (self): - return u'root-alter' - def get_step (self): - ch = self.get_unique_typed_child (get_class (self.step_class_name ())) - return ch.get_text ().strip () - def get_alteration (self): - ch = self.get_maybe_exist_typed_child (get_class (self.alter_class_name ())) - return interpret_alter_element (ch) - -class Root (ChordPitch): +class PerMinute(Music_xml_node): pass -class Bass (ChordPitch): - def step_class_name (self): - return u'bass-step' - def alter_class_name (self): - return u'bass-alter' +class Print(Music_xml_node): + pass -class ChordModification (Music_xml_node): - def get_type (self): - ch = self.get_maybe_exist_typed_child (get_class (u'degree-type')) - return {'add': 1, 'alter': 1, 'subtract': -1}.get (ch.get_text ().strip (), 0) - def get_value (self): - ch = self.get_maybe_exist_typed_child (get_class (u'degree-value')) - value = 0 - if ch: - value = int (ch.get_text ().strip ()) - return value - def get_alter (self): - ch = self.get_maybe_exist_typed_child (get_class (u'degree-alter')) - return interpret_alter_element (ch) - - -class Frame (Music_xml_node): - def get_frets (self): - return self.get_named_child_value_number ('frame-frets', 4) - def get_strings (self): - return self.get_named_child_value_number ('frame-strings', 6) - def get_first_fret (self): - return self.get_named_child_value_number ('first-fret', 1) - -class Frame_Note (Music_xml_node): - def get_string (self): - return self.get_named_child_value_number ('string', 1) - def get_fret (self): - return self.get_named_child_value_number ('fret', 0) - def get_fingering (self): - return self.get_named_child_value_number ('fingering', -1) - def get_barre (self): - n = self.get_maybe_exist_named_child ('barre') - if n: - return getattr (n, 'type', '') - else: - return '' +class Root(ChordPitch): + pass -class FiguredBass (Music_xml_node): +class Score_part(Music_xml_node): pass -class Beats (Music_xml_node): +class Slide(Music_xml_spanner): pass -class BeatType (Music_xml_node): +class Staff(Music_xml_node): pass -class BeatUnit (Music_xml_node): +class Text(Music_xml_node): pass -class BeatUnitDot (Music_xml_node): +class Type(Music_xml_node): pass -class PerMinute (Music_xml_node): +class Wavy_line(Music_xml_spanner): pass -class Print (Music_xml_node): +class Wedge(Music_xml_spanner): pass +class Words(Music_xml_node): + pass ## need this, not all classes are instantiated @@ -1199,6 +1733,7 @@ class_dict = { 'bend' : Bend, 'bracket' : Bracket, 'chord': Chord, + 'credit': Credit, 'dashes' : Dashes, 'degree' : ChordModification, 'dot': Dot, @@ -1221,9 +1756,10 @@ class_dict = { 'measure': Measure, 'notations': Notations, 'note': Note, + 'notehead': Notehead, 'octave-shift': Octave_shift, 'part': Part, - 'part-group': Part_group, + 'part-group': Part_group, 'part-list': Part_list, 'pedal': Pedal, 'per-minute': PerMinute, @@ -1234,10 +1770,13 @@ class_dict = { 'score-part': Score_part, 'slide': Slide, 'slur': Slur, + 'sound': Sound, 'staff': Staff, + 'stem': Stem, 'syllabic': Syllabic, 'text': Text, 'time-modification': Time_modification, + 'tied': Tied, 'tuplet': Tuplet, 'type': Type, 'unpitched': Unpitched, @@ -1247,59 +1786,59 @@ class_dict = { 'work': Work, } -def name2class_name (name): - name = name.replace ('-', '_') - name = name.replace ('#', 'hash_') +def name2class_name(name): + name = name.replace('-', '_') + name = name.replace('#', 'hash_') name = name[0].upper() + name[1:].lower() - return str (name) + return str(name) -def get_class (name): - classname = class_dict.get (name) +def get_class(name): + classname = class_dict.get(name) if classname: return classname else: - class_name = name2class_name (name) - klass = new.classobj (class_name, (Music_xml_node,) , {}) + class_name = name2class_name(name) + klass = new.classobj(class_name,(Music_xml_node,) , {}) class_dict[name] = klass return klass -def lxml_demarshal_node (node): +def lxml_demarshal_node(node): name = node.tag # Ignore comment nodes, which are also returned by the etree parser! if name is None or node.__class__.__name__ == "_Comment": return None - klass = get_class (name) + klass = get_class(name) py_node = klass() py_node._original = node py_node._name = name py_node._data = node.text - py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()] - py_node._children = filter (lambda x: x, py_node._children) + py_node._children = [lxml_demarshal_node(cn) for cn in node.getchildren()] + py_node._children = filter(lambda x: x, py_node._children) for c in py_node._children: c._parent = py_node - for (k, v) in node.items (): + for(k, v) in node.items(): py_node.__dict__[k] = v py_node._attribute_dict[k] = v return py_node -def minidom_demarshal_node (node): +def minidom_demarshal_node(node): name = node.nodeName - klass = get_class (name) - py_node = klass () + klass = get_class(name) + py_node = klass() py_node._name = name - py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes] + py_node._children = [minidom_demarshal_node(cn) for cn in node.childNodes] for c in py_node._children: c._parent = py_node if node.attributes: - for (nm, value) in node.attributes.items (): + for(nm, value) in node.attributes.items(): py_node.__dict__[nm] = value py_node._attribute_dict[nm] = value @@ -1311,11 +1850,11 @@ def minidom_demarshal_node (node): return py_node -if __name__ == '__main__': +if __name__ == '__main__': import lxml.etree - tree = lxml.etree.parse ('beethoven.xml') - mxl_tree = lxml_demarshal_node (tree.getroot ()) - ks = class_dict.keys () - ks.sort () - print '\n'.join (ks) + tree = lxml.etree.parse('beethoven.xml') + mxl_tree = lxml_demarshal_node(tree.getroot()) + ks = class_dict.keys() + ks.sort() + print '\n'.join(ks) diff --git a/python/musicxml2ly_conversion.py b/python/musicxml2ly_conversion.py new file mode 100644 index 0000000000..67df2add83 --- /dev/null +++ b/python/musicxml2ly_conversion.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +from rational import * +import lilylib as ly +import musicexp +_ = ly._ + +def rational_to_lily_duration(rational_len): + d = musicexp.Duration() + + rational_len.normalize_self() + 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 + 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.duration_log = d_log - dots + d.dots = dots + elif(d_log >= 0): + d.duration_log = d_log + d.factor = Rational(rational_len.numerator()) + else: + ly.warning(_("Encountered rational duration with denominator %s, " + "unable to convert to lilypond duration") % + rational_len.denominator()) + # TODO: Test the above error message + return None + + return d + +def musicxml_step_to_lily(step): + if step: + return (ord(step) - ord('A') + 7 - 2) % 7 + else: + return None + +class Marker(musicexp.Music): + def __init__(self): + self.direction = 0 + self.event = None + def print_ly(self, printer): + ly.warning(_("Encountered unprocessed marker %s\n") % self) + pass + def ly_expression(self): + return "" + +class RepeatMarker(Marker): + def __init__(self): + Marker.__init__(self) + self.times = 0 + +class EndingMarker(Marker): + pass diff --git a/python/utilities.py b/python/utilities.py new file mode 100644 index 0000000000..da2386bcde --- /dev/null +++ b/python/utilities.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +import re +import string + +def string_to_number(s): + try: + return int(s) + except ValueError: + return float(s) + +def string_to_integer(s): + num = string_to_number(s) + if isinstance(num, int): + return num + else: + return int(num) + +def escape_ly_output_string (input_string): + return_string = input_string + needs_quotes = not re.match (u"^[a-zA-ZäöüÜÄÖßñ]*$", return_string); + if needs_quotes: + return_string = "\"" + string.replace (return_string, "\"", "\\\"") + "\"" + return return_string + +def interpret_alter_element (alter_elm): + alter = 0 + if alter_elm: + val = eval(alter_elm.get_text ()) + if type (val) in (int, float): + alter = val + return alter + +def musicxml_duration_to_log (dur): + return {'256th': 8, + '128th': 7, + '64th': 6, + '32nd': 5, + '16th': 4, + 'eighth': 3, + 'quarter': 2, + 'half': 1, + 'whole': 0, + 'breve':-1, + 'longa':-2, + 'long':-2}.get (dur, 0) + +def hexcolorval_to_nr (hex_val): + try: + v = int (hex_val, 16) + if v == 255: + v = 256 + return v / 256. + except ValueError: + return 0. + +def hex_to_color(hex_val): + res = re.match ( + r'#([0-9a-f][0-9a-f]|)([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$', + hex_val, + re.IGNORECASE) + if res: + return map(lambda x: hexcolorval_to_nr (x), res.group (2, 3, 4)) + else: + return None + +def split_string_and_preserve_doublequoted_substrings(value): + import shlex + lex = shlex.shlex(value) + lex.quotes = '"' + lex.whitespace_split = True + lex.commenters = '' + return list(lex) + +def musicxml_sound_to_lilypond_midi_instrument(sound): + sounds = { + "brass.french-horn": 'french horn', + "brass.group": 'brass section', + "brass.group.synth": 'synthbrass 1', + "brass.trombone": 'trombone', + "brass.trombone.alto": 'trombone', + "brass.trombone.bass": 'trombone', + "brass.trombone.contrabass": 'trombone', + "brass.trombone.tenor": 'trombone', + "brass.trumpet": 'trumpet', + "brass.trumpet.baroque": 'trumpet', + "brass.trumpet.bass": 'trumpet', + "brass.trumpet.bflat": 'trumpet', + "brass.trumpet.c": 'trumpet', + "brass.trumpet.d": 'trumpet', + "brass.trumpet.piccolo": 'trumpet', + "brass.trumpet.pocket": 'trumpet', + "brass.trumpet.slide": 'trumpet', + "brass.trumpet.tenor": 'trumpet', + "brass.tuba": 'tuba', + "brass.tuba.bass": 'tuba', + "brass.tuba.subcontrabass": 'tuba', + "brass.wagner-tuba": 'french horn', + "drum.timpani": 'timpani', + "drum.tom-tom": 'melodic tom', + "drum.tom-tom.synth": 'synth drum', + "effect.applause": 'applause', + "effect.bass-string-slap": 'slap bass 1', + "effect.bird": 'bird tweet', + "effect.bird.tweet": 'bird tweet', + "effect.breath": 'breath noise', + "effect.guitar-fret": 'guitar fret noise', + "effect.gunshot": 'gunshot', + "effect.helicopter": 'helicopter', + "effect.metronome-click": 'woodblock', + "effect.rain": 'fx1 (rain)', + "effect.seashore": 'seashore', + "effect.telephone-ring": 'telephone ring', + "keyboard.accordion": 'accordion', + "keyboard.bandoneon": 'accordion', + "keyboard.celesta": 'celesta', + "keyboard.clavichord": 'clav', + "keyboard.concertina": 'concertina', + "keyboard.harpsichord": 'harpsichord', + "keyboard.ondes-martenot": 'ocarina', + "keyboard.organ": 'church organ', + "keyboard.organ.drawbar": 'drawbar organ', + "keyboard.organ.percussive": 'percussive organ', + "keyboard.organ.pipe": 'church organ', + "keyboard.organ.reed": 'reed organ', + "keyboard.piano": 'acoustic grand', + "keyboard.piano.electric": 'electric piano 1', + "keyboard.piano.grand": 'acoustic grand', + "keyboard.piano.honky-tonk": 'honky-tonk', + "metal.bells.agogo": 'agogo', + "metal.bells.tinklebell": 'tinkle bell', + "metal.cymbal.reverse": 'reverse cymbal', + "pitched-percussion.glockenspiel": 'glockenspiel', + "pitched-percussion.glockenspiel.alto": 'glockenspiel', + "pitched-percussion.glockenspiel.soprano": 'glockenspiel', + "pitched-percussion.hammer-dulcimer": 'dulcimer', + "pitched-percussion.kalimba": 'kalimba', + "pitched-percussion.marimba": 'marimba', + "pitched-percussion.marimba.bass": 'marimba', + "pitched-percussion.music-box": 'music box', + "pitched-percussion.tubular-bells": 'tubular bells', + "pitched-percussion.vibraphone": 'vibraphone', + "pitched-percussion.xylophone": 'xylophone', + "pitched-percussion.xylophone.alto": 'xylophone', + "pitched-percussion.xylophone.bass": 'xylophone', + "pitched-percussion.xylophone.soprano": 'xylophone', + "pitched-percussion.xylorimba": 'xylophone', + "pluck.banjo": 'banjo', + "pluck.banjo.tenor": 'banjo', + "pluck.bass": 'acoustic bass', + "pluck.bass.acoustic": 'acoustic bass', + "pluck.bass.electric": 'electric bass', + "pluck.bass.fretless": 'fretless bass', + "pluck.bass.synth": 'synth bass 1', + "pluck.dulcimer": 'dulcimer', + "pluck.guitar": 'acoustic guitar (nylon)', + "pluck.guitar.acoustic": 'acoustic guitar (nylon)', + "pluck.guitar.electric": 'electric guitar (jazz)', + "pluck.guitar.nylon-string": 'acoustic guitar (nylon)', + "pluck.guitar.steel-string": 'acoustic guitar (steel)', + "pluck.harp": 'orchestral harp', + "pluck.lute": 'acoustic guitar (nylon)', + "pluck.shamisen": 'shamisen', + "pluck.sitar": 'sitar', + "strings.cello": 'cello', + "strings.cello.piccolo": 'cello', + "strings.contrabass": 'contrabass', + "strings.fiddle": 'fiddle', + "strings.group.synth": 'synth strings 1', + "strings.viola": 'viola', + "strings.violin": 'violin', + "synth.effects.atmosphere": 'fx 4 (atmosphere)', + "synth.effects.brightness": 'fx 5 (brightness)', + "synth.effects.crystal": 'fx 3 (crystal)', + "synth.effects.echoes": 'fx 7 echoes', + "synth.effects.goblins": 'fx 6 goblins', + "synth.effects.rain": 'fx 1 rain', + "synth.effects.sci-fi": 'fx 8 sci-fi', + "synth.effects.soundtrack": 'fx 2 (soundtrack)', + "synth.pad.bowed": 'pad 5 bowed', + "synth.pad.choir": 'pad 4 choir', + "synth.pad.halo": 'pad 7 halo', + "synth.pad.metallic": 'pad 6 metallic', + "synth.pad.polysynth": 'pad 3 polysynth', + "synth.pad.sweep": 'pad 8 sweep', + "synth.pad.warm": 'pad 2 warm', + "synth.tone.sawtooth": 'lead 1 (square)', + "synth.tone.square": 'lead 2 (sawtooth)', + "voice.aa": 'choir aahs', + "voice.alto": 'choir aahs', + "voice.aw": 'choir aahs', + "voice.baritone": 'choir aahs', + "voice.bass": 'choir aahs', + "voice.child": 'choir aahs', + "voice.countertenor": 'choir aahs', + "voice.doo": 'choir aahs', + "voice.ee": 'choir aahs', + "voice.female": 'choir aahs', + "voice.kazoo": 'choir aahs', + "voice.male": 'choir aahs', + "voice.mezzo-soprano": 'choir aahs', + "voice.mm": 'choir aahs', + "voice.oo": 'voice oohs', + "voice.soprano": 'choir aahs', + "voice.synth": 'synth voice', + "wind.flutes.blown-bottle": 'blown bottle', + "wind.flutes.calliope": 'lead 3 (calliope)', + "wind.flutes.flute": 'flute', + "wind.flutes.flute.alto": 'flute', + "wind.flutes.flute.bass": 'flute', + "wind.flutes.flute.contra-alto": 'flute', + "wind.flutes.flute.contrabass": 'flute', + "wind.flutes.flute.double-contrabass": 'flute', + "wind.flutes.flute.piccolo": 'flute', + "wind.flutes.flute.subcontrabass": 'flute', + "wind.flutes.ocarina": 'ocarina', + "wind.flutes.recorder": 'recorder', + "wind.flutes.recorder.alto": 'recorder', + "wind.flutes.recorder.bass": 'recorder', + "wind.flutes.recorder.contrabass": 'recorder', + "wind.flutes.recorder.descant": 'recorder', + "wind.flutes.recorder.garklein": 'recorder', + "wind.flutes.recorder.great-bass": 'recorder', + "wind.flutes.recorder.sopranino": 'recorder', + "wind.flutes.recorder.soprano": 'recorder', + "wind.flutes.recorder.tenor": 'recorder', + "wind.flutes.shakuhachi": 'shakuhachi', + "wind.flutes.whistle": 'whistle', + "wind.flutes.whistle.alto": 'whistle', + "wind.pipes.bagpipes": 'bagpipe', + "wind.reed.basset-horn": 'clarinet', + "wind.reed.bassoon": 'bassoon', + "wind.reed.clarinet": 'clarinet', + "wind.reed.clarinet.a": 'clarinet', + "wind.reed.clarinet.alto": 'clarinet', + "wind.reed.clarinet.bass": 'clarinet', + "wind.reed.clarinet.basset": 'clarinet', + "wind.reed.clarinet.bflat": 'clarinet', + "wind.reed.clarinet.contra-alto": 'clarinet', + "wind.reed.clarinet.contrabass": 'clarinet', + "wind.reed.clarinet.eflat": 'clarinet', + "wind.reed.clarinet.piccolo.aflat": 'clarinet', + "wind.reed.contrabass": 'contrabass', + "wind.reed.contrabassoon": 'bassoon', + "wind.reed.english-horn": 'oboe', + "wind.reed.harmonica": 'harmonica', + "wind.reed.harmonica.bass": 'harmonica', + "wind.reed.oboe": 'oboe', + "wind.reed.oboe.bass": 'oboe', + "wind.reed.oboe.piccolo": 'oboe', + "wind.reed.oboe-da-caccia": 'oboe', + "wind.reed.oboe-damore": 'oboe', + "wind.reed.saxophone": 'alto sax', + "wind.reed.saxophone.alto": 'alto sax', + "wind.reed.saxophone.baritone": 'baritone sax', + "wind.reed.saxophone.bass": 'baritone sax', + "wind.reed.saxophone.contrabass": 'baritone sax', + "wind.reed.saxophone.melody": 'soprano sax', + "wind.reed.saxophone.mezzo-soprano": 'soprano sax', + "wind.reed.saxophone.sopranino": 'soprano sax', + "wind.reed.saxophone.sopranissimo": 'soprano sax', + "wind.reed.saxophone.soprano": 'soprano, sax', + "wind.reed.saxophone.subcontrabass": 'baritone sax', + "wind.reed.saxophone.tenor": 'tenor sax', + "wind.reed.shenai": 'shanai', + "wood.temple-block": 'wood block', + "wood.wood-block": 'wood block', + } + return sounds.get(sound, 'acoustic grand') diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index b1cc496f6e..766214bd76 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -9,6 +9,7 @@ import codecs import zipfile import tempfile import StringIO +import warnings """ @relocate-preamble@ @@ -17,31 +18,38 @@ import StringIO import lilylib as ly _ = ly._ +import utilities +import musicxml2ly_conversion import musicxml import musicexp from rational import Rational +lilypond_version = "@TOPLEVEL_VERSION@" + # Store command-line options in a global variable, so we can access them everywhere options = None class Conversion_Settings: def __init__(self): self.ignore_beaming = False + self.convert_stem_directions = False + self.convert_rest_positions = True -conversion_settings = 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 # this layout and add the corresponding settings -layout_information = musicexp.Layout () +layout_information = musicexp.Layout() +# Use a global variable to store the setting needed inside a \paper block. +paper = musicexp.Paper() needed_additional_definitions = [] additional_definitions = { - "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. - #(define ((tuplet-number::append-note-wrapper function note) grob) + #(define-public ((tuplet-number::append-note-wrapper function note) grob) (let* ((txt (if function (function grob) #f))) (if txt (markup txt #:fontsize -5 #:note note UP) @@ -64,91 +72,105 @@ additional_definitions = { """, } -def round_to_two_digits (val): - return round (val * 100) / 100 +def round_to_two_digits(val): + return round(val * 100) / 100 -def extract_paper_information (tree): - paper = musicexp.Paper () - defaults = tree.get_maybe_exist_named_child ('defaults') +def extract_paper_information(score_partwise): + defaults = score_partwise.get_maybe_exist_named_child('defaults') if not defaults: return None tenths = -1 - scaling = defaults.get_maybe_exist_named_child ('scaling') + scaling = defaults.get_maybe_exist_named_child('scaling') + default_tenths_to_millimeters_ratio = 0.175 + default_staff_size = 20 if scaling: - mm = scaling.get_named_child ('millimeters') - mm = string.atof (mm.get_text ()) - tn = scaling.get_maybe_exist_named_child ('tenths') - tn = string.atof (tn.get_text ()) + mm = scaling.get_named_child('millimeters') + mm = float(mm.get_text()) + tn = scaling.get_maybe_exist_named_child('tenths') + tn = float(tn.get_text()) + # The variable 'tenths' is actually a ratio, NOT the value of . + # TODO: rename and replace. tenths = mm / tn - paper.global_staff_size = mm * 72.27 / 25.4 - # We need the scaling (i.e. the size of staff tenths for everything! + ratio = tenths / default_tenths_to_millimeters_ratio + staff_size = default_staff_size * ratio + + if 1 < staff_size < 100: + paper.global_staff_size = staff_size + else: + msg = "paper.global_staff_size {} is too large, using defaults=20".format( + staff_size) + warnings.warn(msg) + paper.global_staff_size = 20 + + # We need the scaling(i.e. the size of staff tenths for everything! if tenths < 0: return None - def from_tenths (txt): - return round_to_two_digits (string.atof (txt) * tenths / 10) - def set_paper_variable (varname, parent, element_name): - el = parent.get_maybe_exist_named_child (element_name) + def from_tenths(txt): + return round_to_two_digits(float(txt) * tenths / 10) + def set_paper_variable(varname, parent, element_name): + el = parent.get_maybe_exist_named_child(element_name) if el: # Convert to cm from tenths - setattr (paper, varname, from_tenths (el.get_text ())) + setattr(paper, varname, from_tenths(el.get_text())) - pagelayout = defaults.get_maybe_exist_named_child ('page-layout') + pagelayout = defaults.get_maybe_exist_named_child('page-layout') if pagelayout: # TODO: How can one have different margins for even and odd pages??? - set_paper_variable ("page_height", pagelayout, 'page-height') - set_paper_variable ("page_width", pagelayout, 'page-width') - - pmargins = pagelayout.get_named_children ('page-margins') - for pm in pmargins: - set_paper_variable ("left_margin", pm, 'left-margin') - set_paper_variable ("right_margin", pm, 'right-margin') - set_paper_variable ("bottom_margin", pm, 'bottom-margin') - set_paper_variable ("top_margin", pm, 'top-margin') - - systemlayout = defaults.get_maybe_exist_named_child ('system-layout') + set_paper_variable("page_height", pagelayout, 'page-height') + set_paper_variable("page_width", pagelayout, 'page-width') + + if conversion_settings.convert_page_margins: + pmargins = pagelayout.get_named_children('page-margins') + for pm in pmargins: + set_paper_variable("left_margin", pm, 'left-margin') + set_paper_variable("right_margin", pm, 'right-margin') + set_paper_variable("bottom_margin", pm, 'bottom-margin') + set_paper_variable("top_margin", pm, 'top-margin') + + systemlayout = defaults.get_maybe_exist_named_child('system-layout') if systemlayout: - sl = systemlayout.get_maybe_exist_named_child ('system-margins') + sl = systemlayout.get_maybe_exist_named_child('system-margins') if sl: - set_paper_variable ("system_left_margin", sl, 'left-margin') - set_paper_variable ("system_right_margin", sl, 'right-margin') - set_paper_variable ("system_distance", systemlayout, 'system-distance') - set_paper_variable ("top_system_distance", systemlayout, 'top-system-distance') + set_paper_variable("system_left_margin", sl, 'left-margin') + set_paper_variable("system_right_margin", sl, 'right-margin') + set_paper_variable("system_distance", systemlayout, 'system-distance') + set_paper_variable("top_system_distance", systemlayout, 'top-system-distance') - stafflayout = defaults.get_named_children ('staff-layout') + stafflayout = defaults.get_named_children('staff-layout') for sl in stafflayout: - nr = getattr (sl, 'number', 1) - dist = sl.get_named_child ('staff-distance') + nr = getattr(sl, 'number', 1) + dist = sl.get_named_child('staff-distance') #TODO: the staff distance needs to be set in the Staff context!!! # TODO: Finish appearance?, music-font?, word-font?, lyric-font*, lyric-language* - appearance = defaults.get_named_child ('appearance') + appearance = defaults.get_named_child('appearance') if appearance: - lws = appearance.get_named_children ('line-width') + lws = appearance.get_named_children('line-width') for lw in lws: # Possible types are: beam, bracket, dashes, # enclosure, ending, extend, heavy barline, leger, # light barline, octave shift, pedal, slur middle, slur tip, # staff, stem, tie middle, tie tip, tuplet bracket, and wedge tp = lw.type - w = from_tenths (lw.get_text ()) + w = from_tenths(lw.get_text()) # TODO: Do something with these values! - nss = appearance.get_named_children ('note-size') + nss = appearance.get_named_children('note-size') for ns in nss: # Possible types are: cue, grace and large tp = ns.type - sz = from_tenths (ns.get_text ()) + sz = from_tenths(ns.get_text()) # TODO: Do something with these values! # elements have no specified meaning - rawmusicfont = defaults.get_named_child ('music-font') + rawmusicfont = defaults.get_named_child('music-font') if rawmusicfont: # TODO: Convert the font pass - rawwordfont = defaults.get_named_child ('word-font') + rawwordfont = defaults.get_named_child('word-font') if rawwordfont: # TODO: Convert the font pass - rawlyricsfonts = defaults.get_named_children ('lyric-font') + rawlyricsfonts = defaults.get_named_children('lyric-font') for lyricsfont in rawlyricsfonts: # TODO: Convert the font pass @@ -156,58 +178,68 @@ def extract_paper_information (tree): return paper - +credit_dict = { + None:None, + '':None, + 'page number':None, #TODO: what is it used for ? + 'title':'title', + 'subtitle':'subtitle', + 'composer':'composer', + 'arranger':'arranger', + 'lyricist':'poet', + 'rights':'copyright' +} # score information is contained in the , or tags # extract those into a hash, indexed by proper lilypond header attributes -def extract_score_information (tree): - header = musicexp.Header () - def set_if_exists (field, value): +def extract_score_information(tree): + header = musicexp.Header() + def set_if_exists(field, value): if value: - header.set_field (field, musicxml.escape_ly_output_string (value)) + header.set_field(field, utilities.escape_ly_output_string(value)) - work = tree.get_maybe_exist_named_child ('work') - if work: - 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') + movement_title = tree.get_maybe_exist_named_child('movement-title') + movement_number = tree.get_maybe_exist_named_child('movement-number') + if movement_title: + set_if_exists('title', movement_title.get_text()) + if movement_number: + set_if_exists('movementnumber', movement_number.get_text()) + # set_if_exists('piece', movement_number.get_text()) # the movement number should be visible in the score. - # 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 + work = tree.get_maybe_exist_named_child('work') if work: - work_title = work.get_work_title () - set_if_exists ('title', work_title) - if work_title == '' and movement_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') + work_number = work.get_work_number() + work_title = work.get_work_title() + # Overwrite the title from movement-title with work->title + set_if_exists('title', work.get_work_title()) + set_if_exists('opus', work.get_work_number()) + # Use movement-title as subtitle + if movement_title: + set_if_exists('subtitle', movement_title.get_text()) + +# TODO: Translation of opus element. Not to be confused with opus in LilyPond. MusicXML opus is a document element for opus DTD + identifications = tree.get_named_children('identification') for ids in identifications: - set_if_exists ('copyright', ids.get_rights ()) - set_if_exists ('composer', ids.get_composer ()) - set_if_exists ('arranger', ids.get_arranger ()) - set_if_exists ('editor', ids.get_editor ()) - set_if_exists ('poet', ids.get_poet ()) - - 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 ()); + set_if_exists('copyright', ids.get_rights()) + set_if_exists('composer', ids.get_composer()) + set_if_exists('arranger', ids.get_arranger()) + set_if_exists('editor', ids.get_editor()) + set_if_exists('poet', ids.get_poet()) + + 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()) + + # ... becomes + # \header { texidoc = ... + set_if_exists('texidoc', ids.get_file_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 () + 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 @@ -222,179 +254,204 @@ def extract_score_information (tree): "Noteworthy Composer": "Noteworthy Composer's nwc2xm[", } for s in software: - app_description = ignore_beaming_software.get (s, False); + app_description = ignore_beaming_software.get(s, False); if app_description: conversion_settings.ignore_beaming = True - ly.warning (_ ("Encountered file created by %s, containing " + ly.warning(_("Encountered file created by %s, containing " "wrong beaming information. All beaming " "information in the MusicXML file will be " "ignored") % app_description) + credits = tree.get_named_children('credit') + has_composer = False + for cred in credits: + type = credit_dict.get(cred.get_type()) + if(type == None): + type = credit_dict.get(cred.find_type(credits)) + if(type == 'composer'): + if(has_composer): + type = 'poet' + else: + has_composer = True + set_if_exists(type, cred.get_text()) + elif(type == 'title'): + if(not(work) and not(movement_title)): + set_if_exists('title', cred.get_text()) + #elif(not(movement_title)): #bullshit! + # set_if_exists('subtitle', cred.get_text()) #bullshit! otherwise both title and subtitle show the work-title. + elif(type == None): + pass + else: + set_if_exists(type, cred.get_text()) + + # TODO: Check for other unsupported features return header class PartGroupInfo: - def __init__ (self): + def __init__(self): self.start = {} self.end = {} - def is_empty (self): - return len (self.start) + len (self.end) == 0 - def add_start (self, g): - self.start[getattr (g, 'number', "1")] = g - def add_end (self, g): - self.end[getattr (g, 'number', "1")] = g - def print_ly (self, printer): - ly.warning (_ ("Unprocessed PartGroupInfo %s encountered") % self) - def ly_expression (self): - ly.warning (_ ("Unprocessed PartGroupInfo %s encountered") % self) + def is_empty(self): + return len(self.start) + len(self.end) == 0 + def add_start(self, g): + self.start[getattr(g, 'number', "1")] = g + def add_end(self, g): + self.end[getattr(g, 'number', "1")] = g + def print_ly(self, printer): + ly.warning(_("Unprocessed PartGroupInfo %s encountered") % self) + def ly_expression(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') +def staff_attributes_to_string_tunings(mxl_attr): + details = mxl_attr.get_maybe_exist_named_child('staff-details') if not details: return [] lines = 6 - staff_lines = details.get_maybe_exist_named_child ('staff-lines') + staff_lines = details.get_maybe_exist_named_child('staff-lines') if staff_lines: - lines = string.atoi (staff_lines.get_text ()) - + lines = string.atoi(staff_lines.get_text()) tunings = [musicexp.Pitch()] * lines - staff_tunings = details.get_named_children ('staff-tuning') + staff_tunings = details.get_named_children('staff-tuning') for i in staff_tunings: p = musicexp.Pitch() line = 0 try: - line = string.atoi (i.line) - 1 + line = string.atoi(i.line) - 1 except ValueError: pass tunings[line] = p - step = i.get_named_child (u'tuning-step') - step = step.get_text ().strip () - p.step = musicxml_step_to_lily (step) + step = i.get_named_child(u'tuning-step') + step = step.get_text().strip() + p.step = musicxml2ly_conversion.musicxml_step_to_lily(step) - octave = i.get_named_child (u'tuning-octave') - octave = octave.get_text ().strip () - p.octave = int (octave) - 4 + octave = i.get_named_child(u'tuning-octave') + octave = octave.get_text().strip() + p.octave = int(octave) - 4 - alter = i.get_named_child (u'tuning-alter') + alter = i.get_named_child(u'tuning-alter') if alter: - p.alteration = int (alter.get_text ().strip ()) + p.alteration = int(alter.get_text().strip()) # lilypond seems to use the opposite ordering than MusicXML... - tunings.reverse () - + tunings.reverse() return tunings - -def staff_attributes_to_lily_staff (mxl_attr): +def staff_attributes_to_lily_staff(mxl_attr): if not mxl_attr: - return musicexp.Staff () + return musicexp.Staff() - (staff_id, attributes) = mxl_attr.items ()[0] + (staff_id, attributes) = mxl_attr.items()[0] # distinguish by clef: - # percussion (percussion and rhythmic), tab, and everything else + # percussion(percussion and rhythmic), tab, and everything else clef_sign = None - clef = attributes.get_maybe_exist_named_child ('clef') + clef = attributes.get_maybe_exist_named_child('clef') if clef: - sign = clef.get_maybe_exist_named_child ('sign') + sign = clef.get_maybe_exist_named_child('sign') if sign: - clef_sign = {"percussion": "percussion", "TAB": "tab"}.get (sign.get_text (), None) + clef_sign = {"percussion": "percussion", "TAB": "tab"}.get(sign.get_text(), None) lines = 5 - details = attributes.get_named_children ('staff-details') + details = attributes.get_named_children('staff-details') for d in details: - staff_lines = d.get_maybe_exist_named_child ('staff-lines') + staff_lines = d.get_maybe_exist_named_child('staff-lines') if staff_lines: - lines = string.atoi (staff_lines.get_text ()) + 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 () + staff = musicexp.RhythmicStaff() elif clef_sign == "percussion": - staff = musicexp.DrumStaff () + staff = musicexp.DrumStaff() # staff.drum_style_table = ??? elif clef_sign == "tab": - staff = musicexp.TabStaff () - staff.string_tunings = staff_attributes_to_string_tunings (attributes) + staff = musicexp.TabStaff() + staff.string_tunings = staff_attributes_to_string_tunings(attributes) # staff.tablature_format = ??? else: - staff = musicexp.Staff () + staff = musicexp.Staff() # TODO: Handle case with lines <> 5! - if (lines != 5): - staff.add_context_modification ("\\override StaffSymbol.line-count = #%s" % lines) - + if(lines != 5): + staff.add_context_modification("\\override StaffSymbol #'line-count = #%s" % lines) return staff +def extract_instrument_sound(score_part): + score_instrument = score_part.get_maybe_exist_named_child('score-instrument') + if not score_instrument: + return None + sound = score_instrument.get_maybe_exist_named_child('instrument-sound') + if sound: + return utilities.musicxml_sound_to_lilypond_midi_instrument(sound.get_text()) -def extract_score_structure (part_list, staffinfo): - score = musicexp.Score () - structure = musicexp.StaffGroup (None) - score.set_contents (structure) +def extract_score_structure(part_list, staffinfo): + score = musicexp.Score() + structure = musicexp.StaffGroup(None) + score.set_contents(structure) if not part_list: return structure - def read_score_part (el): - if not isinstance (el, musicxml.Score_part): + def read_score_part(el): + if not isinstance(el, musicxml.Score_part): return # Depending on the attributes of the first measure, we create different - # types of staves (Staff, RhythmicStaff, DrumStaff, TabStaff, etc.) - staff = staff_attributes_to_lily_staff (staffinfo.get (el.id, None)) + # types of staves(Staff, RhythmicStaff, DrumStaff, TabStaff, etc.) + staff = staff_attributes_to_lily_staff(staffinfo.get(el.id, None)) if not staff: return None staff.id = el.id - partname = el.get_maybe_exist_named_child ('part-name') + partname = el.get_maybe_exist_named_child('part-name') # Finale gives unnamed parts the name "MusicXML Part" automatically! if partname and partname.get_text() != "MusicXML Part": - staff.instrument_name = partname.get_text () + staff.instrument_name = partname.get_text() # part-name-display overrides part-name! - partname = el.get_maybe_exist_named_child ("part-name-display") + 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') + staff.instrument_name = extract_display_text(partname) + if hasattr(options, 'midi') and options.midi: + staff.sound = extract_instrument_sound(el) + if staff.instrument_name: + paper.indent = max(paper.indent, len(staff.instrument_name)) + paper.instrument_names.append(staff.instrument_name) + partdisplay = el.get_maybe_exist_named_child('part-abbreviation') if partdisplay: - staff.short_instrument_name = partdisplay.get_text () + staff.short_instrument_name = partdisplay.get_text() # part-abbreviation-display overrides part-abbreviation! - partdisplay = el.get_maybe_exist_named_child ("part-abbreviation-display") + partdisplay = el.get_maybe_exist_named_child("part-abbreviation-display") if partdisplay: - staff.short_instrument_name = extract_display_text (partdisplay) + staff.short_instrument_name = extract_display_text(partdisplay) # TODO: Read in the MIDI device / instrument + if staff.short_instrument_name: + paper.short_indent = max(paper.short_indent, len(staff.short_instrument_name)) return staff - def read_score_group (el): - if not isinstance (el, musicxml.Part_group): + def read_score_group(el): + if not isinstance(el, musicxml.Part_group): return - group = musicexp.StaffGroup () - if hasattr (el, 'number'): + group = musicexp.StaffGroup() + if hasattr(el, 'number'): id = el.number group.id = id #currentgroups_dict[id] = group - #currentgroups.append (id) - if el.get_maybe_exist_named_child ('group-name'): - group.instrument_name = el.get_maybe_exist_named_child ('group-name').get_text () - if el.get_maybe_exist_named_child ('group-abbreviation'): - group.short_instrument_name = el.get_maybe_exist_named_child ('group-abbreviation').get_text () - if el.get_maybe_exist_named_child ('group-symbol'): - group.symbol = el.get_maybe_exist_named_child ('group-symbol').get_text () - if el.get_maybe_exist_named_child ('group-barline'): - group.spanbar = el.get_maybe_exist_named_child ('group-barline').get_text () + #currentgroups.append(id) + if el.get_maybe_exist_named_child('group-name'): + group.instrument_name = el.get_maybe_exist_named_child('group-name').get_text() + if el.get_maybe_exist_named_child('group-abbreviation'): + group.short_instrument_name = el.get_maybe_exist_named_child('group-abbreviation').get_text() + if el.get_maybe_exist_named_child('group-symbol'): + group.symbol = el.get_maybe_exist_named_child('group-symbol').get_text() + if el.get_maybe_exist_named_child('group-barline'): + group.spanbar = el.get_maybe_exist_named_child('group-barline').get_text() return group - parts_groups = part_list.get_all_children () + parts_groups = part_list.get_all_children() # the start/end group tags are not necessarily ordered correctly and groups # might even overlap, so we can't go through the children sequentially! @@ -402,146 +459,102 @@ def extract_score_structure (part_list, staffinfo): # 1) Replace all Score_part objects by their corresponding Staff objects, # also collect all group start/stop points into one PartGroupInfo object staves = [] - group_info = PartGroupInfo () + group_info = PartGroupInfo() for el in parts_groups: - if isinstance (el, musicxml.Score_part): - if not group_info.is_empty (): - staves.append (group_info) - group_info = PartGroupInfo () - staff = read_score_part (el) + if isinstance(el, musicxml.Score_part): + if not group_info.is_empty(): + staves.append(group_info) + group_info = PartGroupInfo() + staff = read_score_part(el) if staff: - staves.append (staff) - elif isinstance (el, musicxml.Part_group): + staves.append(staff) + elif isinstance(el, musicxml.Part_group): if el.type == "start": - group_info.add_start (el) + group_info.add_start(el) elif el.type == "stop": - group_info.add_end (el) - if not group_info.is_empty (): - staves.append (group_info) + group_info.add_end(el) + if not group_info.is_empty(): + staves.append(group_info) # 2) Now, detect the groups: group_starts = [] pos = 0 - while pos < len (staves): + while pos < len(staves): el = staves[pos] - if isinstance (el, PartGroupInfo): + if isinstance(el, PartGroupInfo): prev_start = 0 - if len (group_starts) > 0: + if len(group_starts) > 0: prev_start = group_starts[-1] - elif len (el.end) > 0: # no group to end here + elif len(el.end) > 0: # no group to end here el.end = {} - if len (el.end) > 0: # closes an existing group - ends = el.end.keys () - prev_started = staves[prev_start].start.keys () + if len(el.end) > 0: # closes an existing group + ends = el.end.keys() + prev_started = staves[prev_start].start.keys() grpid = None intersection = filter(lambda x:x in ends, prev_started) - if len (intersection) > 0: + if len(intersection) > 0: grpid = intersection[0] else: # Close the last started group - grpid = staves[prev_start].start.keys () [0] + grpid = staves[prev_start].start.keys() [0] # Find the corresponding closing tag and remove it! j = pos + 1 foundclosing = False - while j < len (staves) and not foundclosing: - if isinstance (staves[j], PartGroupInfo) and staves[j].end.has_key (grpid): + while j < len(staves) and not foundclosing: + if isinstance(staves[j], PartGroupInfo) and staves[j].end.has_key(grpid): foundclosing = True del staves[j].end[grpid] - if staves[j].is_empty (): + if staves[j].is_empty(): del staves[j] j += 1 grpobj = staves[prev_start].start[grpid] - group = read_score_group (grpobj) + group = read_score_group(grpobj) # remove the id from both the start and end - if el.end.has_key (grpid): + if el.end.has_key(grpid): del el.end[grpid] del staves[prev_start].start[grpid] - if el.is_empty (): + if el.is_empty(): del staves[pos] # replace the staves with the whole group for j in staves[(prev_start + 1):pos]: - group.append_staff (j) + group.append_staff(j) del staves[(prev_start + 1):pos] - staves.insert (prev_start + 1, group) + staves.insert(prev_start + 1, group) # reset pos so that we continue at the correct position pos = prev_start # remove an empty start group - if staves[prev_start].is_empty (): + if staves[prev_start].is_empty(): del staves[prev_start] - group_starts.remove (prev_start) + group_starts.remove(prev_start) pos -= 1 - elif len (el.start) > 0: # starts new part groups - group_starts.append (pos) + elif len(el.start) > 0: # starts new part groups + group_starts.append(pos) pos += 1 for i in staves: - structure.append_staff (i) + structure.append_staff(i) return score -def musicxml_duration_to_lily (mxl_note): - # if the note has no Type child, then that method returns None. In that case, - # use the tag instead. If that doesn't exist, either -> Error - dur = mxl_note.get_duration_info () - if dur: - d = musicexp.Duration () - d.duration_log = dur[0] - d.dots = dur[1] - # 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 () - return d - - else: - if mxl_note._duration > 0: - return rational_to_lily_duration (mxl_note._duration) - else: - mxl_note.message (_ ("Encountered note at %s without type and duration (=%s)") % (mxl_note.start, mxl_note._duration) ) - return None - - -def rational_to_lily_duration (rational_len): - d = musicexp.Duration () - rational_len.normalize_self () - 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 - 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.duration_log = d_log - dots - d.dots = dots - elif (d_log >= 0): - d.duration_log = d_log - d.factor = Rational (rational_len.numerator ()) - else: - ly.warning (_ ("Encountered rational duration with denominator %s, " - "unable to convert to lilypond duration") % - rational_len.denominator ()) - # TODO: Test the above error message - return None - return d - -def musicxml_partial_to_lily (partial_len): +def musicxml_partial_to_lily(partial_len): if partial_len > 0: - p = musicexp.Partial () - p.partial = rational_to_lily_duration (partial_len) + p = musicexp.Partial() + p.partial = musicxml2ly_conversion.rational_to_lily_duration(partial_len) return p else: return None -# Detect repeats and alternative endings in the chord event list (music_list) +# Detect repeats and alternative endings in the chord event list(music_list) # and convert them to the corresponding musicexp objects, containing nested # music -def group_repeats (music_list): +def group_repeats(music_list): repeat_replaced = True music_start = 0 i = 0 # Walk through the list of expressions, looking for repeat structure - # (repeat start/end, corresponding endings). If we find one, try to find the + #(repeat start/end, corresponding endings). If we find one, try to find the # last event of the repeat, replace the whole structure and start over again. # For nested repeats, as soon as we encounter another starting repeat bar, # treat that one first, and start over for the outer repeat. @@ -553,13 +566,13 @@ def group_repeats (music_list): ending_start = -1 # position of current ending start endings = [] # list of already finished endings pos = 0 - last = len (music_list) - 1 + last = len(music_list) - 1 repeat_replaced = False final_marker = 0 - while pos < len (music_list) and not repeat_replaced: + while pos < len(music_list) and not repeat_replaced: e = music_list[pos] repeat_finished = False - if isinstance (e, RepeatMarker): + if isinstance(e, musicxml2ly_conversion.RepeatMarker): if not repeat_times and e.times: repeat_times = e.times if e.direction == -1: @@ -576,7 +589,7 @@ def group_repeats (music_list): if repeat_end < 0: repeat_end = pos final_marker = pos - elif isinstance (e, EndingMarker): + elif isinstance(e, musicxml2ly_conversion.EndingMarker): if e.direction == -1: if repeat_start < 0: repeat_start = 0 @@ -586,17 +599,17 @@ def group_repeats (music_list): elif e.direction == 1: if ending_start < 0: ending_start = 0 - endings.append ([ending_start, pos]) + endings.append([ending_start, pos]) ending_start = -1 final_marker = pos - elif not isinstance (e, musicexp.BarLine): + elif not isinstance(e, musicexp.BarLine): # As soon as we encounter an element when repeat start and end # is set and we are not inside an alternative ending, # this whole repeat structure is finished => replace it if repeat_start >= 0 and repeat_end > 0 and ending_start < 0: repeat_finished = True - # Finish off all repeats without explicit ending bar (e.g. when + # Finish off all repeats without explicit ending bar(e.g. when # we convert only one page of a multi-page score with repeats) if pos == last and repeat_start >= 0: repeat_finished = True @@ -604,27 +617,27 @@ def group_repeats (music_list): if repeat_end < 0: repeat_end = pos if ending_start >= 0: - endings.append ([ending_start, pos]) + endings.append([ending_start, pos]) ending_start = -1 if repeat_finished: # We found the whole structure replace it! - r = musicexp.RepeatedMusic () + r = musicexp.RepeatedMusic() if repeat_times <= 0: repeat_times = 2 r.repeat_count = repeat_times - # don't erase the first element for "implicit" repeats (i.e. no + # don't erase the first element for "implicit" repeats(i.e. no # starting repeat bars at the very beginning) start = repeat_start + 1 if repeat_start == music_start: start = music_start - r.set_music (music_list[start:repeat_end]) - for (start, end) in endings: - s = musicexp.SequentialMusic () + r.set_music(music_list[start:repeat_end]) + for(start, end) in endings: + s = musicexp.SequentialMusic() s.elements = music_list[start + 1:end] - r.add_ending (s) + r.add_ending(s) del music_list[repeat_start:final_marker + 1] - music_list.insert (repeat_start, r) + music_list.insert(repeat_start, r) repeat_replaced = True pos += 1 # TODO: Implement repeats until the end without explicit ending bar @@ -633,148 +646,160 @@ def group_repeats (music_list): # 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 () - fraction = (1, 1) +def musicxml_tuplet_to_lily(tuplet_elt, time_modification): + tsm = musicexp.TimeScaledMusic() + fraction =(1, 1) if time_modification: - fraction = time_modification.get_fraction () + fraction = time_modification.get_fraction() tsm.numerator = fraction[0] tsm.denominator = fraction[1] - normal_type = tuplet_elt.get_normal_type () + normal_type = tuplet_elt.get_normal_type() if not normal_type and time_modification: - normal_type = time_modification.get_normal_type () + normal_type = time_modification.get_normal_type() if not normal_type and time_modification: - note = time_modification.get_parent () + note = time_modification.get_parent() if note: - normal_type = note.get_duration_info () + normal_type = note.get_duration_info() if normal_type: - normal_note = musicexp.Duration () + normal_note = musicexp.Duration() (normal_note.duration_log, normal_note.dots) = normal_type tsm.normal_type = normal_note - actual_type = tuplet_elt.get_actual_type () + actual_type = tuplet_elt.get_actual_type() if actual_type: - actual_note = musicexp.Duration () + actual_note = musicexp.Duration() (actual_note.duration_log, actual_note.dots) = actual_type tsm.actual_type = actual_note # Obtain non-default nrs of notes from the tuplet object! - tsm.display_numerator = tuplet_elt.get_normal_nr () - tsm.display_denominator = tuplet_elt.get_actual_nr () + tsm.display_numerator = tuplet_elt.get_normal_nr() + tsm.display_denominator = tuplet_elt.get_actual_nr() - if hasattr (tuplet_elt, 'bracket') and tuplet_elt.bracket == "no": + if hasattr(tuplet_elt, 'bracket') and tuplet_elt.bracket == "no": tsm.display_bracket = None - elif hasattr (tuplet_elt, 'line-shape') and getattr (tuplet_elt, 'line-shape') == "curved": + elif hasattr(tuplet_elt, 'line-shape') and getattr(tuplet_elt, 'line-shape') == "curved": tsm.display_bracket = "curved" else: tsm.display_bracket = "bracket" display_values = {"none": None, "actual": "actual", "both": "both"} - if hasattr (tuplet_elt, "show-number"): - tsm.display_number = display_values.get (getattr (tuplet_elt, "show-number"), "actual") + if hasattr(tuplet_elt, "show-number"): + tsm.display_number = display_values.get(getattr(tuplet_elt, "show-number"), "actual") - if hasattr (tuplet_elt, "show-type"): - tsm.display_type = display_values.get (getattr (tuplet_elt, "show-type"), None) + if hasattr(tuplet_elt, "show-type"): + tsm.display_type = display_values.get(getattr(tuplet_elt, "show-type"), None) return tsm -def group_tuplets (music_list, events): +def group_tuplets(music_list, events): """Collect Musics from MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects. """ - indices = [] brackets = {} j = 0 - for (ev_chord, tuplet_elt, time_modification) in events: - while (j < len (music_list)): + for(ev_chord, tuplet_elt, time_modification) in events: + while(j < len(music_list)): if music_list[j] == ev_chord: break j += 1 nr = 0 - if hasattr (tuplet_elt, 'number'): - nr = getattr (tuplet_elt, 'number') + if hasattr(tuplet_elt, 'number'): + nr = getattr(tuplet_elt, 'number') if tuplet_elt.type == 'start': - tuplet_object = musicxml_tuplet_to_lily (tuplet_elt, time_modification) + tuplet_object = musicxml_tuplet_to_lily(tuplet_elt, time_modification) tuplet_info = [j, None, tuplet_object] - indices.append (tuplet_info) + indices.append(tuplet_info) brackets[nr] = tuplet_info elif tuplet_elt.type == 'stop': - bracket_info = brackets.get (nr, None) + bracket_info = brackets.get(nr, None) if bracket_info: bracket_info[1] = j # Set the ending position to j del brackets[nr] new_list = [] last = 0 - for (i1, i2, tsm) in indices: + for(i1, i2, tsm) in indices: if i1 > i2: continue - new_list.extend (music_list[last:i1]) - seq = musicexp.SequentialMusic () + new_list.extend(music_list[last:i1]) + seq = musicexp.SequentialMusic() last = i2 + 1 + + # At this point music_list[i1:last] encompasses all the notes of the + # tuplet. There might be dynamics following this range, however, which + # apply to the last note of the tuplet. Advance last to include them + # in the range. + while last < len(music_list) and isinstance(music_list[last], musicexp.DynamicsEvent): + last += 1 + seq.elements = music_list[i1:last] tsm.element = seq - new_list.append (tsm) + new_list.append(tsm) #TODO: Handle nested tuplets!!!! - new_list.extend (music_list[last:]) + new_list.extend(music_list[last:]) return new_list -def musicxml_clef_to_lily (attributes): - change = musicexp.ClefChange () - (change.type, change.position, change.octave) = attributes.get_clef_information () +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 () +def musicxml_time_to_lily(attributes): + change = musicexp.TimeSignatureChange() + # time signature function + if hasattr(options, 'shift_meter') and options.shift_meter: + tmp_meter = options.shift_meter.split("/",1) + sig = [int(tmp_meter[0]), int(tmp_meter[1])] + change.originalFractions = attributes.get_time_signature() + else: + sig = attributes.get_time_signature() if not sig: return None - change = musicexp.TimeSignatureChange() change.fractions = sig - time_elm = attributes.get_maybe_exist_named_child ('time') - if time_elm and hasattr (time_elm, 'symbol'): + time_elm = attributes.get_maybe_exist_named_child('time') + if time_elm and hasattr(time_elm, 'symbol'): change.style = { 'single-number': "'single-digit", 'cut': None, 'common': None, - 'normal': "'()"}.get (time_elm.symbol, "'()") + 'normal': "'()"}.get(time_elm.symbol, "'()") else: change.style = "'()" # TODO: Handle senza-misura measures - # TODO: Handle hidden time signatures (print-object="no") + # TODO: Handle hidden time signatures(print-object="no") # 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? - + # 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 () - if not key_sig or not (isinstance (key_sig, list) or isinstance (key_sig, tuple)): - ly.warning (_ ("Unable to extract key signature!")) +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)): + ly.warning(_("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) + if len(key_sig) == 2 and not isinstance(key_sig[0], list): + # standard key signature,(fifths, mode) (fifths, mode) = key_sig change.mode = mode - start_pitch = musicexp.Pitch () + start_pitch = musicexp.Pitch() start_pitch.octave = 0 try: (n, a) = { @@ -791,7 +816,7 @@ def musicxml_key_to_lily (attributes): start_pitch.step = n start_pitch.alteration = a except KeyError: - ly.warning (_ ("unknown mode %s, expecting 'major' or 'minor' " + ly.warning(_("unknown mode %s, expecting 'major' or 'minor' " "or a church mode!") % mode) fifth = musicexp.Pitch() @@ -799,9 +824,9 @@ def musicxml_key_to_lily (attributes): if fifths < 0: fifths *= -1 fifth.step *= -1 - fifth.normalize () - for x in range (fifths): - start_pitch = start_pitch.transposed (fifth) + fifth.normalize() + for x in range(fifths): + start_pitch = start_pitch.transposed(fifth) change.tonic = start_pitch else: @@ -809,21 +834,21 @@ def musicxml_key_to_lily (attributes): # 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) + k[0] = musicxml2ly_conversion.musicxml_step_to_lily(k[0]) + alterations.append(k) change.non_standard_alterations = alterations return change -def musicxml_transpose_to_lily (attributes): - transpose = attributes.get_transposition () +def musicxml_transpose_to_lily(attributes): + transpose = attributes.get_transposition() if not transpose: return None - shift = musicexp.Pitch () - octave_change = transpose.get_maybe_exist_named_child ('octave-change') + shift = musicexp.Pitch() + octave_change = transpose.get_maybe_exist_named_child('octave-change') if octave_change: - shift.octave = string.atoi (octave_change.get_text ()) - chromatic_shift = string.atoi (transpose.get_named_child ('chromatic').get_text ()) + shift.octave = string.atoi(octave_change.get_text()) + 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), @@ -832,38 +857,38 @@ def musicxml_transpose_to_lily (attributes): shift.octave += (chromatic_shift - chromatic_shift_normalized) / 12 - diatonic = transpose.get_maybe_exist_named_child ('diatonic') + diatonic = transpose.get_maybe_exist_named_child('diatonic') if diatonic: - diatonic_step = string.atoi (diatonic.get_text ()) % 7 + diatonic_step = string.atoi(diatonic.get_text()) % 7 if diatonic_step != shift.step: # We got the alter incorrect! - old_semitones = shift.semitones () + old_semitones = shift.semitones() shift.step = diatonic_step - new_semitones = shift.semitones () + new_semitones = shift.semitones() shift.alteration += old_semitones - new_semitones - transposition = musicexp.Transposition () - transposition.pitch = musicexp.Pitch ().transposed (shift) + transposition = musicexp.Transposition() + transposition.pitch = musicexp.Pitch().transposed(shift) return transposition -def musicxml_staff_details_to_lily (attributes): - details = attributes.get_maybe_exist_named_child ('staff-details') +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') + 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); + lines = string.atoi(stafflines.get_text()); + lines_event = musicexp.StaffLinesEvent(lines); + ret.append(lines_event); return ret; -def musicxml_attributes_to_lily (attrs): +def musicxml_attributes_to_lily(attrs): elts = [] attr_dispatch = { 'clef': musicxml_clef_to_lily, @@ -872,27 +897,27 @@ def musicxml_attributes_to_lily (attrs): '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) + for (k, func) in attr_dispatch.items(): + children = attrs.get_named_children(k) if children: - ev = func (attrs) - if isinstance (ev, list): + ev = func(attrs) + if isinstance(ev, list): for e in ev: - elts.append (e) + elts.append(e) elif ev: - elts.append (ev) + elts.append(ev) return elts -def extract_display_text (el): - child = el.get_maybe_exist_named_child ("display-text") +def extract_display_text(el): + child = el.get_maybe_exist_named_child("display-text") if child: - return child.get_text () + return child.get_text() else: return False -def musicxml_print_to_lily (el): +def musicxml_print_to_lily(el): # TODO: Implement other print attributes # elts = [] - if (hasattr (el, "new-system") and conversion_settings.convert_page_layout): - val = getattr (el, "new-system") + if (hasattr(el, "new-system") and conversion_settings.convert_system_breaks): + val = getattr(el, "new-system") if (val == "yes"): - elts.append (musicexp.Break ("break")) - if (hasattr (el, "new-page") and conversion_settings.convert_page_layout): - val = getattr (el, "new-page") + elts.append(musicexp.Break("break")) + if (hasattr(el, "new-page") and conversion_settings.convert_page_breaks): + val = getattr(el, "new-page") if (val == "yes"): - elts.append (musicexp.Break ("pageBreak")) - child = el.get_maybe_exist_named_child ("part-name-display") + 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") + 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))) + elts.append(musicexp.SetEvent("Staff.shortInstrumentName", + "\"%s\"" % extract_display_text(child))) return elts -class Marker (musicexp.Music): - def __init__ (self): - self.direction = 0 - self.event = None - def print_ly (self, printer): - ly.warning (_ ("Encountered unprocessed marker %s\n") % self) - pass - def ly_expression (self): - return "" -class RepeatMarker (Marker): - def __init__ (self): - Marker.__init__ (self) - self.times = 0 -class EndingMarker (Marker): - pass - -# Convert the element to musicxml.BarLine (for non-standard barlines) -# and to RepeatMarker and EndingMarker objects for repeat and -# alternatives start/stops -def musicxml_barline_to_lily (barline): - # retval contains all possible markers in the order: - # 0..bw_ending, 1..bw_repeat, 2..barline, 3..fw_repeat, 4..fw_ending - retval = {} - bartype_element = barline.get_maybe_exist_named_child ("bar-style") - repeat_element = barline.get_maybe_exist_named_child ("repeat") - ending_element = barline.get_maybe_exist_named_child ("ending") - - bartype = None - if bartype_element: - bartype = bartype_element.get_text () - - if repeat_element and hasattr (repeat_element, 'direction'): - repeat = RepeatMarker () - repeat.direction = {"forward": -1, "backward": 1}.get (repeat_element.direction, 0) - - if ( (repeat_element.direction == "forward" and bartype == "heavy-light") or - (repeat_element.direction == "backward" and bartype == "light-heavy") ): - bartype = None - if hasattr (repeat_element, 'times'): - try: - repeat.times = int (repeat_element.times) - except ValueError: - repeat.times = 2 - repeat.event = barline - if repeat.direction == -1: - retval[3] = repeat - else: - retval[1] = repeat - - if ending_element and hasattr (ending_element, 'type'): - ending = EndingMarker () - ending.direction = {"start": -1, "stop": 1, "discontinue": 1}.get (ending_element.type, 0) - ending.event = barline - if ending.direction == -1: - retval[4] = ending - else: - retval[0] = ending - - if bartype: - b = musicexp.BarLine () - b.type = bartype - retval[2] = b - return retval.values () spanner_event_dict = { 'beam' : musicexp.BeamEvent, @@ -998,119 +960,140 @@ spanner_event_dict = { 'pedal' : musicexp.PedalEvent, 'slide' : musicexp.GlissandoEvent, 'slur' : musicexp.SlurEvent, - 'wavy-line' : musicexp.TrillSpanEvent, + 'wavy-line' : musicexp.TextSpannerEvent, 'wedge' : musicexp.HairpinEvent } spanner_type_dict = { - 'start': -1, - 'begin': -1, - 'crescendo': -1, - 'decreschendo': -1, - 'diminuendo': -1, + 'start':-1, + 'begin':-1, + 'crescendo':-1, + 'decreschendo':-1, + 'diminuendo':-1, 'continue': 0, 'change': 0, - 'up': -1, - 'down': -1, + 'up':-1, + 'down':-1, 'stop': 1, 'end' : 1 } -def musicxml_spanner_to_lily_event (mxl_event): +def musicxml_spanner_to_lily_event(mxl_event): ev = None name = mxl_event.get_name() - func = spanner_event_dict.get (name) + func = spanner_event_dict.get(name) if func: ev = func() else: - ly.warning (_ ('unknown span event %s') % mxl_event) + ly.warning(_('unknown span event %s') % mxl_event) + if name == "wavy-line": + ev.style=OrnamenthasWhat(mxl_event) - type = mxl_event.get_type () - span_direction = spanner_type_dict.get (type) + 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: - ly.warning (_ ('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') + ev.set_span_type(type) + ev.line_type = getattr(mxl_event, 'line-type', 'solid') # assign the size, which is used for octave-shift, etc. - ev.size = mxl_event.get_size () + ev.size = mxl_event.get_size() 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) +def musicxml_direction_to_indicator(direction): + 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() + txt = mxl_event.get_text() -def musicxml_fermata_to_lily_event (mxl_event): - ev = musicexp.ArticulationEvent () - txt = mxl_event.get_text () # The contents of the element defined the shape, possible are normal, angled and square - ev.type = { "angled": "shortfermata", "square": "longfermata" }.get (txt, "fermata") - if hasattr (mxl_event, 'type'): - dir = musicxml_direction_to_indicator (mxl_event.type) - if dir and options.convert_directions: - ev.force_direction = dir + ev.type = { "angled": "shortfermata", "square": "longfermata" }.get(txt, "fermata") + fermata_types= { "angled": "shortfermata", + "square": "longfermata" } + + # MusicXML fermata types can be specified in two different ways: + # 1. angled and + # 2. -- both need to be handled. + if hasattr(mxl_event, 'type'): + fermata_type = fermata_types.get(mxl_event.type, 'fermata') + else: + fermata_type = fermata_types.get(mxl_event.get_text(), 'fermata') + + ev.type = fermata_type + + if hasattr(mxl_event, 'type'): + dir = musicxml_direction_to_indicator(mxl_event.type) + if dir and options.convert_directions: + ev.force_direction = dir return ev -def musicxml_arpeggiate_to_lily_event (mxl_event): - ev = musicexp.ArpeggioEvent () - ev.direction = musicxml_direction_to_indicator (getattr (mxl_event, 'direction', None)) +def musicxml_arpeggiate_to_lily_event(mxl_event): + ev = musicexp.ArpeggioEvent() + ev.direction = musicxml_direction_to_indicator(getattr(mxl_event, 'direction', None)) return ev -def musicxml_nonarpeggiate_to_lily_event (mxl_event): - ev = musicexp.ArpeggioEvent () +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)) + 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 () +def musicxml_tremolo_to_lily_event(mxl_event): + ev = musicexp.TremoloEvent() + txt = mxl_event.get_text() if txt: - ev.bars = txt + ev.strokes = txt else: - ev.bars = "3" + # This is supposed to be a default for empty tremolo elements + # TODO: Add empty tremolo element to test cases in tremolo.xml + # TODO: Test empty tremolo element + # TODO: Consideration: Is 3 really a reasonable default? + ev.strokes = "3" return ev -def musicxml_falloff_to_lily_event (mxl_event): - ev = musicexp.BendEvent () +def musicxml_falloff_to_lily_event(mxl_event): + ev = musicexp.BendEvent() ev.alter = -4 return ev -def musicxml_doit_to_lily_event (mxl_event): - ev = musicexp.BendEvent () +def musicxml_doit_to_lily_event(mxl_event): + ev = musicexp.BendEvent() ev.alter = 4 return ev -def musicxml_bend_to_lily_event (mxl_event): - ev = musicexp.BendEvent () - ev.alter = mxl_event.bend_alter () +def musicxml_bend_to_lily_event(mxl_event): + ev = musicexp.BendEvent() + ev.alter = mxl_event.bend_alter() return ev -def musicxml_caesura_to_lily_event (mxl_event): - ev = musicexp.MarkupEvent () +def musicxml_caesura_to_lily_event(mxl_event): + ev = musicexp.MarkupEvent() # FIXME: default to straight or curved caesura? ev.contents = "\\musicglyph #\"scripts.caesura.straight\"" ev.force_direction = 1 return ev -def musicxml_fingering_event (mxl_event): - ev = musicexp.ShortArticulationEvent () - ev.type = mxl_event.get_text () +def musicxml_fingering_event(mxl_event): + ev = musicexp.ShortArticulationEvent() + ev.type = mxl_event.get_text() return ev -def musicxml_string_event (mxl_event): - ev = musicexp.NoDirectionArticulationEvent () - ev.type = mxl_event.get_text () +def musicxml_string_event(mxl_event): + ev = musicexp.NoDirectionArticulationEvent() + ev.type = mxl_event.get_text() return ev -def musicxml_accidental_mark (mxl_event): - ev = musicexp.MarkupEvent () +def musicxml_accidental_mark(mxl_event): + ev = musicexp.MarkupEvent() contents = { "sharp": "\\sharp", "natural": "\\natural", "flat": "\\flat", @@ -1124,7 +1107,7 @@ def musicxml_accidental_mark (mxl_event): "quarter-sharp": "\\semisharp", "three-quarters-flat": "\\sesquiflat", "three-quarters-sharp": "\\sesquisharp", - }.get (mxl_event.get_text ()) + }.get(mxl_event.get_text()) if contents: ev.contents = contents return ev @@ -1187,85 +1170,148 @@ articulations_dict = { } articulation_spanners = [ "wavy-line" ] -def musicxml_articulation_to_lily_event (mxl_event): +def OrnamenthasWhat(mxl_event): + wavy = trilly = ignore = start = stop = False + for i in mxl_event._parent._children: + if i._name == "wavy-line": wavy = True + elif i._name == "trill-mark": trilly = True + try: + if i.type == "continue": ignore = True + elif i.type == "start": start = True + elif i.type == "stop": stop = True + except: + pass + if start == True: + if wavy == True and trilly == False: musicexp.whatOrnament = "wave" + else: musicexp.whatOrnament = "trill" + if ignore == True: return "ignore" + elif stop == True: return "stop" + elif wavy == True and trilly == True: return "trill and wave" + elif wavy == True: return "wave" + elif trilly == True: return "trill" + +def OrnamenthasWavyline(mxl_event): + for i in mxl_event._parent._children: + if i._name == "wavy-line": return True + return False + + +def musicxml_articulation_to_lily_event(mxl_event): # wavy-line elements are treated as trill spanners, not as articulation ornaments - if mxl_event.get_name () in articulation_spanners: - return musicxml_spanner_to_lily_event (mxl_event) + if mxl_event.get_name() in articulation_spanners: + return musicxml_spanner_to_lily_event(mxl_event) - tmp_tp = articulations_dict.get (mxl_event.get_name ()) + tmp_tp = articulations_dict.get(mxl_event.get_name()) + if OrnamenthasWavyline(mxl_event): + return if not tmp_tp: return - if isinstance (tmp_tp, str): - ev = musicexp.ArticulationEvent () + if isinstance(tmp_tp, str): + ev = musicexp.ArticulationEvent() ev.type = tmp_tp - elif isinstance (tmp_tp, tuple): - ev = tmp_tp[0] () + elif isinstance(tmp_tp, tuple): + ev = tmp_tp[0]() ev.type = tmp_tp[1] else: - ev = tmp_tp (mxl_event) + ev = tmp_tp(mxl_event) # Some articulations use the type attribute, other the placement... dir = None - if hasattr (mxl_event, 'type') and options.convert_directions: - dir = musicxml_direction_to_indicator (mxl_event.type) - if hasattr (mxl_event, 'placement') and options.convert_directions: - dir = musicxml_direction_to_indicator (mxl_event.placement) + if hasattr(mxl_event, 'type') and hasattr(options, 'convert_directions') and options.convert_directions: + dir = musicxml_direction_to_indicator(mxl_event.type) + if hasattr(mxl_event, 'placement') and hasattr(options, 'convert_directions') and options.convert_directions: + dir = musicxml_direction_to_indicator(mxl_event.placement) if dir: ev.force_direction = dir return ev -def musicxml_dynamics_to_lily_event (dynentry): +def musicxml_dynamics_to_lily_event(dynentry): dynamics_available = ( "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf", - "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" ) - dynamicsname = dynentry.get_name () + "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz") + dynamicsname = dynentry.get_name() if dynamicsname == "other-dynamics": - dynamicsname = dynentry.get_text () + dynamicsname = dynentry.get_text() if not dynamicsname or dynamicsname == "#text": - return + return None if not dynamicsname in dynamics_available: # Get rid of - in tag names (illegal in ly tags!) dynamicstext = dynamicsname - dynamicsname = string.replace (dynamicsname, "-", "") + dynamicsname = string.replace(dynamicsname, "-", "") additional_definitions[dynamicsname] = dynamicsname + \ " = #(make-dynamic-script \"" + dynamicstext + "\")" - needed_additional_definitions.append (dynamicsname) - event = musicexp.DynamicsEvent () + needed_additional_definitions.append(dynamicsname) + event = musicexp.DynamicsEvent() event.type = dynamicsname return event # Convert single-color two-byte strings to numbers 0.0 - 1.0 -def hexcolorval_to_nr (hex_val): +def hexcolorval_to_nr(hex_val): try: - v = int (hex_val, 16) + v = int(hex_val, 16) if v == 255: v = 256 return v / 256. except ValueError: return 0. -def hex_to_color (hex_val): - res = re.match (r'#([0-9a-f][0-9a-f]|)([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$', hex_val, re.IGNORECASE) +def hex_to_color(hex_val): + res = re.match(r'#([0-9a-f][0-9a-f]|)([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$', hex_val, re.IGNORECASE) if res: - return map (lambda x: hexcolorval_to_nr (x), res.group (2, 3, 4)) + return map(lambda x: hexcolorval_to_nr(x), res.group(2, 3, 4)) else: return None -def musicxml_words_to_lily_event (words): - event = musicexp.TextEvent () - text = words.get_text () - text = re.sub ('^ *\n? *', '', text) - text = re.sub (' *\n? *$', '', text) +def font_size_number_to_lily_command(size): + d = { + (0, 8): r'\teeny', + (8, 10): r'\tiny', + (10, 12): r'\small', + (12, 16): r'', + (16, 24): r'\large', + (24, float('inf')): r'\huge', + } + result = None + for r in d.keys(): + if r[0] <= size < r[1]: + result = d[r] + break + return result + +def font_size_word_to_lily_command(size): + font_size_dict = { + "xx-small": '\\teeny', + "x-small": '\\tiny', + "small": '\\small', + "medium": '', + "large": '\\large', + "x-large": '\\huge', + "xx-large": '\\larger\\huge' + } + return font_size_dict.get(size, '') + +def get_font_size(size): + try: + size = float(size) + return font_size_number_to_lily_command(size) + except ValueError: + return font_size_word_to_lily_command(size) + +def musicxml_words_to_lily_event(words): + event = musicexp.TextEvent() + text = words.get_text() + text = re.sub('^ *\n? *', '', text) #remove white spaces and line breaks before text + text = re.sub(' *\n? *$', '', text) #remove white spaces and line breaks before text event.text = text - if hasattr (words, 'default-y') and options.convert_directions: - offset = getattr (words, 'default-y') + if hasattr(words, 'default-y') and hasattr(options, 'convert_directions') and options.convert_directions: + offset = getattr(words, 'default-y') try: - off = string.atoi (offset) + off = string.atoi(offset) if off > 0: event.force_direction = 1 else: @@ -1273,33 +1319,26 @@ def musicxml_words_to_lily_event (words): except ValueError: event.force_direction = 0 - if hasattr (words, 'font-weight'): - font_weight = { "normal": '', "bold": '\\bold' }.get (getattr (words, 'font-weight'), '') + if hasattr(words, 'font-weight'): + font_weight = { "normal": '', "bold": '\\bold' }.get(getattr(words, 'font-weight'), '') if font_weight: event.markup += font_weight - if hasattr (words, 'font-size'): - size = getattr (words, 'font-size') - font_size = { - "xx-small": '\\teeny', - "x-small": '\\tiny', - "small": '\\small', - "medium": '', - "large": '\\large', - "x-large": '\\huge', - "xx-large": '\\larger\\huge' - }.get (size, '') + if hasattr(words, 'font-size'): + size = getattr(words, 'font-size') + # font_size = font_size_dict.get(size, '') + font_size = get_font_size(size) if font_size: event.markup += font_size - if hasattr (words, 'color'): - color = getattr (words, 'color') - rgb = hex_to_color (color) + if hasattr(words, 'color'): + color = getattr(words, 'color') + rgb = hex_to_color(color) if rgb: event.markup += "\\with-color #(rgb-color %s %s %s)" % (rgb[0], rgb[1], rgb[2]) - if hasattr (words, 'font-style'): - font_style = { "italic": '\\italic' }.get (getattr (words, 'font-style'), '') + if hasattr(words, 'font-style'): + font_style = { "italic": '\\italic' }.get(getattr(words, 'font-style'), '') if font_style: event.markup += font_style @@ -1315,24 +1354,24 @@ def musicxml_words_to_lily_event (words): # 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.di.unimi.it/LSR/Item?id=194 -def musicxml_accordion_to_markup (mxl_event): +# 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') + high = mxl_event.get_maybe_exist_named_child('accordion-high') if high: commandname += "H" command += """\\combine \\raise #2.5 \\musicglyph #\"accordion.dot\" """ - middle = mxl_event.get_maybe_exist_named_child ('accordion-middle') + 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 ()) + txt = string.atoi(middle.get_text()) except ValueError: pass if txt == 3: @@ -1356,7 +1395,7 @@ def musicxml_accordion_to_markup (mxl_event): command += """\\combine \\raise #1.5 \\musicglyph #\"accordion.dot\" """ - low = mxl_event.get_maybe_exist_named_child ('accordion-low') + low = mxl_event.get_maybe_exist_named_child('accordion-low') if low: commandname += "L" command += """\\combine @@ -1367,98 +1406,100 @@ def musicxml_accordion_to_markup (mxl_event): 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) + needed_additional_definitions.append(commandname) return "\\%s" % commandname -def musicxml_accordion_to_ly (mxl_event): - txt = musicxml_accordion_to_markup (mxl_event) +def musicxml_accordion_to_ly(mxl_event): + txt = musicxml_accordion_to_markup(mxl_event) if txt: - ev = musicexp.MarkEvent (txt) + ev = musicexp.MarkEvent(txt) return ev return -def musicxml_rehearsal_to_ly_mark (mxl_event): - text = mxl_event.get_text () +def musicxml_rehearsal_to_ly_mark(mxl_event): + text = mxl_event.get_text() if not text: return # default is boxed rehearsal marks! encl = "box" - if hasattr (mxl_event, 'enclosure'): - encl = {"none": None, "square": "box", "circle": "circle" }.get (mxl_event.enclosure, None) + if hasattr(mxl_event, 'enclosure'): + encl = {"none": None, "square": "box", "circle": "circle" }.get(mxl_event.enclosure, None) if encl: text = "\\%s { %s }" % (encl, text) - ev = musicexp.MarkEvent ("\\markup { %s }" % text) + ev = musicexp.MarkEvent("\\markup { %s }" % text) return ev -def musicxml_harp_pedals_to_ly (mxl_event): +def musicxml_harp_pedals_to_ly(mxl_event): count = 0 result = "\\harp-pedal #\"" - for t in mxl_event.get_named_children ('pedal-tuning'): - alter = t.get_named_child ('pedal-alter') + for t in mxl_event.get_named_children('pedal-tuning'): + alter = t.get_named_child('pedal-alter') if alter: - val = int (alter.get_text ().strip ()) - result += {1: "v", 0: "-", -1: "^"}.get (val, "") + val = int(alter.get_text().strip()) + result += {1: "v", 0: "-", -1: "^"}.get(val, "") count += 1 if count == 3: result += "|" - ev = musicexp.MarkupEvent () + ev = musicexp.MarkupEvent() ev.contents = result + "\"" return ev -def musicxml_eyeglasses_to_ly (mxl_event): - needed_additional_definitions.append ("eyeglasses") - return musicexp.MarkEvent ("\\markup { \\eyeglasses }") +def musicxml_eyeglasses_to_ly(mxl_event): + needed_additional_definitions.append("eyeglasses") + return musicexp.MarkEvent("\\markup { \\eyeglasses }") -def next_non_hash_index (lst, pos): +def next_non_hash_index(lst, pos): pos += 1 - while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text): + while pos < len(lst) and isinstance(lst[pos], musicxml.Hash_text): pos += 1 return pos -def musicxml_metronome_to_ly (mxl_event): - children = mxl_event.get_all_children () +def musicxml_metronome_to_ly(mxl_event): + children = mxl_event.get_all_children() if not children: return index = -1 - index = next_non_hash_index (children, index) - if isinstance (children[index], musicxml.BeatUnit): + index = next_non_hash_index(children, index) + 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'): - ev.set_parentheses (mxl_event.parentheses == "yes") - - d = musicexp.Duration () - d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ()) - index = next_non_hash_index (children, index) - if isinstance (children[index], musicxml.BeatUnitDot): + ev = musicexp.TempoMark() + if hasattr(mxl_event, 'parentheses'): + ev.set_parentheses(mxl_event.parentheses == "yes") + + d = musicexp.Duration() + d.duration_log = utilities.musicxml_duration_to_log( + children[index].get_text()) + index = next_non_hash_index(children, index) + if isinstance(children[index], musicxml.BeatUnitDot): d.dots = 1 - index = next_non_hash_index (children, index) - ev.set_base_duration (d) - if isinstance (children[index], musicxml.BeatUnit): + index = next_non_hash_index(children, index) + ev.set_base_duration(d) + if isinstance(children[index], musicxml.BeatUnit): # Form "note = newnote" - newd = musicexp.Duration () - newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ()) - index = next_non_hash_index (children, index) - if isinstance (children[index], musicxml.BeatUnitDot): + newd = musicexp.Duration() + newd.duration_log = utilities.musicxml_duration_to_log( + children[index].get_text()) + index = next_non_hash_index(children, index) + if isinstance(children[index], musicxml.BeatUnitDot): newd.dots = 1 - index = next_non_hash_index (children, index) - ev.set_new_duration (newd) - elif isinstance (children[index], musicxml.PerMinute): + index = next_non_hash_index(children, index) + ev.set_new_duration(newd) + elif isinstance(children[index], musicxml.PerMinute): # Form "note = bpm" try: - beats = int (children[index].get_text ()) - ev.set_beats_per_minute (beats) + beats = int(children[index].get_text()) + ev.set_beats_per_minute(beats) except ValueError: pass else: - ly.warning (_ ("Unknown metronome mark, ignoring")) + ly.warning(_("Unknown metronome mark, ignoring")) return return ev else: #TODO: Implement the other (more complex) way for tempo marks! - ly.warning (_ ("Metronome marks with complex relations ( in MusicXML) are not yet implemented.")) + ly.warning(_("Metronome marks with complex relations ( in MusicXML) are not yet implemented.")) return # translate directions into Events, possible values: @@ -1481,85 +1522,53 @@ directions_dict = { } directions_spanners = [ 'octave-shift', 'pedal', 'wedge', 'dashes', 'bracket' ] -def musicxml_direction_to_lily (n): +def musicxml_direction_to_lily(n): # TODO: Handle the element! res = [] # placement applies to all children! dir = None - if hasattr (n, 'placement') and options.convert_directions: - dir = musicxml_direction_to_indicator (n.placement) + if hasattr(n, 'placement') and hasattr(options, 'convert_directions') 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), # so we can't simply flatten them out! - for dt in n.get_typed_children (musicxml.DirType): - dirtype_children += dt.get_all_children () + for dt in n.get_typed_children(musicxml.DirType): + dirtype_children += dt.get_all_children() for entry in dirtype_children: - # backets, dashes, octave shifts. pedal marks, hairpins etc. are spanners: + # brackets, dashes, octave shifts. pedal marks, hairpins etc. are spanners: if entry.get_name() in directions_spanners: - event = musicxml_spanner_to_lily_event (entry) + event = musicxml_spanner_to_lily_event(entry) if event: - res.append (event) + event.force_direction=dir + res.append(event) continue # now treat all the "simple" ones, that can be translated using the dict ev = None - tmp_tp = directions_dict.get (entry.get_name (), None) - if isinstance (tmp_tp, str): # string means MarkEvent - ev = musicexp.MarkEvent (tmp_tp) - elif isinstance (tmp_tp, tuple): # tuple means (EventClass, "text") - ev = tmp_tp[0] (tmp_tp[1]) + tmp_tp = directions_dict.get(entry.get_name(), None) + if isinstance(tmp_tp, str): # string means MarkEvent + ev = musicexp.MarkEvent(tmp_tp) + elif isinstance(tmp_tp, tuple): # tuple means (EventClass, "text") + ev = tmp_tp[0](tmp_tp[1]) elif tmp_tp: - ev = tmp_tp (entry) + ev = tmp_tp(entry) if ev: # TODO: set the correct direction! Unfortunately, \mark in ly does # not seem to support directions! ev.force_direction = dir - res.append (ev) + res.append(ev) continue - if entry.get_name () == "dynamics": - for dynentry in entry.get_all_children (): - ev = musicxml_dynamics_to_lily_event (dynentry) + if entry.get_name() == "dynamics": + for dynentry in entry.get_all_children(): + ev = musicxml_dynamics_to_lily_event(dynentry) if ev: - res.append (ev) - - return res - -def musicxml_frame_to_lily_event (frame): - ev = musicexp.FretEvent () - ev.strings = frame.get_strings () - ev.frets = frame.get_frets () - #offset = frame.get_first_fret () - 1 - barre = [] - for fn in frame.get_named_children ('frame-note'): - fret = fn.get_fret () - if fret <= 0: - fret = "o" - el = [ fn.get_string (), fret ] - fingering = fn.get_fingering () - if fingering >= 0: - el.append (fingering) - ev.elements.append (el) - b = fn.get_barre () - if b == 'start': - barre[0] = el[0] # start string - barre[2] = el[1] # fret - elif b == 'stop': - barre[1] = el[0] # end string - if barre: - ev.barre = barre - return ev + ev.force_direction = dir + res.append(ev) -def musicxml_harmony_to_lily (n): - res = [] - for f in n.get_named_children ('frame'): - ev = musicxml_frame_to_lily_event (f) - if ev: - res.append (ev) return res - notehead_styles_dict = { 'slash': '\'slash', 'triangle': '\'triangle', @@ -1585,63 +1594,44 @@ notehead_styles_dict = { 'ti': '\'ti', } -def musicxml_notehead_to_lily (nh): - styles = [] - - # Notehead style - style = notehead_styles_dict.get (nh.get_text ().strip (), None) - style_elm = musicexp.NotestyleEvent () - if style: - style_elm.style = style - if hasattr (nh, 'filled'): - style_elm.filled = (getattr (nh, 'filled') == "yes") - if style_elm.style or (style_elm.filled != None): - styles.append (style_elm) - - # parentheses - if hasattr (nh, 'parentheses') and (nh.parentheses == "yes"): - styles.append (musicexp.ParenthesizeEvent ()) - - return styles - -def musicxml_chordpitch_to_lily (mxl_cpitch): - r = musicexp.ChordPitch () - r.alteration = mxl_cpitch.get_alteration () - r.step = musicxml_step_to_lily (mxl_cpitch.get_step ()) +def musicxml_chordpitch_to_lily(mxl_cpitch): + r = musicexp.ChordPitch() + r.alteration = mxl_cpitch.get_alteration() + r.step = musicxml2ly_conversion.musicxml_step_to_lily(mxl_cpitch.get_step()) return r chordkind_dict = { - 'major': '5', - 'minor': 'm5', - 'augmented': 'aug5', - 'diminished': 'dim5', + 'major': r'{}:5', + 'minor': r'{}:m5', + 'augmented': r'{}:aug5', + 'diminished': r'{}:dim5', # Sevenths: - 'dominant': '7', - 'dominant-seventh': '7', - 'major-seventh': 'maj7', - 'minor-seventh': 'm7', - 'diminished-seventh': 'dim7', - 'augmented-seventh': 'aug7', - 'half-diminished': 'dim5m7', - 'major-minor': 'maj7m5', + 'dominant': r'{}:7', + 'dominant-seventh': r'{}:7', + 'major-seventh': r'{}:maj7', + 'minor-seventh': r'{}:m7', + 'diminished-seventh': r'{}:dim7', + 'augmented-seventh': r'{}:aug7', + 'half-diminished': r'{}:dim5m7', + 'major-minor': r'{}:maj7m5', # Sixths: - 'major-sixth': '6', - 'minor-sixth': 'm6', + 'major-sixth': r'{}:6', + 'minor-sixth': r'{}:m6', # Ninths: - 'dominant-ninth': '9', - 'major-ninth': 'maj9', - 'minor-ninth': 'm9', + 'dominant-ninth': r'{}:9', + 'major-ninth': r'{}:maj9', + 'minor-ninth': r'{}:m9', # 11ths (usually as the basis for alteration): - 'dominant-11th': '11', - 'major-11th': 'maj11', - 'minor-11th': 'm11', + 'dominant-11th': r'{}:11', + 'major-11th': r'{}:maj11', + 'minor-11th': r'{}:m11', # 13ths (usually as the basis for alteration): - 'dominant-13th': '13.11', - 'major-13th': 'maj13.11', - 'minor-13th': 'm13', + 'dominant-13th': r'{}:13.11', + 'major-13th': r'{}:maj13.11', + 'minor-13th': r'{}:m13', # Suspended: - 'suspended-second': 'sus2', - 'suspended-fourth': 'sus4', + 'suspended-second': r'{}:sus2', + 'suspended-fourth': r'{}:sus4', # Functional sixths: # TODO #'Neapolitan': '???', @@ -1650,34 +1640,118 @@ chordkind_dict = { #'German': '???', # Other: #'pedal': '???',(pedal-point bass) - 'power': '5^3', + 'power': r'\powerChords {}:1.5', #'Tristan': '???', - 'other': '1', + 'other': r'{}:1', 'none': None, } -def musicxml_chordkind_to_lily (kind): - res = chordkind_dict.get (kind, None) +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: - ly.warning (_ ("Unable to convert chord type %s to lilypond.") % kind) + ly.warning(_("Unable to convert chord type %s to lilypond.") % kind) + return res + + +# Global variable for guitar string tunings +string_tunings = None + +def musicxml_get_string_tunings(lines): + global string_tunings + if (string_tunings == None): + if not lines: + lines = 6 + string_tunings = [musicexp.Pitch()] * lines + for i in range(0, lines): + p = musicexp.Pitch() + p.step = musicxml2ly_conversion.musicxml_step_to_lily(((("E","A","D","G","B")*(lines/5+1))[0:lines])[i]) + p.octave = (([-2+int(x%5>1)+2*(x/5) for x in range(0,lines)][0:lines])[i]) + p.alteration = 0 + p._force_absolute_pitch = True + string_tunings[i] = p + string_tunings = string_tunings[::-1] + return string_tunings[0:lines] + +def musicxml_frame_to_lily_event(frame): + ev = musicexp.FretEvent() + ev.strings = frame.get_strings() + ev.frets = frame.get_frets() + #offset = frame.get_first_fret() - 1 + #offset = frame.get_first_fret() + barre = [] + open_strings = range(1,ev.strings+1) + for fn in frame.get_named_children('frame-note'): + fret = fn.get_fret() + if fret <= 0: + fret = "o" + el = [ fn.get_string(), fret ] + fingering = fn.get_fingering() + if fingering >= 0: + el.append(fingering) + ev.elements.append(el) + open_strings.remove(fn.get_string()) + b = fn.get_barre() + if b == 'start': + barre.append(el[0]) # start string + barre.append(el[1]) # fret + elif b == 'stop': + barre.insert(1, el[0]) # end string + for string in open_strings: + ev.elements.append([string, 'x']) + ev.elements.sort() + ev.elements.reverse() + if barre: + ev.barre = barre + return ev + +def musicxml_harmony_to_lily(n): + res = [] + for f in n.get_named_children('frame'): + ev = musicxml_frame_to_lily_event(f) + if ev: + res.append(ev) + return res + +def musicxml_harmony_to_lily_fretboards(n): + res = [] + frame = n.get_maybe_exist_named_child('frame') + if frame: + strings = frame.get_strings() + if not strings: + strings = 6 + tunings = musicxml_get_string_tunings(strings) + ev = musicexp.FretBoardEvent() + #barre = [] + for fn in frame.get_named_children('frame-note'): + fbn = musicexp.FretBoardNote() + string = fn.get_string() + fbn.string = string + fingering = fn.get_fingering() + if fingering >= 0: + fbn.fingering = fingering + p = tunings[string-1].copy() + p.add_semitones(fn.get_fret()) + fbn.pitch = p + ev.append(fbn) + res.append(ev) return res -def musicxml_harmony_to_lily_chordname (n): +def musicxml_harmony_to_lily_chordname(n): res = [] - root = n.get_maybe_exist_named_child ('root') + root = n.get_maybe_exist_named_child('root') if root: - ev = musicexp.ChordNameEvent () - ev.root = musicxml_chordpitch_to_lily (root) - kind = n.get_maybe_exist_named_child ('kind') + ev = musicexp.ChordNameEvent() + ev.root = musicxml_chordpitch_to_lily(root) + kind = n.get_maybe_exist_named_child('kind') if kind: - ev.kind = musicxml_chordkind_to_lily (kind.get_text ()) + ev.kind = musicxml_chordkind_to_lily(kind.get_text()) if not ev.kind: return res - bass = n.get_maybe_exist_named_child ('bass') + bass = n.get_maybe_exist_named_child('bass') if bass: - ev.bass = musicxml_chordpitch_to_lily (bass) - inversion = n.get_maybe_exist_named_child ('inversion') + 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? @@ -1689,17 +1763,17 @@ def musicxml_harmony_to_lily_chordname (n): # 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 ()) + inversion_count = string.atoi(inversion.get_text()) if inversion_count == 1: # TODO: Calculate the bass note for the inversion... pass pass - for deg in n.get_named_children ('degree'): - d = musicexp.ChordModification () - d.type = deg.get_type () - d.step = deg.get_value () - d.alteration = deg.get_alter () - ev.add_modification (d) + for deg in n.get_named_children('degree'): + d = musicexp.ChordModification() + d.type = deg.get_type() + d.step = deg.get_value() + d.alteration = deg.get_alter() + ev.add_modification(d) #TODO: convert the user-symbols attribute: #major: a triangle, like Unicode 25B3 #minor: -, like Unicode 002D @@ -1707,12 +1781,11 @@ def musicxml_harmony_to_lily_chordname (n): #diminished: (degree), like Unicode 00B0 #half-diminished: (o with slash), like Unicode 00F8 if ev and ev.root: - res.append (ev) - + res.append(ev) return res -def musicxml_figured_bass_note_to_lily (n): - res = musicexp.FiguredBassNote () +def musicxml_figured_bass_note_to_lily(n): + res = musicexp.FiguredBassNote() suffix_dict = { 'sharp' : "+", 'flat' : "-", 'natural' : "!", @@ -1720,16 +1793,16 @@ def musicxml_figured_bass_note_to_lily (n): 'flat-flat' : "--", 'sharp-sharp' : "++", 'slash' : "/" } - prefix = n.get_maybe_exist_named_child ('prefix') + prefix = n.get_maybe_exist_named_child('prefix') if prefix: - res.set_prefix (suffix_dict.get (prefix.get_text (), "")) - fnumber = n.get_maybe_exist_named_child ('figure-number') + res.set_prefix(suffix_dict.get(prefix.get_text(), "")) + fnumber = n.get_maybe_exist_named_child('figure-number') if fnumber: - res.set_number (fnumber.get_text ()) - suffix = n.get_maybe_exist_named_child ('suffix') + res.set_number(fnumber.get_text()) + suffix = n.get_maybe_exist_named_child('suffix') if suffix: - res.set_suffix (suffix_dict.get (suffix.get_text (), "")) - if n.get_maybe_exist_named_child ('extend'): + 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 # to use \set useBassFigureExtenders = ##t, which turns them on # globally, while MusicXML has a property for each note... @@ -1740,24 +1813,24 @@ def musicxml_figured_bass_note_to_lily (n): -def musicxml_figured_bass_to_lily (n): - if not isinstance (n, musicxml.FiguredBass): +def musicxml_figured_bass_to_lily(n): + if not isinstance(n, musicxml.FiguredBass): return - res = musicexp.FiguredBassEvent () - for i in n.get_named_children ('figure'): - note = musicxml_figured_bass_note_to_lily (i) + res = musicexp.FiguredBassEvent() + for i in n.get_named_children('figure'): + note = musicxml_figured_bass_note_to_lily(i) if note: - res.append (note) - dur = n.get_maybe_exist_named_child ('duration') + res.append(note) + dur = n.get_maybe_exist_named_child('duration') if dur: # apply the duration to res length = Rational(int(dur.get_text()), n._divisions) * Rational(1, 4) - res.set_real_duration (length) - duration = rational_to_lily_duration (length) + res.set_real_duration(length) + duration = musicxml2ly_conversion.rational_to_lily_duration(length) if duration: - res.set_duration (duration) - if hasattr (n, 'parentheses') and n.parentheses == "yes": - res.set_parentheses (True) + res.set_duration(duration) + if hasattr(n, 'parentheses') and n.parentheses == "yes": + res.set_parentheses(True) return res instrument_drumtype_dict = { @@ -1769,85 +1842,26 @@ instrument_drumtype_dict = { 'Bass Drum': 'bassdrum', } -def musicxml_note_to_lily_main_event (n): - pitch = None - duration = None - event = None - - mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch) - if mxl_pitch: - pitch = musicxml_pitch_to_lily (mxl_pitch) - event = musicexp.NoteEvent () - event.pitch = pitch - - acc = n.get_maybe_exist_named_child ('accidental') - if acc: - # AccidentalCautionary in lily has parentheses - # so treat accidental explicitly in parentheses as cautionary - if hasattr(acc, 'parentheses') and acc.parentheses == "yes": - event.cautionary = True - else: - 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) - - 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 - rest = n.get_maybe_exist_typed_child (musicxml.Rest) - event = musicexp.RestEvent () - if options.convert_rest_positions: - pitch = musicxml_restdisplay_to_lily (rest) - event.pitch = pitch - - elif n.instrument_name: - event = musicexp.NoteEvent () - drum_type = instrument_drumtype_dict.get (n.instrument_name) - if drum_type: - event.drum_type = drum_type - else: - 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")) - if event: - event.duration = musicxml_duration_to_lily (n) - noteheads = n.get_named_children ('notehead') - for nh in noteheads: - styles = musicxml_notehead_to_lily (nh) - for s in styles: - event.add_associated_event (s) - - return event - -def musicxml_lyrics_to_text (lyrics): +def musicxml_lyrics_to_text(lyrics, ignoremelismata): # TODO: Implement text styles for lyrics syllables continued = False extended = False text = '' - for e in lyrics.get_all_children (): - if isinstance (e, musicxml.Syllabic): - continued = e.continued () - elif isinstance (e, musicxml.Text): + for e in lyrics.get_all_children(): + if isinstance(e, musicxml.Syllabic): + continued = e.continued() + elif isinstance(e, musicxml.Text): # We need to convert soft hyphens to -, otherwise the ascii codec as well # as lilypond will barf on that character - text += string.replace( e.get_text(), u'\xad', '-' ) - elif isinstance (e, musicxml.Elision): + text += string.replace(e.get_text(), u'\xad', '-') + elif isinstance(e, musicxml.Elision): if text: text += " " continued = False extended = False - elif isinstance (e, musicxml.Extend): + elif isinstance(e, musicxml.Extend): if text: text += " " extended = True @@ -1857,195 +1871,298 @@ def musicxml_lyrics_to_text (lyrics): elif text == "_" and extended: return "__" elif continued and text: - return musicxml.escape_ly_output_string (text) + " --" + if hasattr(options, 'convert_beaming') and options.convert_beaming: + if (ignoremelismata == "on"): + return " \set ignoreMelismata = ##t " + utilities.escape_ly_output_string (text) + elif (ignoremelismata == "off"): + return " " + utilities.escape_ly_output_string(text) + " -- \unset ignoreMelismata" + else: + return " " + utilities.escape_ly_output_string(text) + " --" + else: + return " " + utilities.escape_ly_output_string(text) + " -- " elif continued: return "--" elif extended and text: - return musicxml.escape_ly_output_string (text) + " __" + return " " + utilities.escape_ly_output_string(text) + " __" elif extended: return "__" elif text: - return musicxml.escape_ly_output_string (text) + return " " + utilities.escape_ly_output_string(text) else: return "" ## TODO class NegativeSkip: - def __init__ (self, here, dest): + def __init__(self, here, dest): self.here = here self.dest = dest class LilyPondVoiceBuilder: - def __init__ (self): + def __init__(self): self.elements = [] self.pending_dynamics = [] - self.end_moment = Rational (0) - self.begin_moment = Rational (0) - self.pending_multibar = Rational (0) + self.end_moment = Rational(0) + self.begin_moment = Rational(0) + self.pending_multibar = Rational(0) self.ignore_skips = False self.has_relevant_elements = False - self.measure_length = Rational (4, 4) + self.measure_length = Rational(4, 4) + self.stay_here = False - def _insert_multibar (self): - layout_information.set_context_item ('Score', 'skipBars = ##t') - r = musicexp.MultiMeasureRest () + def _insert_multibar(self): + layout_information.set_context_item('Score', 'skipBars = ##t') + r = musicexp.MultiMeasureRest() lenfrac = self.measure_length - r.duration = rational_to_lily_duration (lenfrac) + r.duration = musicxml2ly_conversion.rational_to_lily_duration(lenfrac) r.duration.factor *= self.pending_multibar / lenfrac - self.elements.append (r) + self.elements.append(r) self.begin_moment = self.end_moment self.end_moment = self.begin_moment + self.pending_multibar - self.pending_multibar = Rational (0) + self.pending_multibar = Rational(0) - def set_measure_length (self, mlen): + def set_measure_length(self, mlen): if (mlen != self.measure_length) and self.pending_multibar: - self._insert_multibar () + self._insert_multibar() self.measure_length = mlen - def add_multibar_rest (self, duration): + def add_multibar_rest(self, duration): self.pending_multibar += duration - def set_duration (self, duration): + def set_duration(self, duration): self.end_moment = self.begin_moment + duration - def current_duration (self): + 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): - self._insert_multibar () + def add_pending_dynamics(self): + for d in self.pending_dynamics: + self.elements.append(d) + self.pending_dynamics = [] + + def add_music(self, music, duration, relevant=True): + assert isinstance(music, musicexp.Music) + if self.pending_multibar > Rational(0): + self._insert_multibar() self.has_relevant_elements = self.has_relevant_elements or relevant - self.elements.append (music) + + if isinstance(music, musicexp.BarLine): + if self.pending_dynamics: + for d in self.pending_dynamics: + if not isinstance(d, (musicexp.SpanEvent, musicexp.DynamicsEvent)): + index = self.pending_dynamics.index(d) + dyn = self.pending_dynamics.pop(index) + self.elements.append(dyn) + + self.elements.append(music) self.begin_moment = self.end_moment - self.set_duration (duration) + 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: - music.append (d) - self.pending_dynamics = [] + if isinstance(music, musicexp.ChordEvent) and self.pending_dynamics: + self.add_pending_dynamics() # Insert some music command that does not affect the position in the measure - def add_command (self, command, relevant=True): - assert isinstance (command, musicexp.Music) - if self.pending_multibar > Rational (0): - self._insert_multibar () + def add_command(self, command, relevant=True): + assert isinstance(command, musicexp.Music) + if self.pending_multibar > Rational(0): + self._insert_multibar() self.has_relevant_elements = self.has_relevant_elements or relevant - self.elements.append (command) - def add_barline (self, barline, relevant=False): + self.elements.append(command) + def add_barline(self, barline, relevant=False): # 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 - (self.pending_multibar > Rational (0))): - self.add_music (barline, Rational (0)) + (self.pending_multibar > Rational(0))): + + self.add_music(barline, Rational(0)) + self.has_relevant_elements = has_relevant or relevant - def add_partial (self, command): + + def add_partial(self, command): self.ignore_skips = True # insert the partial, but restore relevant_elements (partial is not relevant) relevant = self.has_relevant_elements - self.add_command (command) + self.add_command(command) self.has_relevant_elements = relevant - def add_dynamics (self, dynamic): + def add_dynamics(self, dynamic): # store the dynamic item(s) until we encounter the next note/rest: - self.pending_dynamics.append (dynamic) + self.pending_dynamics.append(dynamic) - def add_bar_check (self, number): + def add_bar_check(self, number): # re/store has_relevant_elements, so that a barline alone does not # trigger output for figured bass, chord names - b = musicexp.BarLine () + b = musicexp.BarLine() b.bar_number = number - self.add_barline (b) - - def jumpto (self, moment): - current_end = self.end_moment + self.pending_multibar - diff = moment - current_end - - if diff < Rational (0): - 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): - skip = musicexp.SkipEvent() - 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 - # might not start at a measure boundary! - if duration_log > 0: # denominator is a power of 2... - if diff.numerator () == 3: - duration_log -= 1 - duration_dots = 1 + self.add_barline(b) + + def jumpto(self, moment): + if (not self.stay_here): + current_end = self.end_moment + self.pending_multibar + diff = moment - current_end + + if diff < Rational(0): + 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): + skip = musicexp.SkipEvent() + 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 + # might not start at a measure boundary! + if duration_log > 0: # denominator is a power of 2... + if diff.numerator() == 3: + duration_log -= 1 + duration_dots = 1 + else: + duration_factor = Rational(diff.numerator()) 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 - skip.duration.factor = duration_factor - skip.duration.dots = duration_dots - - evc = musicexp.ChordEvent () - evc.elements.append (skip) - self.add_music (evc, diff, False) + # for skips of a whole or more, simply use s1*factor + duration_log = 0 + duration_factor = diff + skip.duration.duration_log = duration_log + skip.duration.factor = duration_factor + skip.duration.dots = duration_dots - if diff > Rational (0) and moment == 0: - self.ignore_skips = False + evc = musicexp.ChordEvent() + evc.elements.append(skip) + self.add_music(evc, diff, False) - def last_event_chord (self, starting_at): + if diff > Rational(0) and moment == 0: + self.ignore_skips = False + def last_event_chord(self, starting_at): value = None # if the position matches, find the last ChordEvent, do not cross a bar line! at = len(self.elements) - 1 while (at >= 0 and - not isinstance (self.elements[at], musicexp.ChordEvent) and - not isinstance (self.elements[at], musicexp.BarLine)): + not isinstance(self.elements[at], musicexp.ChordEvent) and + not isinstance(self.elements[at], musicexp.BarLine)): at -= 1 if (self.elements and at >= 0 - and isinstance (self.elements[at], musicexp.ChordEvent) + and isinstance(self.elements[at], musicexp.ChordEvent) and self.begin_moment == starting_at): value = self.elements[at] else: - self.jumpto (starting_at) + self.jumpto(starting_at) value = None return value - def correct_negative_skip (self, goto): + def correct_negative_skip(self, goto): self.end_moment = goto self.begin_moment = goto - evc = musicexp.ChordEvent () - self.elements.append (evc) - + evc = musicexp.ChordEvent() + self.elements.append(evc) class VoiceData: - def __init__ (self): + def __init__(self): self.voicename = None self.voicedata = None self.ly_voice = None self.figured_bass = None self.chordnames = None + self.fretboards = None self.lyrics_dict = {} self.lyrics_order = [] -def measure_length_from_attributes (attr, current_measure_length): - len = attr.get_measure_length () +def measure_length_from_attributes(attr, current_measure_length): + len = attr.get_measure_length() if not len: len = current_measure_length return len -def musicxml_voice_to_lily_voice (voice): +def music_xml_voice_name_to_lily_name(part_id, name): + str = "Part%sVoice%s" % (part_id, name) + return musicxml_id_to_lily(str) + +def music_xml_lyrics_name_to_lily_name(part_id, name, lyricsnr): + str = music_xml_voice_name_to_lily_name(part_id, name)+("Lyrics%s" % lyricsnr) + return musicxml_id_to_lily(str) + +def music_xml_figuredbass_name_to_lily_name(part_id, voicename): + str = music_xml_voice_name_to_lily_name(part_id, voicename)+"FiguredBass" + return musicxml_id_to_lily(str) + +def music_xml_chordnames_name_to_lily_name(part_id, voicename): + str = music_xml_voice_name_to_lily_name(part_id, voicename)+"Chords" + return musicxml_id_to_lily(str) + +def music_xml_fretboards_name_to_lily_name(part_id, voicename): + str = music_xml_voice_name_to_lily_name(part_id, voicename)+"FretBoards" + return musicxml_id_to_lily(str) + +def get_all_lyric_parts_in_voice(voice): + ''' + Collect the indexes of all lyric parts in this voice. + In case not all of the current lyric parts are active (a typical case would be + a refrain/chorus), the current implementation inserts \skip-commands in the + inactive parts to keep them in sync. + ''' + all_lyric_parts = [] + for elem in voice._elements: + lyrics = elem.get_typed_children(musicxml.Lyric) + if lyrics: + for lyric in lyrics: + index = lyric.number + if not index in all_lyric_parts: + all_lyric_parts.append(index) + return all_lyric_parts + + +def extract_lyrics(voice, lyric_key, lyrics_dict): + curr_number = None + result = [] + + def is_note(elem): + return isinstance(elem, musicxml.Note) + + def is_rest(elem): + return elem.get_typed_children(musicxml.Rest) + + def is_note_and_not_rest(elem): + return is_note(elem) and not is_rest(elem) + + def get_lyric_elements(note): + return note.get_typed_children(musicxml.Lyric) + + def has_lyric_belonging_to_lyric_part(note, lyric_part_id): + lyric_elements = get_lyric_elements(note) + lyric_numbers = [lyric.number for lyric in lyric_elements] + return any([lyric_number == lyric_part_id for lyric_number in lyric_numbers]) + + for idx, elem in enumerate(voice._elements): + lyrics = get_lyric_elements(elem) + lyric_keys = [lyric.number for lyric in lyrics] + note_has_lyric_belonging_to_lyric_part = lyric_key in lyric_keys + # Current note has lyric with 'number' matching 'lyric_key'. + if note_has_lyric_belonging_to_lyric_part: + for lyric in lyrics: + if lyric.number == lyric_key: + text = musicxml_lyrics_to_text(lyric, None) + result.append(text) + # Note has any lyric. + elif get_lyric_elements(elem) and \ + not note_has_lyric_belonging_to_lyric_part: + result.append('\skip1 ') + # Note does not have any lyric attached to it. + elif is_note_and_not_rest(elem): + result.append('\skip1 ') + + lyrics_dict[lyric_key].extend(result) + +def musicxml_voice_to_lily_voice(voice): tuplet_events = [] modes_found = {} lyrics = {} - return_value = VoiceData () + return_value = VoiceData() return_value.voicedata = voice # First pitch needed for relative mode (if selected in command-line options) @@ -2062,153 +2179,190 @@ def musicxml_voice_to_lily_voice (voice): pending_figured_bass = [] pending_chordnames = [] + pending_fretboards = [] # Make sure that the keys in the dict don't get reordered, since # we need the correct ordering of the lyrics stanzas! By default, # a dict will reorder its keys - return_value.lyrics_order = voice.get_lyrics_numbers () + return_value.lyrics_order = voice.get_lyrics_numbers() for k in return_value.lyrics_order: lyrics[k] = [] - voice_builder = LilyPondVoiceBuilder () - figured_bass_builder = LilyPondVoiceBuilder () - chordnames_builder = LilyPondVoiceBuilder () - current_measure_length = Rational (4, 4) - voice_builder.set_measure_length (current_measure_length) + voice_builder = LilyPondVoiceBuilder() + figured_bass_builder = LilyPondVoiceBuilder() + chordnames_builder = LilyPondVoiceBuilder() + fretboards_builder = LilyPondVoiceBuilder() + current_measure_length = Rational(4, 4) + voice_builder.set_measure_length(current_measure_length) + in_slur = False - for n in voice._elements: + all_lyric_parts = set(get_all_lyric_parts_in_voice(voice)) + if lyrics.keys(): + for number in lyrics.keys(): + extracted_lyrics = extract_lyrics(voice, number, lyrics) + + for idx, n in enumerate(voice._elements): tie_started = False - if n.get_name () == 'forward': + if n.get_name() == 'forward': continue - staff = n.get_maybe_exist_named_child ('staff') + staff = n.get_maybe_exist_named_child('staff') if staff: - staff = staff.get_text () - if current_staff and staff <> current_staff and not n.get_maybe_exist_named_child ('chord'): - voice_builder.add_command (musicexp.StaffChange (staff)) + staff = staff.get_text() + if current_staff and staff <> current_staff and not n.get_maybe_exist_named_child('chord'): + voice_builder.add_command(musicexp.StaffChange(staff)) current_staff = staff - if isinstance (n, musicxml.Partial) and n.partial > 0: - a = musicxml_partial_to_lily (n.partial) + if isinstance(n, musicxml.Partial) and n.partial > 0: + a = musicxml_partial_to_lily(n.partial) if a: - voice_builder.add_partial (a) - figured_bass_builder.add_partial (a) - chordnames_builder.add_partial (a) + voice_builder.add_partial(a) + figured_bass_builder.add_partial(a) + chordnames_builder.add_partial(a) + fretboards_builder.add_partial(a) continue - is_chord = n.get_maybe_exist_named_child ('chord') - is_after_grace = (isinstance (n, musicxml.Note) and n.is_after_grace ()); + is_chord = n.get_maybe_exist_named_child('chord') + is_after_grace =(isinstance(n, musicxml.Note) and n.is_after_grace()); if not is_chord and not is_after_grace: try: - voice_builder.jumpto (n._when) - figured_bass_builder.jumpto (n._when) - chordnames_builder.jumpto (n._when) + voice_builder.jumpto(n._when) + figured_bass_builder.jumpto(n._when) + chordnames_builder.jumpto(n._when) + fretboards_builder.jumpto(n._when) except NegativeSkip, neg: - voice_builder.correct_negative_skip (n._when) - figured_bass_builder.correct_negative_skip (n._when) - chordnames_builder.correct_negative_skip (n._when) - n.message (_ ("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here)) - - if isinstance (n, musicxml.Barline): - barlines = musicxml_barline_to_lily (n) + voice_builder.correct_negative_skip(n._when) + figured_bass_builder.correct_negative_skip(n._when) + chordnames_builder.correct_negative_skip(n._when) + fretboards_builder.correct_negative_skip(n._when) + n.message(_("Negative skip found: from %s to %s, difference is %s") % (neg.here, neg.dest, neg.dest - neg.here)) + + if isinstance(n, musicxml.Barline): + barlines = n.to_lily_object() for a in barlines: - if isinstance (a, musicexp.BarLine): - voice_builder.add_barline (a) - figured_bass_builder.add_barline (a, False) - chordnames_builder.add_barline (a, False) - elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker): - voice_builder.add_command (a) - figured_bass_builder.add_barline (a, False) - chordnames_builder.add_barline (a, False) + if isinstance(a, musicexp.BarLine): + voice_builder.add_barline(a) + figured_bass_builder.add_barline(a, False) + chordnames_builder.add_barline(a, False) + fretboards_builder.add_barline(a, False) + elif isinstance(a, musicxml2ly_conversion.RepeatMarker) or isinstance(a, musicxml2ly_conversion.EndingMarker): + voice_builder.add_command(a) + figured_bass_builder.add_barline(a, False) + chordnames_builder.add_barline(a, False) + fretboards_builder.add_barline(a, False) continue - - if isinstance (n, musicxml.Print): - for a in musicxml_print_to_lily (n): - voice_builder.add_command (a, False) + if isinstance(n, musicxml.Print): + for a in musicxml_print_to_lily(n): + voice_builder.add_command(a, False) continue # Continue any multimeasure-rests before trying to add bar checks! # Don't handle new MM rests yet, because for them we want bar checks! - rest = n.get_maybe_exist_typed_child (musicxml.Rest) - if (rest and rest.is_whole_measure () - and voice_builder.pending_multibar > Rational (0)): - voice_builder.add_multibar_rest (n._duration) + rest = n.get_maybe_exist_typed_child(musicxml.Rest) + if (rest and rest.is_whole_measure() + and voice_builder.pending_multibar > Rational(0)): + voice_builder.add_multibar_rest(n._duration) continue - # print a bar check at the beginning of each measure! - if n.is_first () and n._measure_position == Rational (0) and n != voice._elements[0]: + if n.is_first() and n._measure_position == Rational(0) and n != voice._elements[0]: try: - num = int (n.get_parent ().number) + num = int(n.get_parent().number) except ValueError: num = 0 if num > 0: - voice_builder.add_bar_check (num) - figured_bass_builder.add_bar_check (num) - chordnames_builder.add_bar_check (num) + voice_builder.add_bar_check(num) + figured_bass_builder.add_bar_check(num) + chordnames_builder.add_bar_check(num) + fretboards_builder.add_bar_check(num) + + if isinstance(n, musicxml.Direction): + # check if Direction already has been converted in another voice. + if n.converted: + continue + else: + n.converted = True + for direction in musicxml_direction_to_lily(n): + if direction.wait_for_note(): + voice_builder.add_dynamics(direction) + else: + voice_builder.add_command(direction) + continue # Start any new multimeasure rests - if (rest and rest.is_whole_measure ()): - voice_builder.add_multibar_rest (n._duration) - continue - - - if isinstance (n, musicxml.Direction): - for a in musicxml_direction_to_lily (n): - if a.wait_for_note (): - voice_builder.add_dynamics (a) - else: - voice_builder.add_command (a) + if (rest and rest.is_whole_measure()): + if pending_chordnames: + chordnames_builder.jumpto(n._when) + chordnames_builder.stay_here = True + if pending_figured_bass: + figured_bass_builder.jumpto(n._when) + figured_bass_builder.stay_here = True + if pending_fretboards: + fretboards_builder.jumpto(n._when) + fretboards_builder.stay_here = True + voice_builder.add_multibar_rest(n._duration) continue - if isinstance (n, musicxml.Harmony): - for a in musicxml_harmony_to_lily (n): - if a.wait_for_note (): - voice_builder.add_dynamics (a) - else: - voice_builder.add_command (a) - for a in musicxml_harmony_to_lily_chordname (n): - pending_chordnames.append (a) + if isinstance(n, musicxml.Harmony): + if (options.fretboards): + # Makes fretboard diagrams in a separate FretBoards voice + for a in musicxml_harmony_to_lily_fretboards(n): + pending_fretboards.append(a) + else: + # Makes markup fretboard-diagrams inside the voice + for a in musicxml_harmony_to_lily(n): + if a.wait_for_note(): + voice_builder.add_dynamics(a) + else: + voice_builder.add_command(a) + for a in musicxml_harmony_to_lily_chordname(n): + pending_chordnames.append(a) continue - if isinstance (n, musicxml.FiguredBass): - a = musicxml_figured_bass_to_lily (n) + if isinstance(n, musicxml.FiguredBass): + a = musicxml_figured_bass_to_lily(n) if a: - pending_figured_bass.append (a) + pending_figured_bass.append(a) continue - if isinstance (n, musicxml.Attributes): - for a in musicxml_attributes_to_lily (n): - voice_builder.add_command (a) - measure_length = measure_length_from_attributes (n, current_measure_length) + if isinstance(n, musicxml.Attributes): + for a in musicxml_attributes_to_lily(n): + voice_builder.add_command(a) + measure_length = measure_length_from_attributes(n, current_measure_length) if current_measure_length != measure_length: current_measure_length = measure_length - voice_builder.set_measure_length (current_measure_length) + voice_builder.set_measure_length(current_measure_length) continue if not n.__class__.__name__ == 'Note': - n.message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline')) + 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 not hasattr(conversion_settings, 'convert_rest_positions'): +# conversion_settings.convert_rest_positions = True + + main_event = n.to_lily_object( + convert_stem_directions=conversion_settings.convert_stem_directions, + convert_rest_positions=conversion_settings.convert_rest_positions) + if main_event and not first_pitch: first_pitch = main_event.pitch # ignore lyrics for notes inside a slur, tie, chord or beam - ignore_lyrics = inside_slur or is_tied or is_chord or is_beamed + ignore_lyrics = is_tied or is_chord #or is_beamed or inside_slur - if main_event and hasattr (main_event, 'drum_type') and main_event.drum_type: + if main_event and hasattr(main_event, 'drum_type') and main_event.drum_type: modes_found['drummode'] = True - ev_chord = voice_builder.last_event_chord (n._when) + ev_chord = voice_builder.last_event_chord(n._when) if not ev_chord: ev_chord = musicexp.ChordEvent() - voice_builder.add_music (ev_chord, n._duration) + voice_builder.add_music(ev_chord, n._duration) # For grace notes: - grace = n.get_maybe_exist_typed_child (musicxml.Grace) - if n.is_grace (): - is_after_grace = ev_chord.has_elements () or n.is_after_grace (); - is_chord = n.get_maybe_exist_typed_child (musicxml.Chord) + grace = n.get_maybe_exist_typed_child(musicxml.Grace) + if n.is_grace(): + is_after_grace = ev_chord.has_elements() or n.is_after_grace(); + is_chord = n.get_maybe_exist_typed_child(musicxml.Chord) grace_chord = None @@ -2216,64 +2370,81 @@ def musicxml_voice_to_lily_voice (voice): # 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): - grace_chord = ev_chord.after_grace_elements.get_last_event_chord () + if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child(musicxml.Chord): + grace_chord = ev_chord.after_grace_elements.get_last_event_chord() if not grace_chord: - grace_chord = musicexp.ChordEvent () - ev_chord.append_after_grace (grace_chord) - elif n.is_grace (): - if ev_chord.grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord): - grace_chord = ev_chord.grace_elements.get_last_event_chord () + grace_chord = musicexp.ChordEvent() + ev_chord.append_after_grace(grace_chord) + elif n.is_grace(): + if ev_chord.grace_elements and n.get_maybe_exist_typed_child(musicxml.Chord): + grace_chord = ev_chord.grace_elements.get_last_event_chord() if not grace_chord: - grace_chord = musicexp.ChordEvent () - ev_chord.append_grace (grace_chord) + grace_chord = musicexp.ChordEvent() + ev_chord.append_grace(grace_chord) - if hasattr (grace, 'slash') and not is_after_grace: + if hasattr(grace, 'slash') and not is_after_grace: # TODO: use grace_type = "appoggiatura" for slurred grace notes if grace.slash == "yes": ev_chord.grace_type = "acciaccatura" # now that we have inserted the chord into the grace music, insert # everything into that chord instead of the ev_chord ev_chord = grace_chord - ev_chord.append (main_event) + ev_chord.append(main_event) ignore_lyrics = True else: - ev_chord.append (main_event) + ev_chord.append(main_event) # When a note/chord has grace notes (duration==0), the duration of the # event chord is not yet known, but the event chord was already added # 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 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: try: - figured_bass_builder.jumpto (n._when) + figured_bass_builder.jumpto(n._when) + if (figured_bass_builder.stay_here): + figured_bass_builder.stay_here = False except NegativeSkip, neg: pass for fb in pending_figured_bass: # if a duration is given, use that, otherwise the one of the note dur = fb.real_duration if not dur: - dur = ev_chord.get_length () + dur = ev_chord.get_length() if not fb.duration: - fb.duration = ev_chord.get_duration () - figured_bass_builder.add_music (fb, dur) + 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) + chordnames_builder.jumpto(n._when) + if (chordnames_builder.stay_here): + chordnames_builder.stay_here = False except NegativeSkip, neg: pass for cn in pending_chordnames: # Assign the duration of the EventChord - cn.duration = ev_chord.get_duration () - chordnames_builder.add_music (cn, ev_chord.get_length ()) + cn.duration = ev_chord.get_duration() + chordnames_builder.add_music(cn, ev_chord.get_length()) pending_chordnames = [] - notations_children = n.get_typed_children (musicxml.Notations) + if pending_fretboards: + try: + fretboards_builder.jumpto(n._when) + if (fretboards_builder.stay_here): + fretboards_builder.stay_here = False + except NegativeSkip, neg: + pass + for fb in pending_fretboards: + # Assign the duration of the EventChord + fb.duration = ev_chord.get_duration() + fretboards_builder.add_music(fb, ev_chord.get_length()) + pending_fretboards = [] + + notations_children = n.get_typed_children(musicxml.Notations) tuplet_event = None span_events = [] @@ -2284,76 +2455,76 @@ def musicxml_voice_to_lily_voice (voice): # accidental-mark | other-notation for notations in notations_children: for tuplet_event in notations.get_tuplets(): - time_mod = n.get_maybe_exist_typed_child (musicxml.Time_modification) - tuplet_events.append ((ev_chord, tuplet_event, time_mod)) + time_mod = n.get_maybe_exist_typed_child(musicxml.Time_modification) + tuplet_events.append((ev_chord, tuplet_event, time_mod)) # First, close all open slurs, only then start any new slur # TODO: Record the number of the open slur to dtermine the correct # closing slur! - endslurs = [s for s in notations.get_named_children ('slur') - if s.get_type () in ('stop')] + endslurs = [s for s in notations.get_named_children('slur') + if s.get_type() in ('stop')] if endslurs and not inside_slur: - endslurs[0].message (_ ('Encountered closing slur, but no slur is open')) + endslurs[0].message(_('Encountered closing slur, but no slur is open')) elif endslurs: - if len (endslurs) > 1: - endslurs[0].message (_ ('Cannot have two simultaneous (closing) slurs')) + if len(endslurs) > 1: + endslurs[0].message(_('Cannot have two simultaneous (closing) slurs')) # record the slur status for the next note in the loop inside_slur = False - lily_ev = musicxml_spanner_to_lily_event (endslurs[0]) - ev_chord.append (lily_ev) + lily_ev = musicxml_spanner_to_lily_event(endslurs[0]) + ev_chord.append(lily_ev) - startslurs = [s for s in notations.get_named_children ('slur') - if s.get_type () in ('start')] + startslurs = [s for s in notations.get_named_children('slur') + if s.get_type() in('start')] if startslurs and inside_slur: - startslurs[0].message (_ ('Cannot have a slur inside another slur')) + startslurs[0].message(_('Cannot have a slur inside another slur')) elif startslurs: - if len (startslurs) > 1: - startslurs[0].message (_ ('Cannot have two simultaneous slurs')) + if len(startslurs) > 1: + startslurs[0].message(_('Cannot have two simultaneous slurs')) # record the slur status for the next note in the loop inside_slur = True - lily_ev = musicxml_spanner_to_lily_event (startslurs[0]) - ev_chord.append (lily_ev) + lily_ev = musicxml_spanner_to_lily_event(startslurs[0]) + ev_chord.append(lily_ev) if not grace: - mxl_tie = notations.get_tie () + mxl_tie = notations.get_tie() if mxl_tie and mxl_tie.type == 'start': - ev_chord.append (musicexp.TieEvent ()) + ev_chord.append(musicexp.TieEvent()) is_tied = True tie_started = True else: is_tied = False - fermatas = notations.get_named_children ('fermata') + fermatas = notations.get_named_children('fermata') for a in fermatas: - ev = musicxml_fermata_to_lily_event (a) + ev = musicxml_fermata_to_lily_event(a) if ev: - ev_chord.append (ev) + ev_chord.append(ev) - arpeggiate = notations.get_named_children ('arpeggiate') + arpeggiate = notations.get_named_children('arpeggiate') for a in arpeggiate: - ev = musicxml_arpeggiate_to_lily_event (a) + ev = musicxml_arpeggiate_to_lily_event(a) if ev: - ev_chord.append (ev) + ev_chord.append(ev) - arpeggiate = notations.get_named_children ('non-arpeggiate') + arpeggiate = notations.get_named_children('non-arpeggiate') for a in arpeggiate: - ev = musicxml_nonarpeggiate_to_lily_event (a) + ev = musicxml_nonarpeggiate_to_lily_event(a) if ev: - ev_chord.append (ev) + ev_chord.append(ev) - glissandos = notations.get_named_children ('glissando') - glissandos += notations.get_named_children ('slide') + glissandos = notations.get_named_children('glissando') + glissandos += notations.get_named_children('slide') for a in glissandos: - ev = musicxml_spanner_to_lily_event (a) + ev = musicxml_spanner_to_lily_event(a) if ev: - ev_chord.append (ev) + ev_chord.append(ev) # accidental-marks are direct children of ! - for a in notations.get_named_children ('accidental-mark'): - ev = musicxml_articulation_to_lily_event (a) + for a in notations.get_named_children('accidental-mark'): + ev = musicxml_articulation_to_lily_event(a) if ev: - ev_chord.append (ev) + ev_chord.append(ev) # Articulations can contain the following child elements: # accent | strong-accent | staccato | tenuto | @@ -2370,88 +2541,85 @@ def musicxml_voice_to_lily_voice (voice): # trill-mark | turn | delayed-turn | inverted-turn | # shake | wavy-line | mordent | inverted-mordent | # schleifer | tremolo | other-ornament, accidental-mark - ornaments = notations.get_named_children ('ornaments') - ornaments += notations.get_named_children ('articulations') - ornaments += notations.get_named_children ('technical') + ornaments = notations.get_named_children('ornaments') + ornaments += notations.get_named_children('articulations') + ornaments += notations.get_named_children('technical') for a in ornaments: - for ch in a.get_all_children (): - ev = musicxml_articulation_to_lily_event (ch) + for ch in a.get_all_children(): + ev = musicxml_articulation_to_lily_event(ch) if ev: - ev_chord.append (ev) + ev_chord.append(ev) - dynamics = notations.get_named_children ('dynamics') + dynamics = notations.get_named_children('dynamics') for a in dynamics: - for ch in a.get_all_children (): - ev = musicxml_dynamics_to_lily_event (ch) + for ch in a.get_all_children(): + ev = musicxml_dynamics_to_lily_event(ch) if ev: - ev_chord.append (ev) + ev_chord.append(ev) - mxl_beams = [b for b in n.get_named_children ('beam') - if (b.get_type () in ('begin', 'end') - and b.is_primary ())] + 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 and not conversion_settings.ignore_beaming: - beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0]) + beam_ev = musicxml_spanner_to_lily_event(mxl_beams[0]) if beam_ev: - ev_chord.append (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 - # 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 (musicxml_lyrics_to_text (l)) - note_lyrics_processed.append (k) - else: - lyrics[l.number].append(musicxml_lyrics_to_text (l)) - note_lyrics_processed.append (l.number) - for lnr in lyrics.keys (): - if not lnr in note_lyrics_processed: - lyrics[lnr].append ("\skip4") - # Assume that a element only lasts for one note. # This might not be correct MusicXML interpretation, but works for # most cases and fixes broken files, which have the end tag missing if is_tied and not tie_started: is_tied = False - ## force trailing mm rests to be written out. - voice_builder.add_music (musicexp.ChordEvent (), Rational (0)) + # force trailing mm rests to be written out. + # voice_builder.add_music (musicexp.ChordEvent(), Rational(0)) + if hasattr(options, 'shift_meter') and options.shift_meter: + for event in voice_builder.elements: + if isinstance(event, musicexp.TimeSignatureChange): + sd = [] + for i in range(0,5): + sd.append(musicexp.ShiftDurations()) + sd[i].set_shift_durations_parameters(event) + break; - ly_voice = group_tuplets (voice_builder.elements, tuplet_events) - ly_voice = group_repeats (ly_voice) + ly_voice = group_tuplets(voice_builder.elements, tuplet_events) + ly_voice = group_repeats(ly_voice) - seq_music = musicexp.SequentialMusic () + seq_music = musicexp.SequentialMusic() - if 'drummode' in modes_found.keys (): + if 'drummode' in modes_found.keys(): ## \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 () + 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: - ly.warning (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ()) + if len(modes_found) > 1: + ly.warning(_('cannot simultaneously have more than one mode: %s') % modes_found.keys()) + + if hasattr(options, 'shift_meter') and options.shift_meter: + sd[-1].element = seq_music + seq_music = sd[-1] + sd.pop() - if options.relative: - v = musicexp.RelativeMusic () + if hasattr(options, 'relative') and options.relative: + v = musicexp.RelativeMusic() v.element = seq_music v.basepitch = first_pitch seq_music = v return_value.ly_voice = seq_music - for mode in modes_found.keys (): + for mode in modes_found.keys(): v = musicexp.ModeChangingMusicWrapper() v.element = seq_music v.mode = mode @@ -2459,81 +2627,87 @@ def musicxml_voice_to_lily_voice (voice): # create \figuremode { figured bass elements } if figured_bass_builder.has_relevant_elements: - fbass_music = musicexp.SequentialMusic () - fbass_music.elements = group_repeats (figured_bass_builder.elements) + fbass_music = musicexp.SequentialMusic() + fbass_music.elements = group_repeats(figured_bass_builder.elements) v = musicexp.ModeChangingMusicWrapper() v.mode = 'figuremode' v.element = fbass_music + if hasattr(options, 'shift_meter') and options.shift_meter: + sd[-1].element = v + v = sd[-1] + sd.pop() return_value.figured_bass = v # create \chordmode { chords } if chordnames_builder.has_relevant_elements: - cname_music = musicexp.SequentialMusic () - cname_music.elements = group_repeats (chordnames_builder.elements) + cname_music = musicexp.SequentialMusic() + cname_music.elements = group_repeats(chordnames_builder.elements) v = musicexp.ModeChangingMusicWrapper() v.mode = 'chordmode' v.element = cname_music + if hasattr(options, 'shift_meter') and options.shift_meter: + sd[-1].element = v + v = sd[-1] + sd.pop() return_value.chordnames = v + # create diagrams for FretBoards engraver + if fretboards_builder.has_relevant_elements: + fboard_music = musicexp.SequentialMusic() + fboard_music.elements = group_repeats(fretboards_builder.elements) + v = musicexp.MusicWrapper() + v.element = fboard_music + if hasattr(options, 'shift_meter') and options.shift_meter: + sd[-1].element = v + v = sd[-1] + sd.pop() + return_value.fretboards = v + + # coll = [] + # pending = [] + + # for elt in return_value.ly_voice.element.elements: + # if isinstance(elt, musicexp.TimeScaledMusic): + # print elt.element.elements + # pending.append(elt) + # else: + # coll.append(elt) + + # if pending: + # coll.extend(pending) + + # return_value.ly_voice.element.elements = coll + return return_value -def musicxml_id_to_lily (id): +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) + d = digits.index(digit) + id = re.sub('%d' % d, digit, id) - id = re.sub ('[^a-zA-Z]', 'X', id) + id = re.sub('[^a-zA-Z]', 'X', id) return id -def musicxml_pitch_to_lily (mxl_pitch): - p = musicexp.Pitch () - p.alteration = mxl_pitch.get_alteration () - p.step = musicxml_step_to_lily (mxl_pitch.get_step ()) - p.octave = mxl_pitch.get_octave () - 4 - return p - -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) - octave = mxl_unpitched.get_octave () - if octave and p: - p.octave = octave - 4 - return p - -def musicxml_restdisplay_to_lily (mxl_rest): - p = None - step = mxl_rest.get_step () - if step: - p = musicexp.Pitch () - p.step = musicxml_step_to_lily (step) - octave = mxl_rest.get_octave () - if octave and p: - p.octave = octave - 4 - return p - -def voices_in_part (part): +def voices_in_part(part): """Return a Name -> Voice dictionary for PART""" - part.interpret () - part.extract_voices () - voices = part.get_voices () - part_info = part.get_staff_attributes () + part.interpret() + part.extract_voices() + voices = part.get_voices() + part_info = part.get_staff_attributes() return (voices, part_info) -def voices_in_part_in_parts (parts): +def voices_in_part_in_parts(parts): """return a Part -> Name -> Voice dictionary""" - # don't crash if p doesn't have an id (that's invalid MusicXML, - # but such files are out in the wild! + # don't crash if Part doesn't have an id (that's invalid MusicXML, + # but such files are out in the wild!) dictionary = {} for p in parts: - voices = voices_in_part (p) - if (hasattr (p, "id")): + voices = voices_in_part(p) + if (hasattr(p, "id")): dictionary[p.id] = voices else: # TODO: extract correct part id from other sources @@ -2541,18 +2715,19 @@ def voices_in_part_in_parts (parts): return dictionary; -def get_all_voices (parts): - all_voices = voices_in_part_in_parts (parts) +def get_all_voices(parts): + all_voices = voices_in_part_in_parts(parts) all_ly_voices = {} all_ly_staffinfo = {} - for p, (name_voice, staff_info) in all_voices.items (): + for p, (name_voice, staff_info) in all_voices.items(): part_ly_voices = {} - for n, v in name_voice.items (): - ly.progress (_ ("Converting to LilyPond expressions..."), True) + for n, v in name_voice.items(): + 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) + voice = musicxml_voice_to_lily_voice(v) + part_ly_voices[n] = voice all_ly_voices[p] = part_ly_voices all_ly_staffinfo[p] = staff_info @@ -2560,23 +2735,24 @@ def get_all_voices (parts): return (all_ly_voices, all_ly_staffinfo) -def option_parser (): - p = ly.get_option_parser (usage = _ ("musicxml2ly [OPTION]... FILE.xml"), - description = -_ ("""Convert MusicXML from FILE.xml to LilyPond input. +def option_parser(): + p = ly.get_option_parser(usage=_("musicxml2ly [OPTION]... FILE.xml"), + description= +_("""Convert MusicXML from FILE.xml to LilyPond input. If the given filename is -, musicxml2ly reads from the command line. """), add_help_option=False) p.add_option("-h", "--help", action="help", - help=_ ("show this help and exit")) + help=_("show this help and exit")) - p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n''' + p.version = ('%prog (LilyPond) ' + lilypond_version + '\n\n' + -_ ("""Copyright (c) 2005--2015 by +_ ("""Copyright (c) 2005--2016 by Han-Wen Nienhuys , Jan Nieuwenhuizen and Reinhold Kainhofer + Patrick L. Schmidt """ + """ @@ -2589,41 +2765,41 @@ information.""") % 'lilypond') action="version", help=_ ("show version number and exit")) - p.add_option ('-v', '--verbose', + p.add_option('-v', '--verbose', action="callback", callback=ly.handle_loglevel_option, callback_args=("DEBUG",), - 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")) - - p.add_option ('-z', '--compressed', - action = "store_true", - dest = 'compressed', - default = False, - 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)")) - - 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 LANG for pitch names, e.g. 'deutsch' for note names in German")) - - p.add_option ("--loglevel", + 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")) + + p.add_option('-z', '--compressed', + action="store_true", + dest='compressed', + default=False, + 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)")) + + 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 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"), @@ -2631,67 +2807,110 @@ information.""") % 'lilypond') callback=ly.handle_loglevel_option, type='string') - 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', - action = "store_false", - default = True, - dest = "convert_rest_positions", - help = _ ("do not convert exact vertical positions of rests")) - - 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", - 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, stdout if -")) - - p.add_option ('-m', '--midi', - action = "store_true", - default = False, - dest = "midi", - help = _("activate midi-block")) - - p.add_option_group ('', - description = ( + 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', + action="store_false", + default=True, + dest="convert_rest_positions", + help=_ ("do not convert exact vertical positions of rests")) + + p.add_option('--nsb', '--no-system-breaks', + action="store_false", + default=True, + dest="convert_system_breaks", + help=_ ("ignore system breaks")) + + p.add_option('--npb', '--no-page-breaks', + action="store_false", + default=True, + dest="convert_page_breaks", + help=_ ("ignore page breaks")) + + p.add_option('--npm', '--no-page-margins', + action="store_false", + default=True, + dest="convert_page_margins", + help=_ ("ignore page margins")) + + 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 (shortcut for \"--nsb --npb --npm\" options)")) + + p.add_option('--nsd', '--no-stem-directions', + action="store_false", + default=True, + dest="convert_stem_directions", + help=_ ("ignore stem directions from MusicXML, use lilypond's automatic stemming instead")) + + p.add_option('--nb', '--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, stdout if -")) + + p.add_option('-m', '--midi', + action="store_true", + default=False, + dest="midi", + help=_("activate midi-block in .ly file")) + + # transpose function + p.add_option('--transpose', + metavar=_ ("TOPITCH"), + action="store", + dest="transpose", + help=_ ("set pitch to transpose by the interval between pitch 'c' and TOPITCH")) + + # time signature changing function + p.add_option('--sm', '--shift-meter', + metavar=_ ("BEATS/BEATTYPE"), + action="store", + dest="shift_meter", + help=_ ("change the length|duration of notes as a function of a given time signature to make the score look faster or slower, (eg. '4/4' or '2/2')")) + + # switch tabstaff clef + p.add_option('--tc', '--tab-clef', + metavar=_ ("TABCLEFNAME"), + action="store", + dest="tab_clef", + help=_ ("switch between two versions of tab clefs (\"tab\" and \"moderntab\")")) + + # StringNumber stencil on/off + p.add_option('--sn', '--string-numbers', + metavar=_ ("t[rue]/f[alse]"), + action="store", + dest="string_numbers", + help=_ ("deactivate string number stencil with --string-numbers f[alse]. Default is t[rue]")) + + # StringNumber stencil on/off + p.add_option('--fb', '--fretboards', + action="store_true", + default=False, + dest="fretboards", + help=_("converts '' events to a separate FretBoards voice instead of markups")) + + p.add_option_group('', + description=( _ ("Report bugs via %s") % 'http://post.gmane.org/post.php' '?group=gmane.comp.gnu.lilypond.bugs') + '\n') return p -def music_xml_voice_name_to_lily_name (part_id, name): - str = "Part%sVoice%s" % (part_id, name) - 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) - -def music_xml_figuredbass_name_to_lily_name (part_id, voicename): - str = "Part%sVoice%sFiguredBass" % (part_id, voicename) - 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) - def print_voice_definitions (printer, part_list, voices): for part in part_list: part_id = part.id @@ -2703,23 +2922,27 @@ def print_voice_definitions (printer, part_list, voices): printer.newline() if voice.chordnames: cnname = music_xml_chordnames_name_to_lily_name (part_id, name) - printer.dump ('%s = ' % cnname ) + printer.dump ('%s = ' % cnname) voice.chordnames.print_ly (printer) printer.newline() for l in voice.lyrics_order: lname = music_xml_lyrics_name_to_lily_name (part_id, name, l) - printer.dump ('%s = ' % lname ) + printer.dump ('%s = ' % lname) voice.lyrics_dict[l].print_ly (printer) printer.newline() if voice.figured_bass: fbname = music_xml_figuredbass_name_to_lily_name (part_id, name) - printer.dump ('%s = ' % fbname ) + printer.dump ('%s = ' % fbname) voice.figured_bass.print_ly (printer) printer.newline() + if voice.fretboards: + fbdname = music_xml_fretboards_name_to_lily_name (part_id, name) + printer.dump ('%s = ' % fbdname) + voice.fretboards.print_ly (printer) + printer.newline() - -def uniq_list (l): - return dict ([(elt, 1) for elt in l]).keys () +def uniq_list(l): + return dict([(elt, 1) for elt in l]).keys() # format the information about the staff in the form # [staffid, @@ -2730,159 +2953,189 @@ def uniq_list (l): # ] # ] # raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*] -def format_staff_info (part_id, staff_id, raw_voices): +def format_staff_info(part_id, staff_id, raw_voices): voices = [] - for (v, lyricsids, figured_bass, chordnames) in raw_voices: - voice_name = music_xml_voice_name_to_lily_name (part_id, v) - voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l) + for (v, lyricsids, figured_bass, chordnames, fretboards) in raw_voices: + voice_name = music_xml_voice_name_to_lily_name(part_id, v) + voice_lyrics = [music_xml_lyrics_name_to_lily_name(part_id, v, l) for l in lyricsids] figured_bass_name = '' if figured_bass: - figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v) + figured_bass_name = music_xml_figuredbass_name_to_lily_name(part_id, v) chordnames_name = '' if chordnames: - chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v) - voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name]) + chordnames_name = music_xml_chordnames_name_to_lily_name(part_id, v) + fretboards_name = '' + if fretboards: + fretboards_name = music_xml_fretboards_name_to_lily_name(part_id, v) + voices.append([voice_name, voice_lyrics, figured_bass_name, chordnames_name, fretboards_name]) return [staff_id, voices] -def update_score_setup (score_structure, part_list, voices): - +def update_score_setup(score_structure, part_list, voices, parts): for part_definition in part_list: part_id = part_definition.id - nv_dict = voices.get (part_id) + nv_dict = voices.get(part_id) if not nv_dict: - ly.warning (_ ('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, - [voice.voicedata._staves.keys () - for voice in nv_dict.values ()], + staves = reduce(lambda x, y: x + y, + [voice.voicedata._staves.keys() + for voice in nv_dict.values()], []) staves_info = [] - if len (staves) > 1: + if len(staves) > 1: staves_info = [] - staves = uniq_list (staves) - staves.sort () + staves = uniq_list(staves) + staves.sort() for s in staves: - thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) - for (voice_name, voice) in nv_dict.items () + thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames, voice.fretboards) + 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)) + 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) - 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) + thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames, voice.fretboards) + 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) + + sounds = [] + for part in parts: + for measure in part.get_typed_children(musicxml.Measure): + for sound in measure.get_typed_children(musicxml.Sound): + sounds.append(sound) + for direction in measure.get_typed_children(musicxml.Direction): + for sound in direction.get_typed_children(musicxml.Sound): + sounds.append(sound) + + score_structure.set_tempo('100') + if len(sounds) != 0: + for sound in sounds: + if (sound.get_tempo() != None and sound.get_tempo() != ""): + score_structure.set_tempo(sound.get_tempo()) + break + # Set global values in the \layout block, like auto-beaming etc. -def update_layout_information (): +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 by musicxml2ly from %s\n' % filename) + layout_information.set_context_item('Score', 'autoBeaming = ##f') + if musicexp.get_string_numbers() == "f": + layout_information.set_context_item('Score', '\\override StringNumber #\'stencil = ##f') + +# \n\t\t\t\t\\override StringNumber #\'stencil = ##f + +def print_ly_preamble(printer, filename): + printer.dump_version(lilypond_version) + printer.print_verbatim('% automatically converted by musicxml2ly from ' + filename) + printer.newline() + printer.dump(r'\pointAndClickOff') + printer.newline() + if options.midi: + printer.newline() + printer.dump(r'\include "articulate.ly"') + printer.newline() -def print_ly_additional_definitions (printer, filename): +def print_ly_additional_definitions(printer, filename=None): if needed_additional_definitions: - printer.newline () - printer.print_verbatim ('%% additional definitions required by the score:') - printer.newline () + printer.newline() + printer.print_verbatim('%% additional definitions required by the score:') + printer.newline() for a in set(needed_additional_definitions): - printer.print_verbatim (additional_definitions.get (a, '')) - printer.newline () - printer.newline () + printer.print_verbatim(additional_definitions.get(a, '')) + printer.newline() + printer.newline() # 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): +def read_xml(io_object, use_lxml): if use_lxml: import lxml.etree - tree = lxml.etree.parse (io_object) - mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ()) + tree = lxml.etree.parse(io_object) + mxl_tree = musicxml.lxml_demarshal_node(tree.getroot()) return mxl_tree else: from xml.dom import minidom, Node doc = minidom.parse(io_object) node = doc.documentElement - return musicxml.minidom_demarshal_node (node) + return musicxml.minidom_demarshal_node(node) return None -def read_musicxml (filename, compressed, use_lxml): +def read_musicxml(filename, compressed, use_lxml): raw_string = None if compressed: if filename == "-": - ly.progress (_ ("Input is compressed, extracting raw MusicXML data from stdin"), True) + 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) + 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") + bytes_read = sys.stdin.read(8192) + z = zipfile.ZipFile(tmp, "r") else: - 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") + 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: return None - container = read_xml (StringIO.StringIO (container_xml), use_lxml) + container = read_xml(StringIO.StringIO(container_xml), use_lxml) if not container: return None - rootfiles = container.get_maybe_exist_named_child ('rootfiles') + rootfiles = container.get_maybe_exist_named_child('rootfiles') if not rootfiles: return None - rootfile_list = rootfiles.get_named_children ('rootfile') + rootfile_list = rootfiles.get_named_children('rootfile') mxml_file = None - if len (rootfile_list) > 0: - mxml_file = getattr (rootfile_list[0], 'full-path', None) + if len(rootfile_list) > 0: + mxml_file = getattr(rootfile_list[0], 'full-path', None) if mxml_file: - raw_string = z.read (mxml_file) + raw_string = z.read(mxml_file) if raw_string: - io_object = StringIO.StringIO (raw_string) + io_object = StringIO.StringIO(raw_string) elif filename == "-": io_object = sys.stdin else: io_object = filename - return read_xml (io_object, use_lxml) + return read_xml(io_object, use_lxml) -def convert (filename, options): +def convert(filename, options): if filename == "-": - ly.progress (_ ("Reading MusicXML from Standard input ..."), True) + ly.progress(_("Reading MusicXML from Standard input ..."), True) else: - ly.progress (_ ("Reading MusicXML from %s ...") % filename, True) + ly.progress(_("Reading MusicXML from %s ...") % filename, True) - tree = read_musicxml (filename, options.compressed, options.use_lxml) - score_information = extract_score_information (tree) - paper_information = extract_paper_information (tree) + 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) + parts = tree.get_typed_children(musicxml.Part) + (voices, staff_info) = get_all_voices(parts) score = None - mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list) + mxl_pl = tree.get_maybe_exist_typed_child(musicxml.Part_list) if mxl_pl: - score = extract_score_structure (mxl_pl, staff_info) - part_list = mxl_pl.get_named_children ("score-part") + score = extract_score_structure(mxl_pl, staff_info) + part_list = mxl_pl.get_named_children("score-part") # score information is contained in the , or tags - update_score_setup (score, part_list, voices) + update_score_setup(score, part_list, voices, parts) # After the conversion, update the list of settings for the \layout block - update_layout_information () + update_layout_information() if not options.output_name: - 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] + 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] #defs_ly_name = options.output_name + '-defs.ly' @@ -2890,78 +3143,108 @@ def convert (filename, options): output_ly_name = 'Standard output' else: output_ly_name = options.output_name + '.ly' - - ly.progress (_ ("Output to `%s'") % output_ly_name, True) + ly.progress(_("Output to `%s'") % output_ly_name, True) printer = musicexp.Output_printer() - #ly.progress (_ ("Output to `%s'") % defs_ly_name, True) + #ly.progress(_("Output to `%s'") % defs_ly_name, True) if (options.output_name == "-"): - printer.set_file (codecs.getwriter ("utf-8")(sys.stdout)) + printer.set_file(codecs.getwriter("utf-8")(sys.stdout)) else: - printer.set_file (codecs.open (output_ly_name, 'wb', encoding='utf-8')) - print_ly_preamble (printer, filename) - print_ly_additional_definitions (printer, filename) + printer.set_file(codecs.open(output_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) + score_information.print_ly(printer) if paper_information and conversion_settings.convert_page_layout: - paper_information.print_ly (printer) + paper_information.print_ly(printer) if layout_information: - layout_information.print_ly (printer) - print_voice_definitions (printer, part_list, voices) + layout_information.print_ly(printer) + print_voice_definitions(printer, part_list, voices) + + printer.newline() + printer.dump("% The score definition") + printer.newline() + score.print_ly(printer) + printer.newline() - printer.newline () - printer.dump ("% The score definition") - printer.newline () - score.print_ly (printer) - printer.newline () + # Syntax update to current version + if (options.output_name != "-"): + version = os.popen("lilypond --version | head -1 | cut -d' ' -f3").read().strip() + ly.progress(_("Converting to current version (%s) notations ..." % version), True) + os.system("convert-ly -e %s 2> /dev/null" % utilities.escape_ly_output_string(output_ly_name)) return voices -def get_existing_filename_with_extension (filename, ext): - if os.path.exists (filename): +def get_existing_filename_with_extension(filename, ext): + if os.path.exists(filename): return filename newfilename = filename + "." + ext - if os.path.exists (newfilename): + if os.path.exists(newfilename): return newfilename; newfilename = filename + ext - if os.path.exists (newfilename): + if os.path.exists(newfilename): return newfilename; return '' -def main (): + +def main(): opt_parser = option_parser() global options - (options, args) = opt_parser.parse_args () + (options, args) = opt_parser.parse_args() if not args: opt_parser.print_usage() - sys.exit (2) + sys.exit(2) + # midi-block option if options.midi: - musicexp.set_create_midi (options.midi) + musicexp.set_create_midi(options.midi) + + # transpose function + if options.transpose: + musicexp.set_transpose(options.transpose) + + # tab clef option + if options.tab_clef: + musicexp.set_tab_clef(options.tab_clef) + + # string numbers option + if options.string_numbers: + musicexp.set_string_numbers(options.string_numbers) if options.language: - musicexp.set_pitch_language (options.language) - needed_additional_definitions.append (options.language) + musicexp.set_pitch_language(options.language) + needed_additional_definitions.append(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 + if conversion_settings.convert_page_layout: + conversion_settings.convert_system_breaks = options.convert_system_breaks + conversion_settings.convert_page_breaks = options.convert_page_breaks + conversion_settings.convert_page_margins = options.convert_page_margins + else: + conversion_settings.convert_system_breaks = False + conversion_settings.convert_page_breaks = False + conversion_settings.convert_page_margins = False + conversion_settings.convert_stem_directions = options.convert_stem_directions # Allow the user to leave out the .xml or xml on the filename basefilename = args[0].decode('utf-8') if basefilename == "-": # Read from stdin filename = "-" else: - filename = get_existing_filename_with_extension (basefilename, "xml") + filename = get_existing_filename_with_extension(basefilename, "xml") if not filename: - filename = get_existing_filename_with_extension (basefilename, "mxl") + filename = get_existing_filename_with_extension(basefilename, "mxl") options.compressed = True - if filename and filename.endswith ("mxl"): + if filename and filename.endswith("mxl"): options.compressed = True - if filename and (filename == "-" or os.path.exists (filename)): - voices = convert (filename, options) + if filename and (filename == "-" or os.path.exists(filename)): + voices = convert(filename, options) else: - ly.error (_ ("Unable to find input file %s") % basefilename) + ly.error(_("Unable to find input file %s") % basefilename) + sys.exit(1) if __name__ == '__main__': main()