+ 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 = musicxml_step_to_lily (step)
+
+ 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
+
+ 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))
+ 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!
+ if partname and partname.get_text() != "MusicXML Part":
+ staff.instrument_name = partname.get_text ()
+ if el.get_maybe_exist_named_child ('part-abbreviation'):
+ staff.short_instrument_name = el.get_maybe_exist_named_child ('part-abbreviation').get_text ()
+ # 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 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)
+ if staff:
+ staves.append (staff)
+ elif isinstance (el, musicxml.Part_group):
+ if el.type == "start":
+ group_info.add_start (el)
+ elif el.type == "stop":
+ 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