From: Reinhold Kainhofer Date: Fri, 2 Nov 2007 00:49:11 +0000 (+0100) Subject: MusicXML: convert Tab and drum staves to Lilypond X-Git-Tag: release/2.11.35-1~50 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=25d705179a863839d6c4bc942ca8493c8de8a7d8;p=lilypond.git MusicXML: convert Tab and drum staves to Lilypond -) Depending on the staff attributes, create TabStaff/TabVoice, DrumStaff/DrumVoice, RhythmicStaff/Voice or ordinary Staff/Voice staves. -) From the staff attributes, create the list of string tunings for tab staves. These two points were all that was missing for proper tablature support. As an implementation detail, I changed all the keys in the dicts from the full part objects to the part ID (which is also used in the part-list element in Lilypond, so we can do proper assignments now). --- diff --git a/input/regression/musicxml/18a-TabStaves-Finale.xml b/input/regression/musicxml/18a-TabStaves-Finale.xml new file mode 100644 index 0000000000..0b878114e5 --- /dev/null +++ b/input/regression/musicxml/18a-TabStaves-Finale.xml @@ -0,0 +1,1644 @@ + + + + Tablatures + + + Finale 2007 for Windows + Dolet Light for Finale 2007 + 2007-10-30 + + + + + bracket + yes + + + Guitar + Gtr. + + Guitar + + + 1 + 26 + + + + Guitar + Gtr. + + Guitar + + + 2 + 26 + + + + Guitar + Gtr. + + Guitar + + + 3 + 26 + + + + Guitar + Gtr. + + Guitar + + + 4 + 26 + + + + Bass Guitar + Bass + + Bass Guitar + + + 5 + 35 + + + + Banjo + Bjo. + + Banjo + + + 6 + 106 + + + + Lute + L. + + Lute + + + 7 + 25 + + + + Ukulele + Uk. + + Ukulele + + + 8 + 25 + + + + + + + + + 1 + + 0 + major + + + TAB + 5 + + + 6 + + E + 2 + + + A + 2 + + + D + 3 + + + G + 3 + + + B + 3 + + + E + 4 + + + + + + + G + 3 + + 1 + 1 + quarter + none + + + 4 + 5 + + + + + + E + 5 + + 1 + 1 + quarter + none + + + 2 + 17 + + + + + + C + 4 + + 1 + 1 + quarter + none + + + 2 + 1 + + + + + + E + 3 + + 1 + 1 + quarter + none + + + 4 + 2 + + + + + + + + + G + 1 + 2 + + 1 + 1 + quarter + none + + + 6 + 4 + + + + + + + F + 1 + 3 + + 1 + 1 + quarter + none + + + 4 + 4 + + + + + + + G + 3 + + 1 + 1 + quarter + none + + + 3 + 0 + + + + + + D + 3 + + 1 + 1 + quarter + none + + + 5 + 5 + + + + + + G + 1 + 3 + + 1 + 1 + quarter + none + + + 3 + 1 + + + + + + E + 4 + + 1 + 1 + quarter + none + + + 1 + 0 + + + + + + + + + 4 + 1 + + + light-heavy + + + + + + + + 1 + + 0 + major + + + TAB + 5 + + + 6 + + E + 2 + + + A + 2 + + + D + 3 + + + G + 3 + + + B + 3 + + + E + 4 + + + + + + + D + 1 + 4 + + 1 + 1 + quarter + up + + + 2 + 4 + + + + + + G + 1 + 3 + + 1 + 1 + quarter + up + + + 3 + 1 + + + + + + C + 3 + + 1 + 1 + quarter + up + + + 5 + 3 + + + + + + 1 + 1 + quarter + + + + + + + F + 1 + 4 + + 1 + 1 + quarter + up + + + 1 + 2 + + + + + + G + 3 + + 1 + 1 + quarter + up + + + 3 + 0 + + + + + + G + 3 + + 1 + 1 + quarter + up + + + 4 + 5 + + + + + + F + 1 + 2 + + 1 + 1 + quarter + up + + + 6 + 2 + + + + + + + + + 4 + 1 + + + light-heavy + + + + + + + + 2 + + 0 + major + + + TAB + 5 + + + 6 + + D + 2 + + + A + 2 + + + D + 3 + + + F + 1 + 3 + + + A + 3 + + + E + 4 + + + + + + + A + 1 + 3 + + 2 + 1 + quarter + none + + + 2 + 1 + + + + + + B + 2 + + 1 + 1 + eighth + none + + + 5 + 2 + + + + + + A + 3 + + 1 + 1 + eighth + none + + + 3 + 3 + + + + + + 4 + 1 + half + + + + + + + G + 1 + 4 + + 2 + 1 + quarter + none + + + 1 + 4 + + + + + + D + 4 + + 2 + 1 + quarter + none + + + 2 + 5 + + + + + + G + 1 + 2 + + 2 + 1 + quarter + none + + + 6 + 6 + + + + + + D + 3 + + 2 + 1 + quarter + none + + + 4 + 0 + + + + + + + + + 8 + 1 + + + light-heavy + + + + + + + + 2 + + 0 + major + + + TAB + 5 + + + 6 + + D + 2 + + + A + 2 + + + D + 3 + + + G + 3 + + + B + 3 + + + D + 4 + + + + + + + B + 3 + + 1 + 1 + eighth + none + + + 3 + 4 + + + + + + D + 2 + + 1 + 1 + eighth + none + + + 5 + -7 + + + + + + + D + 2 + + 1 + 1 + eighth + none + + + 6 + 0 + + + + + + + G + 2 + + 1 + 1 + eighth + none + + + 6 + 5 + + + + + + + A + 2 + + 1 + 1 + eighth + none + + + 5 + 0 + + + + + + + A + 2 + + 1 + 1 + eighth + none + + + 5 + 0 + + + + + + 2 + 1 + quarter + + + + 4 + 1 + half + + + + + + + D + 4 + + 2 + 1 + quarter + none + + + 1 + 0 + + + + + + D + 3 + + 2 + 1 + quarter + none + + + 4 + 0 + + + + + + A + 2 + + 2 + 1 + quarter + none + + + 5 + 0 + + + + + + D + 2 + + 2 + 1 + quarter + none + + + 6 + 0 + + + + + + + + + 8 + 1 + + + light-heavy + + + + + + + + 1 + + 0 + major + + + TAB + 5 + + + 4 + + E + 1 + + + A + 1 + + + D + 2 + + + G + 2 + + + + + + + G + 2 + + 1 + 1 + quarter + none + + + 1 + 0 + + + + + + E + 1 + + 1 + 1 + quarter + none + + + 4 + 0 + + + + + + D + 2 + + 1 + 1 + quarter + none + + + 2 + 0 + + + + + + A + 1 + 2 + + 1 + 1 + quarter + none + + + 2 + 8 + + + + + + + + + A + 1 + + 1 + 1 + quarter + none + + + 3 + 0 + + + + + + A + 1 + + 1 + 1 + quarter + none + + + 3 + 0 + + + + + + A + 1 + + 1 + 1 + quarter + none + + + 3 + 0 + + + + + + A + 1 + + 1 + 1 + quarter + none + + + 3 + 0 + + + + + + + + + 4 + 1 + + + light-heavy + + + + + + + + 1 + + 0 + major + + + TAB + 5 + + + 5 + + G + 4 + + + D + 3 + + + G + 3 + + + C + 4 + + + D + 4 + + + + + + + D + 1 + 4 + + 1 + 1 + quarter + up + + + 2 + 3 + + + + + + D + 3 + + 1 + 1 + quarter + up + + + 4 + 0 + + + + + + D + 4 + + 1 + 1 + quarter + up + + + 1 + 0 + + + + + + 1 + 1 + quarter + + + + + + + G + 3 + + 1 + 1 + quarter + up + + + 3 + 0 + + + + + + G + 4 + + 1 + 1 + quarter + up + + + 5 + 0 + + + + + + G + 4 + + 1 + 1 + quarter + up + + + 5 + 0 + + + + + + G + 4 + + 1 + 1 + quarter + up + + + 5 + 0 + + + + + + + + + 4 + 1 + + + light-heavy + + + + + + + + 1 + + 0 + major + + + TAB + 5 + + + 6 + + G + 2 + + + C + 3 + + + F + 3 + + + A + 3 + + + D + 4 + + + G + 4 + + + + + + + C + 1 + 4 + + 1 + 1 + quarter + up + + + 3 + 4 + + + + + + C + 1 + 3 + + 1 + 1 + quarter + up + + + 5 + 1 + + + + + + A + 4 + + 1 + 1 + quarter + up + + + 1 + 2 + + + + + + E + 4 + + 1 + 1 + quarter + up + + + 3 + 7 + + + + + + + + + D + 4 + + 1 + 1 + quarter + up + + + 2 + 0 + + + + + + F + 3 + + 1 + 1 + quarter + up + + + 4 + 0 + + + + + + G + 2 + + 1 + 1 + quarter + up + + + 6 + 0 + + + + + + C + 3 + + 1 + 1 + quarter + up + + + 5 + 0 + + + + + + + + + 4 + 1 + + + light-heavy + + + + + + + + 1 + + 0 + major + + + TAB + 5 + + + 4 + + A + 4 + + + E + 4 + + + C + 4 + + + G + 4 + + + + + + + E + 6 + + 1 + 1 + quarter + up + + + 3 + 24 + + + + + + B + 4 + + 1 + 1 + quarter + up + + + 1 + 4 + + + + + + C + 4 + + 1 + 1 + quarter + up + + + 2 + 0 + + + + + + E + 4 + + 1 + 1 + quarter + up + + + 3 + 0 + + + + + + + + + C + 4 + + 1 + 1 + quarter + up + + + 2 + 0 + + + + + + C + 4 + + 1 + 1 + quarter + up + + + 2 + 0 + + + + + + C + 4 + + 1 + 1 + quarter + up + + + 2 + 0 + + + + + + C + 4 + + 1 + 1 + quarter + up + + + 2 + 0 + + + + + + + + + 4 + 1 + + + light-heavy + + + + + diff --git a/python/musicexp.py b/python/musicexp.py index edae323f79..2289129289 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -1111,10 +1111,12 @@ class StaffGroup: class Staff (StaffGroup): - def __init__ (self): - StaffGroup.__init__ (self, "Staff") + def __init__ (self, command = "Staff"): + StaffGroup.__init__ (self, command) self.is_group = False self.part = None + self.voice_command = "Voice" + self.substafftype = None def print_ly_overrides (self, printer): pass @@ -1122,12 +1124,15 @@ class Staff (StaffGroup): def print_ly_contents (self, printer): if not self.id or not self.part_information: return + sub_staff_type = self.substafftype + if not sub_staff_type: + sub_staff_type = self.stafftype for [staff_id, voices] in self.part_information: if staff_id: - printer ('\\context %s = "%s" << ' % (self.stafftype, staff_id)) + printer ('\\context %s = "%s" << ' % (sub_staff_type, staff_id)) else: - printer ('\\context %s << ' % self.stafftype) + printer ('\\context %s << ' % sub_staff_type) printer.newline () n = 0 nr_voices = len (voices) @@ -1137,7 +1142,7 @@ class Staff (StaffGroup): if nr_voices > 1: voice_count_text = {1: ' \\voiceOne', 2: ' \\voiceTwo', 3: ' \\voiceThree'}.get (n, ' \\voiceFour') - printer ('\\context Voice = "%s" {%s \\%s }' % (v,voice_count_text,v)) + printer ('\\context %s = "%s" {%s \\%s }' % (self.voice_command, v, voice_count_text, v)) printer.newline () for l in lyrics: @@ -1148,8 +1153,43 @@ class Staff (StaffGroup): def print_ly (self, printer): if self.part_information and len (self.part_information) > 1: self.stafftype = "PianoStaff" + self.substafftype = "Staff" StaffGroup.print_ly (self, printer) +class TabStaff (Staff): + def __init__ (self, command = "TabStaff"): + Staff.__init__ (self, command) + self.string_tunings = [] + self.tablature_format = None + self.voice_command = "TabVoice" + def print_ly_overrides (self, printer): + if self.string_tunings or self.tablature_format: + printer.dump ("\\with {") + if self.string_tunings: + printer.dump ("stringTunings = #'(") + for i in self.string_tunings: + printer.dump ("%s" % i.semitones ()) + printer.dump (")") + if self.tablature_format: + printer.dump ("tablatureFormat = #%s" % self.tablature_format) + printer.dump ("}") + + +class DrumStaff (Staff): + def __init__ (self, command = "DrumStaff"): + Staff.__init__ (self, command) + self.drum_style_table = None + self.voice_command = "DrumVoice" + def print_ly_overrides (self, printer): + if self.drum_style_table: + printer.dump ("\with {") + printer.dump ("drumStyleTable = #%s" % self.drum_style_table) + printer.dump ("}") + +class RhythmicStaff (Staff): + def __init__ (self, command = "RhythmicStaff"): + Staff.__init__ (self, command) + def test_pitch (): bflat = Pitch() diff --git a/python/musicxml.py b/python/musicxml.py index cb8e1957cb..6949fe28d5 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -442,7 +442,7 @@ class Musicxml_voice: class Part (Music_xml_node): def __init__ (self): Music_xml_node.__init__ (self) - self._voices = [] + self._voices = {} self._staff_attributes_dict = {} def get_part_list (self): @@ -688,6 +688,8 @@ class Part (Music_xml_node): 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): diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 9b99d3abd0..2c130b42fc 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -79,7 +79,83 @@ class PartGroupInfo: return '' -def extract_score_layout (part_list): +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 () + + return staff + + +def extract_score_layout (part_list, staffinfo): layout = musicexp.StaffGroup (None) if not part_list: return layout @@ -87,7 +163,11 @@ def extract_score_layout (part_list): 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! @@ -132,7 +212,9 @@ def extract_score_layout (part_list): if not group_info.is_empty (): staves.append (group_info) group_info = PartGroupInfo () - staves.append (read_score_part (el)) + staff = read_score_part (el) + if staff: + staves.append (staff) elif isinstance (el, musicxml.Part_group): if el.type == "start": group_info.add_start (el) @@ -1039,9 +1121,9 @@ def musicxml_voice_to_lily_voice (voice): current_staff = None - # 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 + # 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] = [] @@ -1364,20 +1446,22 @@ 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 (): @@ -1386,8 +1470,9 @@ def get_all_voices (parts): 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 (): @@ -1428,35 +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 (): + 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 (p, name) + k = music_xml_voice_name_to_lily_name (part_id, name) printer.dump ('%s = ' % k) voice.ly_voice.print_ly (printer) printer.newline() - for l in voice.lyrics_order: - lname = music_xml_lyrics_name_to_lily_name (p, name, l) + lname = music_xml_lyrics_name_to_lily_name (part_id, name, l) printer.dump ('%s = ' %lname ) voice.lyrics_dict[l].print_ly (printer) printer.newline() - + def uniq_list (l): return dict ([(elt,1) for elt in l]).keys () @@ -1469,27 +1549,24 @@ def uniq_list (l): # ] # ] # raw_voices is of the form [(voicename, lyricsids)*] -def format_staff_info (part, staff_id, raw_voices): +def format_staff_info (part_id, staff_id, raw_voices): voices = [] for (v, lyricsids) 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) + 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, [voice.voicedata._staves.keys () for voice in nv_dict.values ()], @@ -1503,12 +1580,12 @@ def update_score_setup (score_structure, part_list, 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, 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) for (voice_name, voice) in nv_dict.items ()] - staves_info.append (format_staff_info (part, None, thisstaff_raw_voices)) - score_structure.set_part_information (part_name, staves_info) + 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 () @@ -1535,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: