def _get_name_node(self): vbox_text_nodes = self.xml_tree.findall('Score/Staff/[@id="1"]/VBox/Text') for vbox_text_node in vbox_text_nodes: if find_exactly_one(vbox_text_node, 'style').text == 'Instrument Name (Part)': return find_exactly_one(vbox_text_node, 'text') raise ValueError('No vbox part node found')
def _assert_nonlinked_score_metadata_correct(self, root, work_title): score_xml = find_exactly_one(root, 'Score') # MuseScore default, true for all the resource files self.assertEqual(find_exactly_one(score_xml, 'Style/Spatium').text, '1.76389') self.assertGreater(len(score_xml.findall('metaTag')), 1) self.assertEqual(find_exactly_one(score_xml, 'metaTag/[@name="workTitle"]').text, work_title)
def _assert_vbox_field_match(self, root, field_name, field_value): vbox_node = find_exactly_one(root, 'Score/Staff/VBox') matching_nodes = 0 for vbox_text_node in vbox_node.findall('Text'): if find_exactly_one(vbox_text_node, 'style').text == field_name: self.assertEqual(find_exactly_one(vbox_text_node, 'text').text, field_value) matching_nodes += 1 self.assertEqual(matching_nodes, 1, 'Did not find a matching node')
def test_split_keep_global_text(self): [violin1, violin2, piano] = self._multi_part_global_text_score.generate_part_scores() violin1_root = _write_score_to_string_and_read_xml(violin1) self._assert_staff_id_1_measure_index_has_element_name(violin1_root, 0, 'RehearsalMark') self._assert_staff_id_1_measure_index_has_element_name(violin1_root, 0, 'Tempo') self._assert_staff_id_1_measure_index_has_element_name(violin1_root, 1, 'SystemText') self._assert_staff_id_1_measure_index_has_element_name(violin1_root, 2, 'Tempo') self._assert_staff_id_1_measure_index_has_element_name(violin1_root, 3, 'RehearsalMark') violin2_root = _write_score_to_string_and_read_xml(violin2) self._assert_staff_id_1_measure_index_has_element_name(violin2_root, 0, 'RehearsalMark') self._assert_staff_id_1_measure_index_has_element_name(violin2_root, 0, 'Tempo') self._assert_staff_id_1_measure_index_has_element_name(violin2_root, 1, 'SystemText') self._assert_staff_id_1_measure_index_has_element_name(violin2_root, 2, 'Tempo') self._assert_staff_id_1_measure_index_has_element_name(violin2_root, 3, 'RehearsalMark') piano_root = _write_score_to_string_and_read_xml(piano) self._assert_staff_id_1_measure_index_has_element_name(piano_root, 0, 'RehearsalMark') self._assert_staff_id_1_measure_index_has_element_name(piano_root, 0, 'Tempo') self._assert_staff_id_1_measure_index_has_element_name(piano_root, 1, 'SystemText') self._assert_staff_id_1_measure_index_has_element_name(piano_root, 2, 'Tempo') self._assert_staff_id_1_measure_index_has_element_name(piano_root, 3, 'RehearsalMark') piano_lh_root = find_exactly_one(piano_root, 'Score/Staff/[@id="2"]') self.assertEqual(len(piano_lh_root.findall('Measure/voice/RehearsalMark')), 0) self.assertEqual(len(piano_lh_root.findall('Measure/voice/Tempo')), 0) self.assertEqual(len(piano_lh_root.findall('Measure/voice/SystemText')), 0)
def _assert_staff_id_1_measure_index_has_element_name(self, root, measure_index, element_name): measure_voice_nodes = find_exactly_one(root, 'Score/Staff/[@id="1"]').findall('Measure/voice') self.assertLess(measure_index, len(measure_voice_nodes), 'measure_index is larger than number of measure nodes') for i, measure_voice_node in enumerate(measure_voice_nodes): if i == measure_index: self.assertEqual(len(measure_voice_node.findall(element_name)), 1)
def _add_vbox_with_part_text(xml_tree, original_vbox_node): vbox_text_node = ET.Element('Text') vbox_text_node.extend([ create_node_with_text('style', 'Instrument Name (Part)'), create_node_with_text('text', find_exactly_one(xml_tree, 'Score/Part/Instrument/longName').text) ]) vbox_node = copy.deepcopy(original_vbox_node) vbox_node.append(vbox_text_node) xml_tree.find('Score/Staff').insert(0, vbox_node)
def _remove_unneeded_staves_and_staff_vbox(xml_tree): staff_ids = [staff.get('id') for staff in xml_tree.findall('Score/Part/Staff')] score_node = find_exactly_one(xml_tree, 'Score') for score_staff_node in score_node.findall('Staff'): if score_staff_node.get('id') not in staff_ids: score_node.remove(score_staff_node) continue existing_staff_vbox_node = score_staff_node.find('VBox') if existing_staff_vbox_node is not None: score_staff_node.remove(existing_staff_vbox_node)
def _find_all_measure_global_text_nodes(xml_tree): _GLOBAL_TEXT_NODE_NAMES = ['RehearsalMark', 'Tempo', 'SystemText'] # At least as of time of writing, MuseScore only allows these elements on staff id 1 (i.e. the first staff) measure_global_text_nodes_list = [] measure_voice_nodes = find_exactly_one(xml_tree, 'Score/Staff/[@id="1"]').findall('Measure/voice') for i, measure_voice_node in enumerate(measure_voice_nodes): global_text_nodes_for_measure = [child_node for child_node in measure_voice_node if child_node.tag in _GLOBAL_TEXT_NODE_NAMES] if len(global_text_nodes_for_measure) > 0: measure_global_text_nodes_list.append(_MeasureGlobalTextNodes(i, global_text_nodes_for_measure)) return measure_global_text_nodes_list
def _fix_split_part_score_part_names(self, part_scores): part_name_to_num_appearances = defaultdict(int) for part_node in self._xml_tree.findall('Score/Part'): part_name_to_num_appearances[find_exactly_one(part_node, 'Instrument/longName').text] += 1 # Note that this does not handle if there's a "Violin 1", "Violin", and "Violin" part. # It's unclear what should be done (maybe the violin parts should be named "Solo Violin", for example) part_name_to_correct_part_number = defaultdict(int) for part in part_scores: part_name = part.get_name() is_duplicate_part_name = part_name_to_num_appearances[part_name] > 1 if is_duplicate_part_name: part_name_to_correct_part_number[part_name] += 1 part.set_name(f'{part_name} {part_name_to_correct_part_number[part_name]}')
def _apply_measure_global_text_nodes(xml_tree, measure_global_text_nodes_list): measure_voice_nodes = find_exactly_one(xml_tree, 'Score/Staff/[@id="1"]').findall('Measure/voice') for measure_global_text_nodes in measure_global_text_nodes_list: measure_voice_node = measure_voice_nodes[measure_global_text_nodes.measure_index] # Inserting the nodes right after Time/ KeySig (as opposed to just at the end) positions the global text at # the beginning of the measure. insertion_index = 0 for measure_voice_child_node in measure_voice_node: if measure_voice_child_node.tag not in ['TimeSig', 'KeySig']: break insertion_index += 1 # This inserts a list at insertion_index (i.e. list extend but in the middle) measure_voice_node[insertion_index:insertion_index] = measure_global_text_nodes.nodes
def create_parts_from_xml(cls, xml_tree): vbox_node = find_exactly_one(xml_tree, 'Score/Staff/[@id="1"]/VBox') measure_global_text_nodes_list = _PartScore._find_all_measure_global_text_nodes(xml_tree) parts = [] for part_index in range(len(xml_tree.findall('Score/Part'))): part_xml_tree = copy.deepcopy(xml_tree) # Ordering is important for these method calls, as they depend on each other's results. _PartScore._remove_unneeded_parts(part_xml_tree, part_index) _PartScore._remove_unneeded_staves_and_staff_vbox(part_xml_tree) # Layout breaks from the score are hopefully unneeded in the part itself, as the measure rendering has # different lines/ pages. _PartScore._remove_layout_breaks(part_xml_tree) _PartScore._add_vbox_with_part_text(part_xml_tree, vbox_node) _PartScore._fix_staff_ids(part_xml_tree) # These were never removed from the first staff, so we skip this on the first part. if part_index != 0: _PartScore._apply_measure_global_text_nodes(part_xml_tree, measure_global_text_nodes_list) parts.append(cls(part_xml_tree)) return parts
def _assert_part_correct(self, root, staff_ids, instrument_long_name): part_node = find_exactly_one(root, 'Score/Part') self.assertListEqual([s.get('id') for s in part_node.findall('Staff')], staff_ids) self.assertEqual(find_exactly_one(part_node, 'Instrument/longName').text, instrument_long_name)
def _remove_unneeded_parts(xml_tree, part_index): score_node = find_exactly_one(xml_tree, 'Score') for i, part_node in enumerate(score_node.findall('Part')): if i != part_index: score_node.remove(part_node)