X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmusicxml2ly.py;h=2c130b42fce4d6237e70df31e1fae752bd74fc6c;hb=59b91873be1615ddbeb800632afad2b538686de9;hp=ba841e0d00bd1fa73e238d7762b1e201b4b360cc;hpb=c25fa09c277d02f0b793a545464e682111abc527;p=lilypond.git diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index ba841e0d00..2c130b42fc 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -62,26 +62,112 @@ def extract_score_information (tree): return header +class PartGroupInfo: + 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): + error_message ("Unprocessed PartGroupInfo %s encountered" % self) + def ly_expression (self): + error_message ("Unprocessed PartGroupInfo %s encountered" % self) + return '' + + +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') + if staff_lines: + lines = string.atoi (staff_lines.get_text ()) + + tunings = [0]*lines + 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 + except ValueError: + pass + tunings[line] = p + + step = i.get_named_child (u'tuning-step') + step = step.get_text ().strip () + p.step = (ord (step) - ord ('A') + 7 - 2) % 7 + + 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') + if alter: + p.alteration = int (alter.get_text ().strip ()) + # lilypond seems to use the opposite ordering than MusicXML... + tunings.reverse () + + return tunings + + +def staff_attributes_to_lily_staff (mxl_attr): + if not mxl_attr: + return musicexp.Staff () + + (staff_id, attributes) = mxl_attr.items ()[0] + + # distinguish by clef: + # percussion (percussion and rhythmic), tab, and everything else + clef_sign = None + clef = attributes.get_maybe_exist_named_child ('clef') + if clef: + sign = clef.get_maybe_exist_named_child ('sign') + if sign: + clef_sign = {"percussion": "percussion", "TAB": "tab"}.get (sign.get_text (), None) + + lines = 5 + details = attributes.get_maybe_exist_named_child ('staff-details') + if details: + staff_lines = details.get_maybe_exist_named_child ('staff-lines') + if staff_lines: + lines = string.atoi (staff_lines.get_text ()) + + staff = None + if clef_sign == "percussion" and lines == 1: + staff = musicexp.RhythmicStaff () + elif clef_sign == "percussion": + staff = musicexp.DrumStaff () + # staff.drum_style_table = ??? + elif clef_sign == "tab": + staff = musicexp.TabStaff () + staff.string_tunings = staff_attributes_to_string_tunings (attributes) + # staff.tablature_format = ??? + else: + # TODO: Handle case with lines <> 5! + staff = musicexp.Staff () -def extract_score_layout (part_list): + return staff + + +def extract_score_layout (part_list, staffinfo): layout = musicexp.StaffGroup (None) - currentgroups_dict = {} - currentgroups = [] if not part_list: return layout - def insert_into_layout (object): - if len (currentgroups) > 0: - group_to_insert = currentgroups_dict.get (currentgroups [-1], layout) - else: - group_to_insert = layout - group_to_insert.appendStaff (object) - return group_to_insert - def read_score_part (el): if not isinstance (el, musicxml.Score_part): return - staff = musicexp.Staff () + # 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)) + if not staff: + return None staff.id = el.id partname = el.get_maybe_exist_named_child ('part-name') # Finale gives unnamed parts the name "MusicXML Part" automatically! @@ -92,48 +178,112 @@ def extract_score_layout (part_list): # TODO: Read in the MIDI device / instrument return staff + def read_score_group (el): + if not isinstance (el, musicxml.Part_group): + return + 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 () + return group + parts_groups = part_list.get_all_children () - # the start/end group tags are not necessarily ordered correctly, so - # we can't go through the children sequentially! - if len (parts_groups) == 1 and isinstance (parts_group[1], musicxml.Score_part): - return read_score_part (parts_group[1]) + # the start/end group tags are not necessarily ordered correctly and groups + # might even overlap, so we can't go through the children sequentially! + # 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 () 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) - insert_into_layout (staff) + if staff: + staves.append (staff) elif isinstance (el, musicxml.Part_group): if el.type == "start": - group = musicexp.StaffGroup () - staff_group = insert_into_layout (group) - # If we're inserting a nested staffgroup, we need to use InnerStaffGroup - if staff_group != layout: - group.stafftype = "InnerStaffGroup" - 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 () - + group_info.add_start (el) elif el.type == "stop": - # end the part-group, i.e. simply remove it from the lists - if hasattr (el, 'number'): - pid = el.number - elif len (currentgroups) > 0: - pid = el[-1] - if pid: - del currentgroups_dict[pid] - currentgroups.remove (pid) + 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): + el = staves[pos] + if isinstance (el, PartGroupInfo): + prev_start = 0 + if len (group_starts) > 0: + prev_start = group_starts[-1] + 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 () + grpid = None + intersection = filter(lambda x:x in ends, prev_started) + if len (intersection) > 0: + grpid = intersection[0] + else: + # Close the last started group + 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): + foundclosing = True + del staves[j].end[grpid] + if staves[j].is_empty (): + del staves[j] + j += 1 + grpobj = staves[prev_start].start[grpid] + group = read_score_group (grpobj) + # remove the id from both the start and end + if el.end.has_key (grpid): + del el.end[grpid] + del staves[prev_start].start[grpid] + if el.is_empty (): + del staves[pos] + # replace the staves with the whole group + for j in staves[(prev_start + 1):pos]: + if j.is_group: + j.stafftype = "InnerStaffGroup" + group.append_staff (j) + del staves[(prev_start + 1):pos] + 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 (): + del staves[prev_start] + group_starts.remove (prev_start) + pos -= 1 + elif len (el.start) > 0: # starts new part groups + group_starts.append (pos) + pos += 1 + + if len (staves) == 1: + return staves[0] + for i in staves: + layout.append_staff (i) return layout @@ -173,6 +323,105 @@ def musicxml_partial_to_lily (partial_len): else: return Null +# 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): + 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 + # 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. + while repeat_replaced and i < 100: + i += 1 + repeat_start = -1 # position of repeat start / end + repeat_end = -1 # position of repeat start / end + repeat_times = 0 + ending_start = -1 # position of current ending start + endings = [] # list of already finished endings + pos = 0 + last = len (music_list) - 1 + repeat_replaced = False + final_marker = 0 + while pos < len (music_list) and not repeat_replaced: + e = music_list[pos] + repeat_finished = False + if isinstance (e, RepeatMarker): + if not repeat_times and e.times: + repeat_times = e.times + if e.direction == -1: + if repeat_end >= 0: + repeat_finished = True + else: + repeat_start = pos + repeat_end = -1 + ending_start = -1 + endings = [] + elif e.direction == 1: + if repeat_start < 0: + repeat_start = 0 + if repeat_end < 0: + repeat_end = pos + final_marker = pos + elif isinstance (e, EndingMarker): + if e.direction == -1: + if repeat_start < 0: + repeat_start = 0 + if repeat_end < 0: + repeat_end = pos + ending_start = pos + elif e.direction == 1: + if ending_start < 0: + ending_start = 0 + endings.append ([ending_start, pos]) + ending_start = -1 + final_marker = pos + 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 + # we convert only one page of a multi-page score with repeats) + if pos == last and repeat_start >= 0: + repeat_finished = True + final_marker = pos + if repeat_end < 0: + repeat_end = pos + if ending_start >= 0: + endings.append ([ending_start, pos]) + ending_start = -1 + + if repeat_finished: + # We found the whole structure replace it! + 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 + # 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 () + s.elements = music_list[start+1:end] + r.add_ending (s) + del music_list[repeat_start:final_marker+1] + music_list.insert (repeat_start, r) + repeat_replaced = True + pos += 1 + # TODO: Implement repeats until the end without explicit ending bar + return music_list + + + def group_tuplets (music_list, events): @@ -274,6 +523,71 @@ def musicxml_attributes_to_lily (attrs): return elts +class Marker (musicexp.Music): + def __init__ (self): + self.direction = 0 + self.event = None + def print_ly (self, printer): + sys.stderr.write ("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 = { 'slur' : musicexp.SlurEvent, 'beam' : musicexp.BeamEvent, @@ -336,6 +650,13 @@ def musicxml_fermata_to_lily_event (mxl_event): ev.force_direction = dir return ev + +def musicxml_arpeggiate_to_lily_event (mxl_event): + ev = musicexp.ArpeggioEvent () + ev.direction = {"up": 1, "down": -1}.get (getattr (mxl_event, 'direction', None), 0) + return ev + + def musicxml_tremolo_to_lily_event (mxl_event): ev = musicexp.TremoloEvent () ev.bars = mxl_event.get_text () @@ -352,6 +673,11 @@ def musicxml_fingering_event (mxl_event): ev.type = mxl_event.get_text () return ev +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 () contents = { "sharp": "\\sharp", @@ -381,13 +707,13 @@ def musicxml_accidental_mark (mxl_event): # -) (class, name) (like string, only that a different class than ArticulationEvent is used) # TODO: Some translations are missing! articulations_dict = { - "accent": (musicexp.ShortArticulationEvent, ">"), + "accent": (musicexp.ShortArticulationEvent, ">"), # or "accent" "accidental-mark": musicxml_accidental_mark, "bend": musicxml_bend_to_lily_event, "breath-mark": (musicexp.NoDirectionArticulationEvent, "breathe"), #"caesura": "caesura", #"delayed-turn": "?", - #"detached-legato": "", + "detached-legato": (musicexp.ShortArticulationEvent, "_"), # or "portato" #"doit": "", #"double-tongue": "", "down-bow": "downbow", @@ -401,24 +727,23 @@ articulations_dict = { "inverted-mordent": "prall", "inverted-turn": "reverseturn", "mordent": "mordent", - #"open-string": "", + "open-string": "open", #"plop": "", #"pluck": "", - #"portato": (musicexp.ShortArticulationEvent, "_"), # does not exist in MusicXML #"pull-off": "", #"schleifer": "?", #"scoop": "", #"shake": "?", #"snap-pizzicato": "", #"spiccato": "", - "staccatissimo": (musicexp.ShortArticulationEvent, "|"), - "staccato": (musicexp.ShortArticulationEvent, "."), - "stopped": (musicexp.ShortArticulationEvent, "+"), + "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo" + "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato" + "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped" #"stress": "", - #"string": "", - "strong-accent": (musicexp.ShortArticulationEvent, "^"), + "string": musicxml_string_event, + "strong-accent": (musicexp.ShortArticulationEvent, "^"), # or "marcato" #"tap": "", - "tenuto": (musicexp.ShortArticulationEvent, "-"), + "tenuto": (musicexp.ShortArticulationEvent, "-"), # or "tenuto" #"thumb-position": "", #"toe": "", "turn": "turn", @@ -469,6 +794,79 @@ def musicxml_dynamics_to_lily_event (dynentry): event.type = dynentry.get_name () return event +# Convert single-color two-byte strings to numbers 0.0 - 1.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 musicxml_words_to_lily_event (words): + event = musicexp.TextEvent () + text = words.get_text () + text = re.sub ('^ *\n? *', '', text) + text = re.sub (' *\n? *$', '', text) + event.text = text + + if hasattr (words, 'default-y'): + offset = getattr (words, 'default-y') + try: + off = string.atoi (offset) + if off > 0: + event.force_direction = 1 + else: + event.force_direction = -1 + except ValueError: + event.force_direction = 0 + + 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": '\\bigger\\huge' + }.get (size, '') + if font_size: + event.markup += font_size + + 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 font_style: + event.markup += font_style + + # TODO: How should I best convert the font-family attribute? + + # TODO: How can I represent the underline, overline and line-through + # attributes in Lilypond? Values of these attributes indicate + # the number of lines + + return event + direction_spanners = [ 'octave-shift', 'pedal', 'wedge' ] @@ -480,12 +878,18 @@ def musicxml_direction_to_lily (n): dirtype_children += dt.get_all_children () for entry in dirtype_children: + if entry.get_name () == "dynamics": for dynentry in entry.get_all_children (): ev = musicxml_dynamics_to_lily_event (dynentry) if ev: res.append (ev) + if entry.get_name () == "words": + ev = musicxml_words_to_lily_event (entry) + if ev: + res.append (ev) + # octave shifts. pedal marks, hairpins etc. are spanners: if entry.get_name() in direction_spanners: event = musicxml_spanner_to_lily_event (entry) @@ -495,6 +899,40 @@ def musicxml_direction_to_lily (n): 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 + +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 + instrument_drumtype_dict = { 'Acoustic Snare Drum': 'acousticsnare', 'Side Stick': 'sidestick', @@ -597,6 +1035,9 @@ class LilyPondVoiceBuilder: if self.pending_multibar > Rational (0): self._insert_multibar () self.elements.append (command) + def add_barline (self, barline): + # TODO: Implement merging of default barline and custom bar line + self.add_music (barline, Rational (0)) def add_partial (self, command): self.ignore_skips = True self.add_command (command) @@ -606,9 +1047,9 @@ class LilyPondVoiceBuilder: self.pending_dynamics.append (dynamic) def add_bar_check (self, number): - b = musicexp.BarCheck () + b = musicexp.BarLine () b.bar_number = number - self.add_command (b) + self.add_barline (b) def jumpto (self, moment): current_end = self.end_moment + self.pending_multibar @@ -618,7 +1059,7 @@ class LilyPondVoiceBuilder: error_message ('Negative skip %s' % diff) diff = Rational (0) - if diff > Rational (0) and not self.ignore_skips: + if diff > Rational (0) and not (self.ignore_skips and moment == 0): skip = musicexp.SkipEvent() skip.duration.duration_log = 0 skip.duration.factor = diff @@ -638,7 +1079,7 @@ class LilyPondVoiceBuilder: at = len( self.elements ) - 1 while (at >= 0 and not isinstance (self.elements[at], musicexp.EventChord) and - not isinstance (self.elements[at], musicexp.BarCheck)): + not isinstance (self.elements[at], musicexp.BarLine)): at -= 1 if (self.elements @@ -656,16 +1097,35 @@ class LilyPondVoiceBuilder: self.begin_moment = goto evc = musicexp.EventChord () self.elements.append (evc) - + + +class VoiceData: + def __init__ (self): + self.voicedata = None + self.ly_voice = None + self.lyrics_dict = {} + self.lyrics_order = [] + def musicxml_voice_to_lily_voice (voice): tuplet_events = [] modes_found = {} lyrics = {} - - # TODO: 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 - for k in voice.get_lyrics_numbers (): + return_value = VoiceData () + return_value.voicedata = voice + + # Needed for melismata detection (ignore lyrics on those notes!): + inside_slur = False + is_tied = False + is_chord = False + ignore_lyrics = False + + current_staff = None + + # 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 () + for k in return_value.lyrics_order: lyrics[k] = [] voice_builder = LilyPondVoiceBuilder() @@ -673,6 +1133,12 @@ def musicxml_voice_to_lily_voice (voice): for n in voice._elements: if n.get_name () == 'forward': continue + 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)) + current_staff = staff if isinstance (n, musicxml.Partial) and n.partial > 0: a = musicxml_partial_to_lily (n.partial) @@ -687,8 +1153,17 @@ def musicxml_voice_to_lily_voice (voice): else: voice_builder.add_command (a) continue - - if not n.get_maybe_exist_named_child ('chord'): + + 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) + continue + + is_chord = n.get_maybe_exist_named_child ('chord') + if not is_chord: try: voice_builder.jumpto (n._when) except NegativeSkip, neg: @@ -703,11 +1178,20 @@ def musicxml_voice_to_lily_voice (voice): number = 0 if number > 0: voice_builder.add_bar_check (number) - + for a in musicxml_attributes_to_lily (n): voice_builder.add_command (a) continue + if isinstance (n, musicxml.Barline): + barlines = musicxml_barline_to_lily (n) + for a in barlines: + if isinstance (a, musicexp.BarLine): + voice_builder.add_barline (a) + elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker): + voice_builder.add_command (a) + continue + if not n.__class__.__name__ == 'Note': error_message ('not a Note or Attributes? %s' % n) continue @@ -726,8 +1210,9 @@ def musicxml_voice_to_lily_voice (voice): num = 0 if num > 0: voice_builder.add_bar_check (num) - + main_event = musicxml_note_to_lily_main_event (n) + ignore_lyrics = inside_slur or is_tied or is_chord if hasattr (main_event, 'drum_type') and main_event.drum_type: modes_found['drummode'] = True @@ -756,6 +1241,7 @@ def musicxml_voice_to_lily_voice (voice): # everything into that chord instead of the ev_chord ev_chord = grace_chord ev_chord.append (main_event) + ignore_lyrics = True else: ev_chord.append (main_event) # When a note/chord has grace notes (duration==0), the duration of the @@ -764,16 +1250,16 @@ def musicxml_voice_to_lily_voice (voice): if voice_builder.current_duration () == 0 and n._duration > 0: voice_builder.set_duration (n._duration) - notations = n.get_maybe_exist_typed_child (musicxml.Notations) + notations_children = n.get_typed_children (musicxml.Notations) tuplet_event = None span_events = [] - + # The element can have the following children (+ means implemented, ~ partially, - not): # +tied | +slur | +tuplet | glissando | slide | # ornaments | technical | articulations | dynamics | # +fermata | arpeggiate | non-arpeggiate | # accidental-mark | other-notation - if notations: + for notations in notations_children: if notations.get_tuplet(): tuplet_event = notations.get_tuplet() mod = n.get_maybe_exist_typed_child (musicxml.Time_modification) @@ -788,14 +1274,23 @@ def musicxml_voice_to_lily_voice (voice): if slurs: if len (slurs) > 1: error_message ('more than 1 slur?') - + # record the slur status for the next note in the loop + if not grace: + if slurs[0].get_type () == 'start': + inside_slur = True + elif slurs[0].get_type () == 'stop': + inside_slur = False lily_ev = musicxml_spanner_to_lily_event (slurs[0]) ev_chord.append (lily_ev) - mxl_tie = notations.get_tie () - if mxl_tie and mxl_tie.type == 'start': - ev_chord.append (musicexp.TieEvent ()) - + if not grace: + mxl_tie = notations.get_tie () + if mxl_tie and mxl_tie.type == 'start': + ev_chord.append (musicexp.TieEvent ()) + is_tied = True + else: + is_tied = False + fermatas = notations.get_named_children ('fermata') for a in fermatas: ev = musicxml_fermata_to_lily_event (a) @@ -804,7 +1299,9 @@ def musicxml_voice_to_lily_voice (voice): arpeggiate = notations.get_named_children ('arpeggiate') for a in arpeggiate: - ev_chord.append (musicexp.ArpeggioEvent ()) + ev = musicxml_arpeggiate_to_lily_event (a) + if ev: + ev_chord.append (ev) glissandos = notations.get_named_children ('glissando') for a in glissandos: @@ -851,7 +1348,7 @@ def musicxml_voice_to_lily_voice (voice): ev_chord.append (ev) # Extract the lyrics - if not rest: + 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: @@ -887,6 +1384,7 @@ def musicxml_voice_to_lily_voice (voice): voice_builder.add_music (musicexp.EventChord (), Rational (0)) ly_voice = group_tuplets (voice_builder.elements, tuplet_events) + ly_voice = group_repeats (ly_voice) seq_music = musicexp.SequentialMusic () @@ -896,23 +1394,22 @@ def musicxml_voice_to_lily_voice (voice): if not isinstance(e, musicexp.KeySignatureChange)] seq_music.elements = ly_voice - lyrics_dict = {} for k in lyrics.keys (): - lyrics_dict[k] = musicexp.Lyrics () - lyrics_dict[k].lyrics_syllables = lyrics[k] + return_value.lyrics_dict[k] = musicexp.Lyrics () + return_value.lyrics_dict[k].lyrics_syllables = lyrics[k] if len (modes_found) > 1: error_message ('Too many modes found %s' % modes_found.keys ()) - return_value = seq_music + return_value.ly_voice = seq_music for mode in modes_found.keys (): v = musicexp.ModeChangingMusicWrapper() - v.element = return_value + v.element = seq_music v.mode = mode - return_value = v + return_value.ly_voice = v - return (return_value, lyrics_dict) + return return_value def musicxml_id_to_lily (id): @@ -949,30 +1446,33 @@ def voices_in_part (part): """Return a Name -> Voice dictionary for PART""" part.interpret () part.extract_voices () - voice_dict = part.get_voices () + voices = part.get_voices () + part_info = part.get_staff_attributes () - return voice_dict + return (voices, part_info) def voices_in_part_in_parts (parts): """return a Part -> Name -> Voice dictionary""" - return dict([(p, voices_in_part (p)) for p in parts]) + return dict([(p.id, voices_in_part (p)) for p in parts]) def get_all_voices (parts): all_voices = voices_in_part_in_parts (parts) all_ly_voices = {} - for p, name_voice in all_voices.items (): + all_ly_staffinfo = {} + for p, (name_voice, staff_info) in all_voices.items (): part_ly_voices = {} for n, v in name_voice.items (): progress ("Converting to LilyPond expressions...") # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics}) - part_ly_voices[n] = (musicxml_voice_to_lily_voice (v), v) + part_ly_voices[n] = musicxml_voice_to_lily_voice (v) all_ly_voices[p] = part_ly_voices - - return all_ly_voices + all_ly_staffinfo[p] = staff_info + + return (all_ly_voices, all_ly_staffinfo) def option_parser (): @@ -1013,34 +1513,30 @@ Copyright (c) 2005--2007 by '''?group=gmane.comp.gnu.lilypond.bugs\n''')) return p -def music_xml_voice_name_to_lily_name (part, name): - str = "Part%sVoice%s" % (part.id, name) +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, name, lyricsnr): - str = "Part%sVoice%sLyrics%s" % (part.id, name, lyricsnr) +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 print_voice_definitions (printer, part_list, voices): - part_dict={} - for (part, nv_dict) in voices.items(): - part_dict[part.id] = (part, nv_dict) - for part in part_list: - (p, nv_dict) = part_dict.get (part.id, (None, {})) - for (name, ((voice, lyrics), mxlvoice)) in nv_dict.items (): - k = music_xml_voice_name_to_lily_name (p, name) + part_id = part.id + nv_dict = voices.get (part_id, {}) + for (name, voice) in nv_dict.items (): + k = music_xml_voice_name_to_lily_name (part_id, name) printer.dump ('%s = ' % k) - voice.print_ly (printer) + voice.ly_voice.print_ly (printer) printer.newline() - - for l in lyrics.keys (): - lname = music_xml_lyrics_name_to_lily_name (p, name, l) + for l in voice.lyrics_order: + lname = music_xml_lyrics_name_to_lily_name (part_id, name, l) printer.dump ('%s = ' %lname ) - lyrics[l].print_ly (printer) + voice.lyrics_dict[l].print_ly (printer) printer.newline() - + def uniq_list (l): return dict ([(elt,1) for elt in l]).keys () @@ -1052,31 +1548,28 @@ def uniq_list (l): # ... # ] # ] -# raw_voices is of the form [(voicename, lyrics)*] -def format_staff_info (part, staff_id, raw_voices): +# raw_voices is of the form [(voicename, lyricsids)*] +def format_staff_info (part_id, staff_id, raw_voices): voices = [] - for (v, lyrics) in raw_voices: - voice_name = music_xml_voice_name_to_lily_name (part, v) - voice_lyrics = [music_xml_lyrics_name_to_lily_name (part, v, l) - for l in lyrics.keys ()] + for (v, lyricsids) 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] voices.append ([voice_name, voice_lyrics]) return [staff_id, voices] def update_score_setup (score_structure, part_list, voices): - part_dict = dict ([(p.id, p) for p in voices.keys ()]) - final_part_dict = {} for part_definition in part_list: - part_name = part_definition.id - part = part_dict.get (part_name) - if not part: - error_message ('unknown part in part-list: %s' % part_name) + part_id = part_definition.id + nv_dict = voices.get (part_id) + if not nv_dict: + error_message ('unknown part in part-list: %s' % part_id) continue - nv_dict = voices.get (part) staves = reduce (lambda x,y: x+ y, - [mxlvoice._staves.keys () - for (v, mxlvoice) in nv_dict.values ()], + [voice.voicedata._staves.keys () + for voice in nv_dict.values ()], []) staves_info = [] if len (staves) > 1: @@ -1084,15 +1577,15 @@ def update_score_setup (score_structure, part_list, voices): staves = uniq_list (staves) staves.sort () for s in staves: - thisstaff_raw_voices = [(voice_name, lyrics) - for (voice_name, ((music, lyrics), mxlvoice)) in nv_dict.items () - if mxlvoice._start_staff == s] - staves_info.append (format_staff_info (part, s, thisstaff_raw_voices)) + thisstaff_raw_voices = [(voice_name, voice.lyrics_order) + for (voice_name, voice) in nv_dict.items () + if voice.voicedata._start_staff == s] + staves_info.append (format_staff_info (part_id, s, thisstaff_raw_voices)) else: - thisstaff_raw_voices = [(voice_name, lyrics) - for (voice_name, ((music, lyrics), mxlvoice)) in nv_dict.items ()] - staves_info.append (format_staff_info (part, None, thisstaff_raw_voices)) - score_structure.setPartInformation (part_name, staves_info) + thisstaff_raw_voices = [(voice_name, voice.lyrics_order) + 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) def print_ly_preamble (printer, filename): printer.dump_version () @@ -1119,17 +1612,17 @@ def convert (filename, options): progress ("Reading MusicXML from %s ..." % filename) tree = read_musicxml (filename, options.use_lxml) + parts = tree.get_typed_children (musicxml.Part) + (voices, staff_info) = get_all_voices (parts) score_structure = None mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list) if mxl_pl: - score_structure = extract_score_layout (mxl_pl) + score_structure = extract_score_layout (mxl_pl, staff_info) part_list = mxl_pl.get_named_children ("score-part") # score information is contained in the , or tags score_information = extract_score_information (tree) - parts = tree.get_typed_children (musicxml.Part) - voices = get_all_voices (parts) update_score_setup (score_structure, part_list, voices) if not options.output_name: